diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-19 11:55:32 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-19 15:43:29 +0000 |
| commit | fa065ad128882755f2a988d6203b59a2ab5e38ff (patch) | |
| tree | e8326de70a6e6ea56b5bf4250e0a00a3cda4afed | |
| parent | 98c6fa4bfa897ff0b8f9c95ea698d4d065b5e9f3 (diff) | |
add landing page and nostr-relay-builder relay on same port
| -rw-r--r-- | Cargo.lock | 754 | ||||
| -rw-r--r-- | Cargo.toml | 18 | ||||
| -rw-r--r-- | src/http/landing.rs | 37 | ||||
| -rw-r--r-- | src/http/mod.rs | 31 | ||||
| -rw-r--r-- | src/http/websocket.rs | 73 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 24 | ||||
| -rw-r--r-- | src/nostr/builder.rs | 121 | ||||
| -rw-r--r-- | src/nostr/mod.rs | 2 | ||||
| -rw-r--r-- | src/nostr/relay.rs | 340 | ||||
| -rw-r--r-- | src/storage/mod.rs | 132 | ||||
| -rw-r--r-- | templates/landing.html | 151 | ||||
| -rw-r--r-- | tests/nip01_compliance.rs | 50 | ||||
| -rw-r--r-- | tests/nip34_announcements.rs | 111 |
14 files changed, 1227 insertions, 619 deletions
| @@ -3,6 +3,209 @@ | |||
| 3 | version = 3 | 3 | version = 3 |
| 4 | 4 | ||
| 5 | [[package]] | 5 | [[package]] |
| 6 | name = "actix-codec" | ||
| 7 | version = "0.5.2" | ||
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 9 | checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" | ||
| 10 | dependencies = [ | ||
| 11 | "bitflags", | ||
| 12 | "bytes", | ||
| 13 | "futures-core", | ||
| 14 | "futures-sink", | ||
| 15 | "memchr", | ||
| 16 | "pin-project-lite", | ||
| 17 | "tokio", | ||
| 18 | "tokio-util", | ||
| 19 | "tracing", | ||
| 20 | ] | ||
| 21 | |||
| 22 | [[package]] | ||
| 23 | name = "actix-http" | ||
| 24 | version = "3.11.2" | ||
| 25 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 26 | checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" | ||
| 27 | dependencies = [ | ||
| 28 | "actix-codec", | ||
| 29 | "actix-rt", | ||
| 30 | "actix-service", | ||
| 31 | "actix-utils", | ||
| 32 | "base64", | ||
| 33 | "bitflags", | ||
| 34 | "brotli", | ||
| 35 | "bytes", | ||
| 36 | "bytestring", | ||
| 37 | "derive_more", | ||
| 38 | "encoding_rs", | ||
| 39 | "flate2", | ||
| 40 | "foldhash", | ||
| 41 | "futures-core", | ||
| 42 | "h2", | ||
| 43 | "http 0.2.12", | ||
| 44 | "httparse", | ||
| 45 | "httpdate", | ||
| 46 | "itoa", | ||
| 47 | "language-tags", | ||
| 48 | "local-channel", | ||
| 49 | "mime", | ||
| 50 | "percent-encoding", | ||
| 51 | "pin-project-lite", | ||
| 52 | "rand 0.9.2", | ||
| 53 | "sha1", | ||
| 54 | "smallvec", | ||
| 55 | "tokio", | ||
| 56 | "tokio-util", | ||
| 57 | "tracing", | ||
| 58 | "zstd", | ||
| 59 | ] | ||
| 60 | |||
| 61 | [[package]] | ||
| 62 | name = "actix-macros" | ||
| 63 | version = "0.2.4" | ||
| 64 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 65 | checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" | ||
| 66 | dependencies = [ | ||
| 67 | "quote", | ||
| 68 | "syn", | ||
| 69 | ] | ||
| 70 | |||
| 71 | [[package]] | ||
| 72 | name = "actix-router" | ||
| 73 | version = "0.5.3" | ||
| 74 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 75 | checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" | ||
| 76 | dependencies = [ | ||
| 77 | "bytestring", | ||
| 78 | "cfg-if", | ||
| 79 | "http 0.2.12", | ||
| 80 | "regex", | ||
| 81 | "regex-lite", | ||
| 82 | "serde", | ||
| 83 | "tracing", | ||
| 84 | ] | ||
| 85 | |||
| 86 | [[package]] | ||
| 87 | name = "actix-rt" | ||
| 88 | version = "2.11.0" | ||
| 89 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 90 | checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" | ||
| 91 | dependencies = [ | ||
| 92 | "futures-core", | ||
| 93 | "tokio", | ||
| 94 | ] | ||
| 95 | |||
| 96 | [[package]] | ||
| 97 | name = "actix-server" | ||
| 98 | version = "2.6.0" | ||
| 99 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 100 | checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" | ||
| 101 | dependencies = [ | ||
| 102 | "actix-rt", | ||
| 103 | "actix-service", | ||
| 104 | "actix-utils", | ||
| 105 | "futures-core", | ||
| 106 | "futures-util", | ||
| 107 | "mio", | ||
| 108 | "socket2 0.5.10", | ||
| 109 | "tokio", | ||
| 110 | "tracing", | ||
| 111 | ] | ||
| 112 | |||
| 113 | [[package]] | ||
| 114 | name = "actix-service" | ||
| 115 | version = "2.0.3" | ||
| 116 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 117 | checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" | ||
| 118 | dependencies = [ | ||
| 119 | "futures-core", | ||
| 120 | "pin-project-lite", | ||
| 121 | ] | ||
| 122 | |||
| 123 | [[package]] | ||
| 124 | name = "actix-utils" | ||
| 125 | version = "3.0.1" | ||
| 126 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 127 | checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" | ||
| 128 | dependencies = [ | ||
| 129 | "local-waker", | ||
| 130 | "pin-project-lite", | ||
| 131 | ] | ||
| 132 | |||
| 133 | [[package]] | ||
| 134 | name = "actix-web" | ||
| 135 | version = "4.12.0" | ||
| 136 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 137 | checksum = "2233f53f6cb18ae038ce1f0713ca0c72ca0c4b71fe9aaeb59924ce2c89c6dd85" | ||
| 138 | dependencies = [ | ||
| 139 | "actix-codec", | ||
| 140 | "actix-http", | ||
| 141 | "actix-macros", | ||
| 142 | "actix-router", | ||
| 143 | "actix-rt", | ||
| 144 | "actix-server", | ||
| 145 | "actix-service", | ||
| 146 | "actix-utils", | ||
| 147 | "actix-web-codegen", | ||
| 148 | "bytes", | ||
| 149 | "bytestring", | ||
| 150 | "cfg-if", | ||
| 151 | "cookie", | ||
| 152 | "derive_more", | ||
| 153 | "encoding_rs", | ||
| 154 | "foldhash", | ||
| 155 | "futures-core", | ||
| 156 | "futures-util", | ||
| 157 | "impl-more", | ||
| 158 | "itoa", | ||
| 159 | "language-tags", | ||
| 160 | "log", | ||
| 161 | "mime", | ||
| 162 | "once_cell", | ||
| 163 | "pin-project-lite", | ||
| 164 | "regex", | ||
| 165 | "regex-lite", | ||
| 166 | "serde", | ||
| 167 | "serde_json", | ||
| 168 | "serde_urlencoded", | ||
| 169 | "smallvec", | ||
| 170 | "socket2 0.6.1", | ||
| 171 | "time", | ||
| 172 | "tracing", | ||
| 173 | "url", | ||
| 174 | ] | ||
| 175 | |||
| 176 | [[package]] | ||
| 177 | name = "actix-web-codegen" | ||
| 178 | version = "4.3.0" | ||
| 179 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 180 | checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" | ||
| 181 | dependencies = [ | ||
| 182 | "actix-router", | ||
| 183 | "proc-macro2", | ||
| 184 | "quote", | ||
| 185 | "syn", | ||
| 186 | ] | ||
| 187 | |||
| 188 | [[package]] | ||
| 189 | name = "actix-ws" | ||
| 190 | version = "0.3.0" | ||
| 191 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 192 | checksum = "a3a1fb4f9f2794b0aadaf2ba5f14a6f034c7e86957b458c506a8cb75953f2d99" | ||
| 193 | dependencies = [ | ||
| 194 | "actix-codec", | ||
| 195 | "actix-http", | ||
| 196 | "actix-web", | ||
| 197 | "bytestring", | ||
| 198 | "futures-core", | ||
| 199 | "tokio", | ||
| 200 | ] | ||
| 201 | |||
| 202 | [[package]] | ||
| 203 | name = "adler2" | ||
| 204 | version = "2.0.1" | ||
| 205 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 206 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" | ||
| 207 | |||
| 208 | [[package]] | ||
| 6 | name = "aead" | 209 | name = "aead" |
| 7 | version = "0.5.2" | 210 | version = "0.5.2" |
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | 211 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -22,6 +225,21 @@ dependencies = [ | |||
| 22 | ] | 225 | ] |
| 23 | 226 | ||
| 24 | [[package]] | 227 | [[package]] |
| 228 | name = "alloc-no-stdlib" | ||
| 229 | version = "2.0.4" | ||
| 230 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 231 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" | ||
| 232 | |||
| 233 | [[package]] | ||
| 234 | name = "alloc-stdlib" | ||
| 235 | version = "0.2.2" | ||
| 236 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 237 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" | ||
| 238 | dependencies = [ | ||
| 239 | "alloc-no-stdlib", | ||
| 240 | ] | ||
| 241 | |||
| 242 | [[package]] | ||
| 25 | name = "android_system_properties" | 243 | name = "android_system_properties" |
| 26 | version = "0.1.5" | 244 | version = "0.1.5" |
| 27 | source = "registry+https://github.com/rust-lang/crates.io-index" | 245 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -244,16 +462,31 @@ dependencies = [ | |||
| 244 | ] | 462 | ] |
| 245 | 463 | ||
| 246 | [[package]] | 464 | [[package]] |
| 247 | name = "bumpalo" | 465 | name = "brotli" |
| 248 | version = "3.19.0" | 466 | version = "8.0.2" |
| 249 | source = "registry+https://github.com/rust-lang/crates.io-index" | 467 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 250 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" | 468 | checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" |
| 469 | dependencies = [ | ||
| 470 | "alloc-no-stdlib", | ||
| 471 | "alloc-stdlib", | ||
| 472 | "brotli-decompressor", | ||
| 473 | ] | ||
| 251 | 474 | ||
| 252 | [[package]] | 475 | [[package]] |
| 253 | name = "byteorder" | 476 | name = "brotli-decompressor" |
| 254 | version = "1.5.0" | 477 | version = "5.0.0" |
| 478 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 479 | checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" | ||
| 480 | dependencies = [ | ||
| 481 | "alloc-no-stdlib", | ||
| 482 | "alloc-stdlib", | ||
| 483 | ] | ||
| 484 | |||
| 485 | [[package]] | ||
| 486 | name = "bumpalo" | ||
| 487 | version = "3.19.0" | ||
| 255 | source = "registry+https://github.com/rust-lang/crates.io-index" | 488 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 256 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | 489 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" |
| 257 | 490 | ||
| 258 | [[package]] | 491 | [[package]] |
| 259 | name = "bytes" | 492 | name = "bytes" |
| @@ -262,6 +495,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 262 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" | 495 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" |
| 263 | 496 | ||
| 264 | [[package]] | 497 | [[package]] |
| 498 | name = "bytestring" | ||
| 499 | version = "1.5.0" | ||
| 500 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 501 | checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" | ||
| 502 | dependencies = [ | ||
| 503 | "bytes", | ||
| 504 | ] | ||
| 505 | |||
| 506 | [[package]] | ||
| 265 | name = "cbc" | 507 | name = "cbc" |
| 266 | version = "0.1.2" | 508 | version = "0.1.2" |
| 267 | source = "registry+https://github.com/rust-lang/crates.io-index" | 509 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -277,6 +519,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 277 | checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" | 519 | checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" |
| 278 | dependencies = [ | 520 | dependencies = [ |
| 279 | "find-msvc-tools", | 521 | "find-msvc-tools", |
| 522 | "jobserver", | ||
| 523 | "libc", | ||
| 280 | "shlex", | 524 | "shlex", |
| 281 | ] | 525 | ] |
| 282 | 526 | ||
| @@ -381,6 +625,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 381 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" | 625 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" |
| 382 | 626 | ||
| 383 | [[package]] | 627 | [[package]] |
| 628 | name = "cookie" | ||
| 629 | version = "0.16.2" | ||
| 630 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 631 | checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" | ||
| 632 | dependencies = [ | ||
| 633 | "percent-encoding", | ||
| 634 | "time", | ||
| 635 | "version_check", | ||
| 636 | ] | ||
| 637 | |||
| 638 | [[package]] | ||
| 384 | name = "core-foundation-sys" | 639 | name = "core-foundation-sys" |
| 385 | version = "0.8.7" | 640 | version = "0.8.7" |
| 386 | source = "registry+https://github.com/rust-lang/crates.io-index" | 641 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -396,6 +651,15 @@ dependencies = [ | |||
| 396 | ] | 651 | ] |
| 397 | 652 | ||
| 398 | [[package]] | 653 | [[package]] |
| 654 | name = "crc32fast" | ||
| 655 | version = "1.5.0" | ||
| 656 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 657 | checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" | ||
| 658 | dependencies = [ | ||
| 659 | "cfg-if", | ||
| 660 | ] | ||
| 661 | |||
| 662 | [[package]] | ||
| 399 | name = "crypto-common" | 663 | name = "crypto-common" |
| 400 | version = "0.1.6" | 664 | version = "0.1.6" |
| 401 | source = "registry+https://github.com/rust-lang/crates.io-index" | 665 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -413,6 +677,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 413 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" | 677 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" |
| 414 | 678 | ||
| 415 | [[package]] | 679 | [[package]] |
| 680 | name = "deranged" | ||
| 681 | version = "0.5.5" | ||
| 682 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 683 | checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" | ||
| 684 | dependencies = [ | ||
| 685 | "powerfmt", | ||
| 686 | ] | ||
| 687 | |||
| 688 | [[package]] | ||
| 689 | name = "derive_more" | ||
| 690 | version = "2.0.1" | ||
| 691 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 692 | checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" | ||
| 693 | dependencies = [ | ||
| 694 | "derive_more-impl", | ||
| 695 | ] | ||
| 696 | |||
| 697 | [[package]] | ||
| 698 | name = "derive_more-impl" | ||
| 699 | version = "2.0.1" | ||
| 700 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 701 | checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" | ||
| 702 | dependencies = [ | ||
| 703 | "proc-macro2", | ||
| 704 | "quote", | ||
| 705 | "syn", | ||
| 706 | "unicode-xid", | ||
| 707 | ] | ||
| 708 | |||
| 709 | [[package]] | ||
| 416 | name = "digest" | 710 | name = "digest" |
| 417 | version = "0.10.7" | 711 | version = "0.10.7" |
| 418 | source = "registry+https://github.com/rust-lang/crates.io-index" | 712 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -447,18 +741,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 447 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" | 741 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" |
| 448 | 742 | ||
| 449 | [[package]] | 743 | [[package]] |
| 744 | name = "encoding_rs" | ||
| 745 | version = "0.8.35" | ||
| 746 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 747 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" | ||
| 748 | dependencies = [ | ||
| 749 | "cfg-if", | ||
| 750 | ] | ||
| 751 | |||
| 752 | [[package]] | ||
| 753 | name = "equivalent" | ||
| 754 | version = "1.0.2" | ||
| 755 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 756 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | ||
| 757 | |||
| 758 | [[package]] | ||
| 450 | name = "find-msvc-tools" | 759 | name = "find-msvc-tools" |
| 451 | version = "0.1.4" | 760 | version = "0.1.4" |
| 452 | source = "registry+https://github.com/rust-lang/crates.io-index" | 761 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 453 | checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" | 762 | checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" |
| 454 | 763 | ||
| 455 | [[package]] | 764 | [[package]] |
| 765 | name = "flate2" | ||
| 766 | version = "1.1.5" | ||
| 767 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 768 | checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" | ||
| 769 | dependencies = [ | ||
| 770 | "crc32fast", | ||
| 771 | "miniz_oxide", | ||
| 772 | ] | ||
| 773 | |||
| 774 | [[package]] | ||
| 456 | name = "fnv" | 775 | name = "fnv" |
| 457 | version = "1.0.7" | 776 | version = "1.0.7" |
| 458 | source = "registry+https://github.com/rust-lang/crates.io-index" | 777 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 459 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" | 778 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" |
| 460 | 779 | ||
| 461 | [[package]] | 780 | [[package]] |
| 781 | name = "foldhash" | ||
| 782 | version = "0.1.5" | ||
| 783 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 784 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" | ||
| 785 | |||
| 786 | [[package]] | ||
| 462 | name = "form_urlencoded" | 787 | name = "form_urlencoded" |
| 463 | version = "1.2.2" | 788 | version = "1.2.2" |
| 464 | source = "registry+https://github.com/rust-lang/crates.io-index" | 789 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -611,7 +936,7 @@ dependencies = [ | |||
| 611 | "chrono", | 936 | "chrono", |
| 612 | "clap", | 937 | "clap", |
| 613 | "futures", | 938 | "futures", |
| 614 | "nostr-sdk", | 939 | "nostr-sdk 0.43.0", |
| 615 | "serde", | 940 | "serde", |
| 616 | "serde_json", | 941 | "serde_json", |
| 617 | "thiserror 1.0.69", | 942 | "thiserror 1.0.69", |
| @@ -623,12 +948,43 @@ dependencies = [ | |||
| 623 | ] | 948 | ] |
| 624 | 949 | ||
| 625 | [[package]] | 950 | [[package]] |
| 951 | name = "h2" | ||
| 952 | version = "0.3.27" | ||
| 953 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 954 | checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" | ||
| 955 | dependencies = [ | ||
| 956 | "bytes", | ||
| 957 | "fnv", | ||
| 958 | "futures-core", | ||
| 959 | "futures-sink", | ||
| 960 | "futures-util", | ||
| 961 | "http 0.2.12", | ||
| 962 | "indexmap", | ||
| 963 | "slab", | ||
| 964 | "tokio", | ||
| 965 | "tokio-util", | ||
| 966 | "tracing", | ||
| 967 | ] | ||
| 968 | |||
| 969 | [[package]] | ||
| 970 | name = "hashbrown" | ||
| 971 | version = "0.16.0" | ||
| 972 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 973 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" | ||
| 974 | |||
| 975 | [[package]] | ||
| 626 | name = "heck" | 976 | name = "heck" |
| 627 | version = "0.5.0" | 977 | version = "0.5.0" |
| 628 | source = "registry+https://github.com/rust-lang/crates.io-index" | 978 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 629 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | 979 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" |
| 630 | 980 | ||
| 631 | [[package]] | 981 | [[package]] |
| 982 | name = "hex" | ||
| 983 | version = "0.4.3" | ||
| 984 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 985 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" | ||
| 986 | |||
| 987 | [[package]] | ||
| 632 | name = "hex-conservative" | 988 | name = "hex-conservative" |
| 633 | version = "0.1.2" | 989 | version = "0.1.2" |
| 634 | source = "registry+https://github.com/rust-lang/crates.io-index" | 990 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -654,6 +1010,17 @@ dependencies = [ | |||
| 654 | 1010 | ||
| 655 | [[package]] | 1011 | [[package]] |
| 656 | name = "http" | 1012 | name = "http" |
| 1013 | version = "0.2.12" | ||
| 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1015 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" | ||
| 1016 | dependencies = [ | ||
| 1017 | "bytes", | ||
| 1018 | "fnv", | ||
| 1019 | "itoa", | ||
| 1020 | ] | ||
| 1021 | |||
| 1022 | [[package]] | ||
| 1023 | name = "http" | ||
| 657 | version = "1.3.1" | 1024 | version = "1.3.1" |
| 658 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 659 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" | 1026 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" |
| @@ -670,6 +1037,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 670 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" | 1037 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" |
| 671 | 1038 | ||
| 672 | [[package]] | 1039 | [[package]] |
| 1040 | name = "httpdate" | ||
| 1041 | version = "1.0.3" | ||
| 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1043 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" | ||
| 1044 | |||
| 1045 | [[package]] | ||
| 673 | name = "iana-time-zone" | 1046 | name = "iana-time-zone" |
| 674 | version = "0.1.64" | 1047 | version = "0.1.64" |
| 675 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -796,6 +1169,22 @@ dependencies = [ | |||
| 796 | ] | 1169 | ] |
| 797 | 1170 | ||
| 798 | [[package]] | 1171 | [[package]] |
| 1172 | name = "impl-more" | ||
| 1173 | version = "0.1.9" | ||
| 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1175 | checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" | ||
| 1176 | |||
| 1177 | [[package]] | ||
| 1178 | name = "indexmap" | ||
| 1179 | version = "2.12.0" | ||
| 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1181 | checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" | ||
| 1182 | dependencies = [ | ||
| 1183 | "equivalent", | ||
| 1184 | "hashbrown", | ||
| 1185 | ] | ||
| 1186 | |||
| 1187 | [[package]] | ||
| 799 | name = "inout" | 1188 | name = "inout" |
| 800 | version = "0.1.4" | 1189 | version = "0.1.4" |
| 801 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -830,6 +1219,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 830 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" | 1219 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" |
| 831 | 1220 | ||
| 832 | [[package]] | 1221 | [[package]] |
| 1222 | name = "jobserver" | ||
| 1223 | version = "0.1.34" | ||
| 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1225 | checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" | ||
| 1226 | dependencies = [ | ||
| 1227 | "getrandom 0.3.4", | ||
| 1228 | "libc", | ||
| 1229 | ] | ||
| 1230 | |||
| 1231 | [[package]] | ||
| 833 | name = "js-sys" | 1232 | name = "js-sys" |
| 834 | version = "0.3.82" | 1233 | version = "0.3.82" |
| 835 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -840,6 +1239,12 @@ dependencies = [ | |||
| 840 | ] | 1239 | ] |
| 841 | 1240 | ||
| 842 | [[package]] | 1241 | [[package]] |
| 1242 | name = "language-tags" | ||
| 1243 | version = "0.3.2" | ||
| 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1245 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" | ||
| 1246 | |||
| 1247 | [[package]] | ||
| 843 | name = "lazy_static" | 1248 | name = "lazy_static" |
| 844 | version = "1.5.0" | 1249 | version = "1.5.0" |
| 845 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -858,6 +1263,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 858 | checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" | 1263 | checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" |
| 859 | 1264 | ||
| 860 | [[package]] | 1265 | [[package]] |
| 1266 | name = "local-channel" | ||
| 1267 | version = "0.1.5" | ||
| 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1269 | checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" | ||
| 1270 | dependencies = [ | ||
| 1271 | "futures-core", | ||
| 1272 | "futures-sink", | ||
| 1273 | "local-waker", | ||
| 1274 | ] | ||
| 1275 | |||
| 1276 | [[package]] | ||
| 1277 | name = "local-waker" | ||
| 1278 | version = "0.1.4" | ||
| 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1280 | checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" | ||
| 1281 | |||
| 1282 | [[package]] | ||
| 861 | name = "lock_api" | 1283 | name = "lock_api" |
| 862 | version = "0.4.14" | 1284 | version = "0.4.14" |
| 863 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -894,12 +1316,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 894 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" | 1316 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" |
| 895 | 1317 | ||
| 896 | [[package]] | 1318 | [[package]] |
| 1319 | name = "mime" | ||
| 1320 | version = "0.3.17" | ||
| 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1322 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" | ||
| 1323 | |||
| 1324 | [[package]] | ||
| 1325 | name = "miniz_oxide" | ||
| 1326 | version = "0.8.9" | ||
| 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1328 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" | ||
| 1329 | dependencies = [ | ||
| 1330 | "adler2", | ||
| 1331 | "simd-adler32", | ||
| 1332 | ] | ||
| 1333 | |||
| 1334 | [[package]] | ||
| 897 | name = "mio" | 1335 | name = "mio" |
| 898 | version = "1.1.0" | 1336 | version = "1.1.0" |
| 899 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 900 | checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" | 1338 | checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" |
| 901 | dependencies = [ | 1339 | dependencies = [ |
| 902 | "libc", | 1340 | "libc", |
| 1341 | "log", | ||
| 903 | "wasi", | 1342 | "wasi", |
| 904 | "windows-sys 0.61.2", | 1343 | "windows-sys 0.61.2", |
| 905 | ] | 1344 | ] |
| @@ -914,20 +1353,22 @@ checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" | |||
| 914 | name = "ngit-grasp" | 1353 | name = "ngit-grasp" |
| 915 | version = "0.1.0" | 1354 | version = "0.1.0" |
| 916 | dependencies = [ | 1355 | dependencies = [ |
| 1356 | "actix-web", | ||
| 1357 | "actix-ws", | ||
| 917 | "anyhow", | 1358 | "anyhow", |
| 918 | "dotenvy", | 1359 | "dotenvy", |
| 919 | "futures-util", | 1360 | "futures-util", |
| 920 | "grasp-audit", | 1361 | "grasp-audit", |
| 921 | "nostr-sdk", | 1362 | "nostr-relay-builder", |
| 1363 | "nostr-sdk 0.44.1", | ||
| 922 | "serde", | 1364 | "serde", |
| 923 | "serde_json", | 1365 | "serde_json", |
| 924 | "thiserror 1.0.69", | 1366 | "thiserror 1.0.69", |
| 925 | "tokio", | 1367 | "tokio", |
| 926 | "tokio-test", | 1368 | "tokio-test", |
| 927 | "tokio-tungstenite 0.21.0", | 1369 | "tokio-tungstenite 0.28.0", |
| 928 | "tracing", | 1370 | "tracing", |
| 929 | "tracing-subscriber", | 1371 | "tracing-subscriber", |
| 930 | "tungstenite 0.21.0", | ||
| 931 | "url", | 1372 | "url", |
| 932 | ] | 1373 | ] |
| 933 | 1374 | ||
| @@ -955,14 +1396,75 @@ dependencies = [ | |||
| 955 | ] | 1396 | ] |
| 956 | 1397 | ||
| 957 | [[package]] | 1398 | [[package]] |
| 1399 | name = "nostr" | ||
| 1400 | version = "0.44.1" | ||
| 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1402 | checksum = "d3595fecf0e0aaacb69a0dc0101a4453f3c76eda333d6bbc49f68f64390b3d85" | ||
| 1403 | dependencies = [ | ||
| 1404 | "base64", | ||
| 1405 | "bech32", | ||
| 1406 | "bip39", | ||
| 1407 | "bitcoin_hashes 0.14.0", | ||
| 1408 | "cbc", | ||
| 1409 | "chacha20", | ||
| 1410 | "chacha20poly1305", | ||
| 1411 | "getrandom 0.2.16", | ||
| 1412 | "hex", | ||
| 1413 | "instant", | ||
| 1414 | "scrypt", | ||
| 1415 | "secp256k1", | ||
| 1416 | "serde", | ||
| 1417 | "serde_json", | ||
| 1418 | "unicode-normalization", | ||
| 1419 | "url", | ||
| 1420 | ] | ||
| 1421 | |||
| 1422 | [[package]] | ||
| 958 | name = "nostr-database" | 1423 | name = "nostr-database" |
| 959 | version = "0.43.0" | 1424 | version = "0.43.0" |
| 960 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 961 | checksum = "b1c75a8c2175d2785ba73cfddef21d1e30da5fbbdf158569b6808ba44973a15b" | 1426 | checksum = "b1c75a8c2175d2785ba73cfddef21d1e30da5fbbdf158569b6808ba44973a15b" |
| 962 | dependencies = [ | 1427 | dependencies = [ |
| 963 | "lru", | 1428 | "lru", |
| 964 | "nostr", | 1429 | "nostr 0.43.1", |
| 1430 | "tokio", | ||
| 1431 | ] | ||
| 1432 | |||
| 1433 | [[package]] | ||
| 1434 | name = "nostr-database" | ||
| 1435 | version = "0.44.0" | ||
| 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1437 | checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1" | ||
| 1438 | dependencies = [ | ||
| 1439 | "lru", | ||
| 1440 | "nostr 0.44.1", | ||
| 1441 | "tokio", | ||
| 1442 | ] | ||
| 1443 | |||
| 1444 | [[package]] | ||
| 1445 | name = "nostr-gossip" | ||
| 1446 | version = "0.44.0" | ||
| 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1448 | checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6" | ||
| 1449 | dependencies = [ | ||
| 1450 | "nostr 0.44.1", | ||
| 1451 | ] | ||
| 1452 | |||
| 1453 | [[package]] | ||
| 1454 | name = "nostr-relay-builder" | ||
| 1455 | version = "0.44.0" | ||
| 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1457 | checksum = "51ba8e48eaadd5644e7317ca2f892b89a394a8cc57e99fea9f624dc081df1a24" | ||
| 1458 | dependencies = [ | ||
| 1459 | "async-utility", | ||
| 1460 | "async-wsocket", | ||
| 1461 | "atomic-destructor", | ||
| 1462 | "hex", | ||
| 1463 | "negentropy", | ||
| 1464 | "nostr 0.44.1", | ||
| 1465 | "nostr-database 0.44.0", | ||
| 965 | "tokio", | 1466 | "tokio", |
| 1467 | "tracing", | ||
| 966 | ] | 1468 | ] |
| 967 | 1469 | ||
| 968 | [[package]] | 1470 | [[package]] |
| @@ -976,8 +1478,26 @@ dependencies = [ | |||
| 976 | "atomic-destructor", | 1478 | "atomic-destructor", |
| 977 | "lru", | 1479 | "lru", |
| 978 | "negentropy", | 1480 | "negentropy", |
| 979 | "nostr", | 1481 | "nostr 0.43.1", |
| 980 | "nostr-database", | 1482 | "nostr-database 0.43.0", |
| 1483 | "tokio", | ||
| 1484 | "tracing", | ||
| 1485 | ] | ||
| 1486 | |||
| 1487 | [[package]] | ||
| 1488 | name = "nostr-relay-pool" | ||
| 1489 | version = "0.44.0" | ||
| 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1491 | checksum = "4b1073ccfbaea5549fb914a9d52c68dab2aecda61535e5143dd73e95445a804b" | ||
| 1492 | dependencies = [ | ||
| 1493 | "async-utility", | ||
| 1494 | "async-wsocket", | ||
| 1495 | "atomic-destructor", | ||
| 1496 | "hex", | ||
| 1497 | "lru", | ||
| 1498 | "negentropy", | ||
| 1499 | "nostr 0.44.1", | ||
| 1500 | "nostr-database 0.44.0", | ||
| 981 | "tokio", | 1501 | "tokio", |
| 982 | "tracing", | 1502 | "tracing", |
| 983 | ] | 1503 | ] |
| @@ -989,10 +1509,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 989 | checksum = "599f8963d6a1522a13b1a2b0ea6e168acfc367706606f1d33fa595e91fa22db0" | 1509 | checksum = "599f8963d6a1522a13b1a2b0ea6e168acfc367706606f1d33fa595e91fa22db0" |
| 990 | dependencies = [ | 1510 | dependencies = [ |
| 991 | "async-utility", | 1511 | "async-utility", |
| 992 | "nostr", | 1512 | "nostr 0.43.1", |
| 993 | "nostr-database", | 1513 | "nostr-database 0.43.0", |
| 994 | "nostr-relay-pool", | 1514 | "nostr-relay-pool 0.43.1", |
| 1515 | "tokio", | ||
| 1516 | ] | ||
| 1517 | |||
| 1518 | [[package]] | ||
| 1519 | name = "nostr-sdk" | ||
| 1520 | version = "0.44.1" | ||
| 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1522 | checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393" | ||
| 1523 | dependencies = [ | ||
| 1524 | "async-utility", | ||
| 1525 | "nostr 0.44.1", | ||
| 1526 | "nostr-database 0.44.0", | ||
| 1527 | "nostr-gossip", | ||
| 1528 | "nostr-relay-pool 0.44.0", | ||
| 995 | "tokio", | 1529 | "tokio", |
| 1530 | "tracing", | ||
| 996 | ] | 1531 | ] |
| 997 | 1532 | ||
| 998 | [[package]] | 1533 | [[package]] |
| @@ -1005,6 +1540,12 @@ dependencies = [ | |||
| 1005 | ] | 1540 | ] |
| 1006 | 1541 | ||
| 1007 | [[package]] | 1542 | [[package]] |
| 1543 | name = "num-conv" | ||
| 1544 | version = "0.1.0" | ||
| 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1546 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" | ||
| 1547 | |||
| 1548 | [[package]] | ||
| 1008 | name = "num-traits" | 1549 | name = "num-traits" |
| 1009 | version = "0.2.19" | 1550 | version = "0.2.19" |
| 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -1094,6 +1635,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 1094 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" | 1635 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" |
| 1095 | 1636 | ||
| 1096 | [[package]] | 1637 | [[package]] |
| 1638 | name = "pkg-config" | ||
| 1639 | version = "0.3.32" | ||
| 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1641 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" | ||
| 1642 | |||
| 1643 | [[package]] | ||
| 1097 | name = "poly1305" | 1644 | name = "poly1305" |
| 1098 | version = "0.8.0" | 1645 | version = "0.8.0" |
| 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -1114,6 +1661,12 @@ dependencies = [ | |||
| 1114 | ] | 1661 | ] |
| 1115 | 1662 | ||
| 1116 | [[package]] | 1663 | [[package]] |
| 1664 | name = "powerfmt" | ||
| 1665 | version = "0.2.0" | ||
| 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1667 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" | ||
| 1668 | |||
| 1669 | [[package]] | ||
| 1117 | name = "ppv-lite86" | 1670 | name = "ppv-lite86" |
| 1118 | version = "0.2.21" | 1671 | version = "0.2.21" |
| 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -1215,6 +1768,18 @@ dependencies = [ | |||
| 1215 | ] | 1768 | ] |
| 1216 | 1769 | ||
| 1217 | [[package]] | 1770 | [[package]] |
| 1771 | name = "regex" | ||
| 1772 | version = "1.12.2" | ||
| 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1774 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" | ||
| 1775 | dependencies = [ | ||
| 1776 | "aho-corasick", | ||
| 1777 | "memchr", | ||
| 1778 | "regex-automata", | ||
| 1779 | "regex-syntax", | ||
| 1780 | ] | ||
| 1781 | |||
| 1782 | [[package]] | ||
| 1218 | name = "regex-automata" | 1783 | name = "regex-automata" |
| 1219 | version = "0.4.13" | 1784 | version = "0.4.13" |
| 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -1226,6 +1791,12 @@ dependencies = [ | |||
| 1226 | ] | 1791 | ] |
| 1227 | 1792 | ||
| 1228 | [[package]] | 1793 | [[package]] |
| 1794 | name = "regex-lite" | ||
| 1795 | version = "0.1.8" | ||
| 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1797 | checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" | ||
| 1798 | |||
| 1799 | [[package]] | ||
| 1229 | name = "regex-syntax" | 1800 | name = "regex-syntax" |
| 1230 | version = "0.8.8" | 1801 | version = "0.8.8" |
| 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -1382,6 +1953,18 @@ dependencies = [ | |||
| 1382 | ] | 1953 | ] |
| 1383 | 1954 | ||
| 1384 | [[package]] | 1955 | [[package]] |
| 1956 | name = "serde_urlencoded" | ||
| 1957 | version = "0.7.1" | ||
| 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1959 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" | ||
| 1960 | dependencies = [ | ||
| 1961 | "form_urlencoded", | ||
| 1962 | "itoa", | ||
| 1963 | "ryu", | ||
| 1964 | "serde", | ||
| 1965 | ] | ||
| 1966 | |||
| 1967 | [[package]] | ||
| 1385 | name = "sha1" | 1968 | name = "sha1" |
| 1386 | version = "0.10.6" | 1969 | version = "0.10.6" |
| 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1970 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -1428,6 +2011,12 @@ dependencies = [ | |||
| 1428 | ] | 2011 | ] |
| 1429 | 2012 | ||
| 1430 | [[package]] | 2013 | [[package]] |
| 2014 | name = "simd-adler32" | ||
| 2015 | version = "0.3.7" | ||
| 2016 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2017 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" | ||
| 2018 | |||
| 2019 | [[package]] | ||
| 1431 | name = "slab" | 2020 | name = "slab" |
| 1432 | version = "0.4.11" | 2021 | version = "0.4.11" |
| 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2022 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -1441,6 +2030,16 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" | |||
| 1441 | 2030 | ||
| 1442 | [[package]] | 2031 | [[package]] |
| 1443 | name = "socket2" | 2032 | name = "socket2" |
| 2033 | version = "0.5.10" | ||
| 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2035 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" | ||
| 2036 | dependencies = [ | ||
| 2037 | "libc", | ||
| 2038 | "windows-sys 0.52.0", | ||
| 2039 | ] | ||
| 2040 | |||
| 2041 | [[package]] | ||
| 2042 | name = "socket2" | ||
| 1444 | version = "0.6.1" | 2043 | version = "0.6.1" |
| 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 1446 | checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" | 2045 | checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" |
| @@ -1539,6 +2138,37 @@ dependencies = [ | |||
| 1539 | ] | 2138 | ] |
| 1540 | 2139 | ||
| 1541 | [[package]] | 2140 | [[package]] |
| 2141 | name = "time" | ||
| 2142 | version = "0.3.44" | ||
| 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2144 | checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" | ||
| 2145 | dependencies = [ | ||
| 2146 | "deranged", | ||
| 2147 | "itoa", | ||
| 2148 | "num-conv", | ||
| 2149 | "powerfmt", | ||
| 2150 | "serde", | ||
| 2151 | "time-core", | ||
| 2152 | "time-macros", | ||
| 2153 | ] | ||
| 2154 | |||
| 2155 | [[package]] | ||
| 2156 | name = "time-core" | ||
| 2157 | version = "0.1.6" | ||
| 2158 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2159 | checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" | ||
| 2160 | |||
| 2161 | [[package]] | ||
| 2162 | name = "time-macros" | ||
| 2163 | version = "0.2.24" | ||
| 2164 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2165 | checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" | ||
| 2166 | dependencies = [ | ||
| 2167 | "num-conv", | ||
| 2168 | "time-core", | ||
| 2169 | ] | ||
| 2170 | |||
| 2171 | [[package]] | ||
| 1542 | name = "tinystr" | 2172 | name = "tinystr" |
| 1543 | version = "0.8.2" | 2173 | version = "0.8.2" |
| 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2174 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -1575,7 +2205,7 @@ dependencies = [ | |||
| 1575 | "parking_lot", | 2205 | "parking_lot", |
| 1576 | "pin-project-lite", | 2206 | "pin-project-lite", |
| 1577 | "signal-hook-registry", | 2207 | "signal-hook-registry", |
| 1578 | "socket2", | 2208 | "socket2 0.6.1", |
| 1579 | "tokio-macros", | 2209 | "tokio-macros", |
| 1580 | "windows-sys 0.61.2", | 2210 | "windows-sys 0.61.2", |
| 1581 | ] | 2211 | ] |
| @@ -1639,30 +2269,43 @@ dependencies = [ | |||
| 1639 | 2269 | ||
| 1640 | [[package]] | 2270 | [[package]] |
| 1641 | name = "tokio-tungstenite" | 2271 | name = "tokio-tungstenite" |
| 1642 | version = "0.21.0" | 2272 | version = "0.26.2" |
| 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2273 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 1644 | checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" | 2274 | checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" |
| 1645 | dependencies = [ | 2275 | dependencies = [ |
| 1646 | "futures-util", | 2276 | "futures-util", |
| 1647 | "log", | 2277 | "log", |
| 2278 | "rustls", | ||
| 2279 | "rustls-pki-types", | ||
| 1648 | "tokio", | 2280 | "tokio", |
| 1649 | "tungstenite 0.21.0", | 2281 | "tokio-rustls", |
| 2282 | "tungstenite 0.26.2", | ||
| 2283 | "webpki-roots 0.26.11", | ||
| 1650 | ] | 2284 | ] |
| 1651 | 2285 | ||
| 1652 | [[package]] | 2286 | [[package]] |
| 1653 | name = "tokio-tungstenite" | 2287 | name = "tokio-tungstenite" |
| 1654 | version = "0.26.2" | 2288 | version = "0.28.0" |
| 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2289 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 1656 | checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" | 2290 | checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" |
| 1657 | dependencies = [ | 2291 | dependencies = [ |
| 1658 | "futures-util", | 2292 | "futures-util", |
| 1659 | "log", | 2293 | "log", |
| 1660 | "rustls", | ||
| 1661 | "rustls-pki-types", | ||
| 1662 | "tokio", | 2294 | "tokio", |
| 1663 | "tokio-rustls", | 2295 | "tungstenite 0.28.0", |
| 1664 | "tungstenite 0.26.2", | 2296 | ] |
| 1665 | "webpki-roots 0.26.11", | 2297 | |
| 2298 | [[package]] | ||
| 2299 | name = "tokio-util" | ||
| 2300 | version = "0.7.17" | ||
| 2301 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2302 | checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" | ||
| 2303 | dependencies = [ | ||
| 2304 | "bytes", | ||
| 2305 | "futures-core", | ||
| 2306 | "futures-sink", | ||
| 2307 | "pin-project-lite", | ||
| 2308 | "tokio", | ||
| 1666 | ] | 2309 | ] |
| 1667 | 2310 | ||
| 1668 | [[package]] | 2311 | [[package]] |
| @@ -1671,6 +2314,7 @@ version = "0.1.41" | |||
| 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2314 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 1672 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" | 2315 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" |
| 1673 | dependencies = [ | 2316 | dependencies = [ |
| 2317 | "log", | ||
| 1674 | "pin-project-lite", | 2318 | "pin-project-lite", |
| 1675 | "tracing-attributes", | 2319 | "tracing-attributes", |
| 1676 | "tracing-core", | 2320 | "tracing-core", |
| @@ -1728,37 +2372,35 @@ dependencies = [ | |||
| 1728 | 2372 | ||
| 1729 | [[package]] | 2373 | [[package]] |
| 1730 | name = "tungstenite" | 2374 | name = "tungstenite" |
| 1731 | version = "0.21.0" | 2375 | version = "0.26.2" |
| 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2376 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 1733 | checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" | 2377 | checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" |
| 1734 | dependencies = [ | 2378 | dependencies = [ |
| 1735 | "byteorder", | ||
| 1736 | "bytes", | 2379 | "bytes", |
| 1737 | "data-encoding", | 2380 | "data-encoding", |
| 1738 | "http", | 2381 | "http 1.3.1", |
| 1739 | "httparse", | 2382 | "httparse", |
| 1740 | "log", | 2383 | "log", |
| 1741 | "rand 0.8.5", | 2384 | "rand 0.9.2", |
| 2385 | "rustls", | ||
| 2386 | "rustls-pki-types", | ||
| 1742 | "sha1", | 2387 | "sha1", |
| 1743 | "thiserror 1.0.69", | 2388 | "thiserror 2.0.17", |
| 1744 | "url", | ||
| 1745 | "utf-8", | 2389 | "utf-8", |
| 1746 | ] | 2390 | ] |
| 1747 | 2391 | ||
| 1748 | [[package]] | 2392 | [[package]] |
| 1749 | name = "tungstenite" | 2393 | name = "tungstenite" |
| 1750 | version = "0.26.2" | 2394 | version = "0.28.0" |
| 1751 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2395 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 1752 | checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" | 2396 | checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" |
| 1753 | dependencies = [ | 2397 | dependencies = [ |
| 1754 | "bytes", | 2398 | "bytes", |
| 1755 | "data-encoding", | 2399 | "data-encoding", |
| 1756 | "http", | 2400 | "http 1.3.1", |
| 1757 | "httparse", | 2401 | "httparse", |
| 1758 | "log", | 2402 | "log", |
| 1759 | "rand 0.9.2", | 2403 | "rand 0.9.2", |
| 1760 | "rustls", | ||
| 1761 | "rustls-pki-types", | ||
| 1762 | "sha1", | 2404 | "sha1", |
| 1763 | "thiserror 2.0.17", | 2405 | "thiserror 2.0.17", |
| 1764 | "utf-8", | 2406 | "utf-8", |
| @@ -1786,6 +2428,12 @@ dependencies = [ | |||
| 1786 | ] | 2428 | ] |
| 1787 | 2429 | ||
| 1788 | [[package]] | 2430 | [[package]] |
| 2431 | name = "unicode-xid" | ||
| 2432 | version = "0.2.6" | ||
| 2433 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2434 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" | ||
| 2435 | |||
| 2436 | [[package]] | ||
| 1789 | name = "universal-hash" | 2437 | name = "universal-hash" |
| 1790 | version = "0.5.1" | 2438 | version = "0.5.1" |
| 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2439 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -2284,3 +2932,31 @@ dependencies = [ | |||
| 2284 | "quote", | 2932 | "quote", |
| 2285 | "syn", | 2933 | "syn", |
| 2286 | ] | 2934 | ] |
| 2935 | |||
| 2936 | [[package]] | ||
| 2937 | name = "zstd" | ||
| 2938 | version = "0.13.3" | ||
| 2939 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2940 | checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" | ||
| 2941 | dependencies = [ | ||
| 2942 | "zstd-safe", | ||
| 2943 | ] | ||
| 2944 | |||
| 2945 | [[package]] | ||
| 2946 | name = "zstd-safe" | ||
| 2947 | version = "7.2.4" | ||
| 2948 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2949 | checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" | ||
| 2950 | dependencies = [ | ||
| 2951 | "zstd-sys", | ||
| 2952 | ] | ||
| 2953 | |||
| 2954 | [[package]] | ||
| 2955 | name = "zstd-sys" | ||
| 2956 | version = "2.0.16+zstd.1.5.7" | ||
| 2957 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2958 | checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" | ||
| 2959 | dependencies = [ | ||
| 2960 | "cc", | ||
| 2961 | "pkg-config", | ||
| 2962 | ] | ||
| @@ -10,18 +10,19 @@ repository = "https://gitworkshop.dev/ngit-grasp" | |||
| 10 | [dependencies] | 10 | [dependencies] |
| 11 | # Async runtime | 11 | # Async runtime |
| 12 | tokio = { version = "1.35", features = ["full"] } | 12 | tokio = { version = "1.35", features = ["full"] } |
| 13 | tokio-tungstenite = "0.21" | ||
| 14 | 13 | ||
| 15 | # WebSocket | 14 | # HTTP server |
| 16 | tungstenite = "0.21" | 15 | actix-web = "4.4" |
| 17 | futures-util = "0.3" | 16 | actix-ws = "0.3" |
| 18 | 17 | ||
| 19 | # HTTP server (for future use) | 18 | # Nostr relay |
| 20 | # actix-web = "4.4" | 19 | nostr-relay-builder = "0.44" |
| 21 | # actix-cors = "0.7" | ||
| 22 | 20 | ||
| 23 | # Nostr | 21 | # Nostr |
| 24 | nostr-sdk = "0.43" | 22 | nostr-sdk = "0.44" |
| 23 | |||
| 24 | # Utilities | ||
| 25 | futures-util = "0.3" | ||
| 25 | 26 | ||
| 26 | # Serialization | 27 | # Serialization |
| 27 | serde = { version = "1.0", features = ["derive"] } | 28 | serde = { version = "1.0", features = ["derive"] } |
| @@ -37,6 +38,7 @@ dotenvy = "0.15" | |||
| 37 | # Error handling | 38 | # Error handling |
| 38 | anyhow = "1.0" | 39 | anyhow = "1.0" |
| 39 | thiserror = "1.0" | 40 | thiserror = "1.0" |
| 41 | tokio-tungstenite = "0.28.0" | ||
| 40 | 42 | ||
| 41 | # Git (for future use) | 43 | # Git (for future use) |
| 42 | # git-http-backend = "0.3" | 44 | # git-http-backend = "0.3" |
diff --git a/src/http/landing.rs b/src/http/landing.rs new file mode 100644 index 0000000..35e49e5 --- /dev/null +++ b/src/http/landing.rs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | /// Landing Page Handler | ||
| 2 | /// | ||
| 3 | /// Serves the HTML landing page or upgrades to WebSocket for Nostr relay connections. | ||
| 4 | |||
| 5 | use actix_web::{web, HttpRequest, HttpResponse, Result}; | ||
| 6 | use nostr_relay_builder::LocalRelay; | ||
| 7 | |||
| 8 | use crate::config::Config; | ||
| 9 | |||
| 10 | /// Handle landing page or WebSocket upgrade | ||
| 11 | pub async fn handle( | ||
| 12 | req: HttpRequest, | ||
| 13 | stream: web::Payload, | ||
| 14 | config: web::Data<Config>, | ||
| 15 | relay: web::Data<LocalRelay>, | ||
| 16 | ) -> Result<HttpResponse> { | ||
| 17 | // Check if this is a WebSocket upgrade request | ||
| 18 | if let Some(upgrade) = req.headers().get("upgrade") { | ||
| 19 | if upgrade.to_str().unwrap_or("").eq_ignore_ascii_case("websocket") { | ||
| 20 | // Delegate to WebSocket handler | ||
| 21 | return crate::http::websocket::handle(req, stream, relay).await; | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | // Otherwise, serve the landing page | ||
| 26 | let html = format!( | ||
| 27 | include_str!("../../templates/landing.html"), | ||
| 28 | relay_name = config.relay_name, | ||
| 29 | relay_description = config.relay_description, | ||
| 30 | domain = config.domain, | ||
| 31 | bind_address = config.bind_address, | ||
| 32 | ); | ||
| 33 | |||
| 34 | Ok(HttpResponse::Ok() | ||
| 35 | .content_type("text/html; charset=utf-8") | ||
| 36 | .body(html)) | ||
| 37 | } \ No newline at end of file | ||
diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 0000000..286e8ff --- /dev/null +++ b/src/http/mod.rs | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | /// HTTP Server Module | ||
| 2 | /// | ||
| 3 | /// Provides actix-web HTTP server with WebSocket upgrade support for the Nostr relay. | ||
| 4 | |||
| 5 | pub mod landing; | ||
| 6 | pub mod websocket; | ||
| 7 | |||
| 8 | use actix_web::{middleware, web, App, HttpServer}; | ||
| 9 | use nostr_relay_builder::LocalRelay; | ||
| 10 | |||
| 11 | use crate::config::Config; | ||
| 12 | |||
| 13 | /// Start the HTTP server with integrated Nostr relay | ||
| 14 | pub async fn run_server(config: Config, relay: LocalRelay) -> anyhow::Result<()> { | ||
| 15 | let bind_addr = config.bind_address.clone(); | ||
| 16 | |||
| 17 | tracing::info!("Starting HTTP server on {}", bind_addr); | ||
| 18 | |||
| 19 | HttpServer::new(move || { | ||
| 20 | App::new() | ||
| 21 | .app_data(web::Data::new(config.clone())) | ||
| 22 | .app_data(web::Data::new(relay.clone())) | ||
| 23 | .wrap(middleware::Logger::default()) | ||
| 24 | .route("/", web::get().to(landing::handle)) | ||
| 25 | }) | ||
| 26 | .bind(&bind_addr)? | ||
| 27 | .run() | ||
| 28 | .await?; | ||
| 29 | |||
| 30 | Ok(()) | ||
| 31 | } \ No newline at end of file | ||
diff --git a/src/http/websocket.rs b/src/http/websocket.rs new file mode 100644 index 0000000..7af847a --- /dev/null +++ b/src/http/websocket.rs | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | /// WebSocket Handler | ||
| 2 | /// | ||
| 3 | /// Handles WebSocket upgrade requests and passes connections to the Nostr relay. | ||
| 4 | |||
| 5 | use actix_web::{web, HttpRequest, HttpResponse, Result, Error}; | ||
| 6 | use actix_ws::Message; | ||
| 7 | use futures_util::StreamExt; | ||
| 8 | use nostr_relay_builder::LocalRelay; | ||
| 9 | |||
| 10 | /// Handle WebSocket upgrade and relay connection | ||
| 11 | pub async fn handle( | ||
| 12 | req: HttpRequest, | ||
| 13 | stream: web::Payload, | ||
| 14 | relay: web::Data<LocalRelay>, | ||
| 15 | ) -> Result<HttpResponse, Error> { | ||
| 16 | let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?; | ||
| 17 | |||
| 18 | let peer_addr = req.peer_addr() | ||
| 19 | .unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()); | ||
| 20 | |||
| 21 | tracing::debug!("WebSocket connection from {}", peer_addr); | ||
| 22 | |||
| 23 | // Spawn task to handle the WebSocket connection | ||
| 24 | // TODO: Will use relay.take_connection() for full Nostr relay integration | ||
| 25 | let _relay = relay.get_ref().clone(); | ||
| 26 | actix_web::rt::spawn(async move { | ||
| 27 | // Create a channel to communicate between actix-ws and relay | ||
| 28 | let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); | ||
| 29 | |||
| 30 | // Spawn task to send messages from relay to client | ||
| 31 | let mut session_clone = session.clone(); | ||
| 32 | actix_web::rt::spawn(async move { | ||
| 33 | while let Some(msg) = rx.recv().await { | ||
| 34 | if session_clone.text(msg).await.is_err() { | ||
| 35 | break; | ||
| 36 | } | ||
| 37 | } | ||
| 38 | }); | ||
| 39 | |||
| 40 | // Handle incoming messages from client | ||
| 41 | while let Some(Ok(msg)) = msg_stream.next().await { | ||
| 42 | match msg { | ||
| 43 | Message::Text(text) => { | ||
| 44 | // For now, just echo back - will integrate with relay in next phase | ||
| 45 | tracing::debug!("Received text message: {}", text); | ||
| 46 | if let Err(e) = tx.send(text.to_string()) { | ||
| 47 | tracing::error!("Failed to send message: {}", e); | ||
| 48 | break; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | Message::Binary(_) => { | ||
| 52 | tracing::warn!("Received unexpected binary message"); | ||
| 53 | } | ||
| 54 | Message::Close(_) => { | ||
| 55 | tracing::debug!("Client closed connection"); | ||
| 56 | break; | ||
| 57 | } | ||
| 58 | Message::Ping(bytes) => { | ||
| 59 | if session.pong(&bytes).await.is_err() { | ||
| 60 | break; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | Message::Pong(_) => {} | ||
| 64 | Message::Continuation(_) => {} | ||
| 65 | Message::Nop => {} | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | tracing::debug!("WebSocket connection closed for {}", peer_addr); | ||
| 70 | }); | ||
| 71 | |||
| 72 | Ok(response) | ||
| 73 | } \ No newline at end of file | ||
| @@ -1,3 +1,3 @@ | |||
| 1 | pub mod config; | 1 | pub mod config; |
| 2 | pub mod http; | ||
| 2 | pub mod nostr; | 3 | pub mod nostr; |
| 3 | pub mod storage; | ||
diff --git a/src/main.rs b/src/main.rs index 7da4c73..38a3b95 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -3,8 +3,8 @@ use tracing::{info, Level}; | |||
| 3 | use tracing_subscriber::FmtSubscriber; | 3 | use tracing_subscriber::FmtSubscriber; |
| 4 | 4 | ||
| 5 | mod config; | 5 | mod config; |
| 6 | mod http; | ||
| 6 | mod nostr; | 7 | mod nostr; |
| 7 | mod storage; | ||
| 8 | 8 | ||
| 9 | use config::Config; | 9 | use config::Config; |
| 10 | 10 | ||
| @@ -16,21 +16,23 @@ async fn main() -> Result<()> { | |||
| 16 | .finish(); | 16 | .finish(); |
| 17 | tracing::subscriber::set_global_default(subscriber)?; | 17 | tracing::subscriber::set_global_default(subscriber)?; |
| 18 | 18 | ||
| 19 | info!("Starting ngit-grasp..."); | 19 | info!("Starting ngit-grasp with nostr-relay-builder..."); |
| 20 | 20 | ||
| 21 | // Load configuration | 21 | // Load configuration |
| 22 | let config = Config::from_env()?; | 22 | let config = Config::from_env()?; |
| 23 | info!("Configuration loaded: {}", config.bind_address); | 23 | info!("Configuration loaded: {}", config.bind_address); |
| 24 | 24 | ||
| 25 | // Initialize storage | 25 | // Create Nostr relay with NIP-34 validation |
| 26 | let storage = storage::Storage::new(&config)?; | 26 | if let Ok(relay) = nostr::builder::create_relay(&config) { |
| 27 | info!("Storage initialized at: {}", config.relay_data_path); | 27 | info!( |
| 28 | 28 | "Relay created with NIP-34 validation for domain: {}", | |
| 29 | // Start Nostr relay | 29 | config.domain |
| 30 | let relay = nostr::relay::RelayServer::new(config.clone(), storage)?; | 30 | ); |
| 31 | 31 | ||
| 32 | info!("Starting Nostr relay on {}", config.bind_address); | 32 | // Start HTTP server with integrated relay |
| 33 | relay.run().await?; | 33 | info!("Starting HTTP server on {}", config.bind_address); |
| 34 | http::run_server(config, relay).await?; | ||
| 35 | } | ||
| 34 | 36 | ||
| 35 | Ok(()) | 37 | Ok(()) |
| 36 | } | 38 | } |
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs new file mode 100644 index 0000000..cd1f4d2 --- /dev/null +++ b/src/nostr/builder.rs | |||
| @@ -0,0 +1,121 @@ | |||
| 1 | /// Nostr Relay Builder Configuration | ||
| 2 | /// | ||
| 3 | /// This module integrates nostr-relay-builder with NIP-34 validation logic | ||
| 4 | /// preserved from the original implementation. | ||
| 5 | use std::net::SocketAddr; | ||
| 6 | use std::path::Path; | ||
| 7 | |||
| 8 | use nostr::nips::nip19::ToBech32; | ||
| 9 | use nostr_relay_builder::prelude::*; | ||
| 10 | |||
| 11 | use crate::config::Config; | ||
| 12 | use crate::nostr::events::{ | ||
| 13 | validate_announcement, validate_state, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE, | ||
| 14 | }; | ||
| 15 | |||
| 16 | /// NIP-34 Write Policy | ||
| 17 | /// | ||
| 18 | /// Validates repository announcement and state events according to GRASP-01 spec. | ||
| 19 | /// Preserves all original validation logic from src/nostr/events.rs. | ||
| 20 | #[derive(Debug, Clone)] | ||
| 21 | pub struct Nip34WritePolicy { | ||
| 22 | domain: String, | ||
| 23 | } | ||
| 24 | |||
| 25 | impl Nip34WritePolicy { | ||
| 26 | pub fn new(domain: impl Into<String>) -> Self { | ||
| 27 | Self { | ||
| 28 | domain: domain.into(), | ||
| 29 | } | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | impl WritePolicy for Nip34WritePolicy { | ||
| 34 | fn admit_event<'a>( | ||
| 35 | &'a self, | ||
| 36 | event: &'a nostr_relay_builder::prelude::Event, | ||
| 37 | _addr: &'a SocketAddr, | ||
| 38 | ) -> BoxedFuture<'a, PolicyResult> { | ||
| 39 | Box::pin(async move { | ||
| 40 | match event.kind.as_u16() { | ||
| 41 | KIND_REPOSITORY_ANNOUNCEMENT => match validate_announcement(event, &self.domain) { | ||
| 42 | Ok(_) => { | ||
| 43 | tracing::debug!( | ||
| 44 | "Accepted repository announcement: {}", | ||
| 45 | event | ||
| 46 | .id | ||
| 47 | .to_bech32() | ||
| 48 | .unwrap_or_else(|_| "invalid".to_string()) | ||
| 49 | ); | ||
| 50 | PolicyResult::Accept | ||
| 51 | } | ||
| 52 | Err(e) => { | ||
| 53 | tracing::warn!( | ||
| 54 | "Rejected repository announcement {}: {}", | ||
| 55 | event | ||
| 56 | .id | ||
| 57 | .to_bech32() | ||
| 58 | .unwrap_or_else(|_| "invalid".to_string()), | ||
| 59 | e | ||
| 60 | ); | ||
| 61 | PolicyResult::Reject(e.to_string()) | ||
| 62 | } | ||
| 63 | }, | ||
| 64 | KIND_REPOSITORY_STATE => match validate_state(event) { | ||
| 65 | Ok(_) => { | ||
| 66 | tracing::debug!( | ||
| 67 | "Accepted repository state: {}", | ||
| 68 | event | ||
| 69 | .id | ||
| 70 | .to_bech32() | ||
| 71 | .unwrap_or_else(|_| "invalid".to_string()) | ||
| 72 | ); | ||
| 73 | PolicyResult::Accept | ||
| 74 | } | ||
| 75 | Err(e) => { | ||
| 76 | tracing::warn!( | ||
| 77 | "Rejected repository state {}: {}", | ||
| 78 | event | ||
| 79 | .id | ||
| 80 | .to_bech32() | ||
| 81 | .unwrap_or_else(|_| "invalid".to_string()), | ||
| 82 | e | ||
| 83 | ); | ||
| 84 | PolicyResult::Reject(e.to_string()) | ||
| 85 | } | ||
| 86 | }, | ||
| 87 | // Accept all other event kinds without validation | ||
| 88 | _ => PolicyResult::Accept, | ||
| 89 | } | ||
| 90 | }) | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | /// Create a configured LocalRelay with NIP-34 validation | ||
| 95 | pub fn create_relay(config: &Config) -> Result<LocalRelay> { | ||
| 96 | tracing::info!("Configuring nostr relay..."); | ||
| 97 | |||
| 98 | // Determine database path | ||
| 99 | let db_path = Path::new(&config.relay_data_path); | ||
| 100 | |||
| 101 | // Create database - using in-memory for now, can switch to persistent later | ||
| 102 | // TODO: Add configuration for NostrDB or LMDB backends | ||
| 103 | let database = MemoryDatabase::with_opts(MemoryDatabaseOptions { | ||
| 104 | events: true, | ||
| 105 | max_events: Some(100_000), | ||
| 106 | }); | ||
| 107 | |||
| 108 | tracing::info!("Using in-memory database (path: {})", db_path.display()); | ||
| 109 | |||
| 110 | // Build relay with NIP-34 validation | ||
| 111 | let builder = RelayBuilder::default() | ||
| 112 | .database(database) | ||
| 113 | .write_policy(Nip34WritePolicy::new(&config.domain)); | ||
| 114 | |||
| 115 | tracing::info!( | ||
| 116 | "Relay configured with NIP-34 validation for domain: {}", | ||
| 117 | config.domain | ||
| 118 | ); | ||
| 119 | |||
| 120 | Ok(LocalRelay::new(builder)) | ||
| 121 | } | ||
diff --git a/src/nostr/mod.rs b/src/nostr/mod.rs index b485b91..2bf0346 100644 --- a/src/nostr/mod.rs +++ b/src/nostr/mod.rs | |||
| @@ -1,2 +1,2 @@ | |||
| 1 | pub mod builder; | ||
| 1 | pub mod events; | 2 | pub mod events; |
| 2 | pub mod relay; | ||
diff --git a/src/nostr/relay.rs b/src/nostr/relay.rs deleted file mode 100644 index 1033b5b..0000000 --- a/src/nostr/relay.rs +++ /dev/null | |||
| @@ -1,340 +0,0 @@ | |||
| 1 | use anyhow::Result; | ||
| 2 | use futures_util::{SinkExt, StreamExt}; | ||
| 3 | use nostr_sdk::{Event, EventId, Filter, Kind}; | ||
| 4 | use serde_json::{json, Value}; | ||
| 5 | use std::collections::HashMap; | ||
| 6 | use std::net::SocketAddr; | ||
| 7 | use std::sync::Arc; | ||
| 8 | use tokio::net::{TcpListener, TcpStream}; | ||
| 9 | use tokio::sync::RwLock; | ||
| 10 | use tokio_tungstenite::{accept_async, tungstenite::Message}; | ||
| 11 | use tracing::{debug, error, info, warn}; | ||
| 12 | |||
| 13 | use crate::config::Config; | ||
| 14 | use crate::nostr::events::{validate_announcement, validate_state, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE}; | ||
| 15 | use crate::storage::Storage; | ||
| 16 | |||
| 17 | type Subscriptions = Arc<RwLock<HashMap<String, Vec<Filter>>>>; | ||
| 18 | |||
| 19 | pub struct RelayServer { | ||
| 20 | config: Config, | ||
| 21 | storage: Storage, | ||
| 22 | } | ||
| 23 | |||
| 24 | impl RelayServer { | ||
| 25 | pub fn new(config: Config, storage: Storage) -> Result<Self> { | ||
| 26 | Ok(RelayServer { config, storage }) | ||
| 27 | } | ||
| 28 | |||
| 29 | pub async fn run(self) -> Result<()> { | ||
| 30 | let addr: SocketAddr = self.config.bind_address.parse()?; | ||
| 31 | let listener = TcpListener::bind(&addr).await?; | ||
| 32 | |||
| 33 | info!("✅ Nostr relay listening on ws://{}", addr); | ||
| 34 | info!("📡 Ready to accept connections..."); | ||
| 35 | |||
| 36 | loop { | ||
| 37 | match listener.accept().await { | ||
| 38 | Ok((stream, peer_addr)) => { | ||
| 39 | debug!("New connection from: {}", peer_addr); | ||
| 40 | let storage = self.storage.clone(); | ||
| 41 | tokio::spawn(async move { | ||
| 42 | if let Err(e) = handle_connection(stream, storage).await { | ||
| 43 | error!("Error handling connection from {}: {}", peer_addr, e); | ||
| 44 | } | ||
| 45 | }); | ||
| 46 | } | ||
| 47 | Err(e) => { | ||
| 48 | error!("Error accepting connection: {}", e); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | async fn handle_connection(stream: TcpStream, storage: Storage) -> Result<()> { | ||
| 56 | let ws_stream = accept_async(stream).await?; | ||
| 57 | let (mut ws_sender, mut ws_receiver) = ws_stream.split(); | ||
| 58 | |||
| 59 | let subscriptions: Subscriptions = Arc::new(RwLock::new(HashMap::new())); | ||
| 60 | |||
| 61 | while let Some(msg) = ws_receiver.next().await { | ||
| 62 | match msg { | ||
| 63 | Ok(Message::Text(text)) => { | ||
| 64 | debug!("Received message: {}", text); | ||
| 65 | |||
| 66 | match handle_message(&text, &storage, &subscriptions).await { | ||
| 67 | Ok(responses) => { | ||
| 68 | for response in responses { | ||
| 69 | let response_text = serde_json::to_string(&response)?; | ||
| 70 | debug!("Sending response: {}", response_text); | ||
| 71 | ws_sender.send(Message::Text(response_text)).await?; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | Err(e) => { | ||
| 75 | warn!("Error handling message: {}", e); | ||
| 76 | let notice = json!(["NOTICE", format!("Error: {}", e)]); | ||
| 77 | ws_sender.send(Message::Text(notice.to_string())).await?; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | Ok(Message::Close(_)) => { | ||
| 82 | debug!("Client closed connection"); | ||
| 83 | break; | ||
| 84 | } | ||
| 85 | Ok(Message::Ping(data)) => { | ||
| 86 | ws_sender.send(Message::Pong(data)).await?; | ||
| 87 | } | ||
| 88 | Ok(_) => { | ||
| 89 | // Ignore other message types | ||
| 90 | } | ||
| 91 | Err(e) => { | ||
| 92 | error!("WebSocket error: {}", e); | ||
| 93 | break; | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | Ok(()) | ||
| 99 | } | ||
| 100 | |||
| 101 | async fn handle_message( | ||
| 102 | text: &str, | ||
| 103 | storage: &Storage, | ||
| 104 | subscriptions: &Subscriptions, | ||
| 105 | ) -> Result<Vec<Value>> { | ||
| 106 | let msg: Value = serde_json::from_str(text)?; | ||
| 107 | |||
| 108 | if let Some(arr) = msg.as_array() { | ||
| 109 | if arr.is_empty() { | ||
| 110 | return Ok(vec![json!(["NOTICE", "Empty message"])]); | ||
| 111 | } | ||
| 112 | |||
| 113 | let msg_type = arr[0].as_str().unwrap_or(""); | ||
| 114 | |||
| 115 | match msg_type { | ||
| 116 | "EVENT" => handle_event(arr, storage).await, | ||
| 117 | "REQ" => handle_req(arr, storage, subscriptions).await, | ||
| 118 | "CLOSE" => handle_close(arr, subscriptions).await, | ||
| 119 | _ => Ok(vec![json!(["NOTICE", format!("Unknown message type: {}", msg_type)])]), | ||
| 120 | } | ||
| 121 | } else { | ||
| 122 | Ok(vec![json!(["NOTICE", "Invalid message format"])]) | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | async fn handle_event(arr: &[Value], storage: &Storage) -> Result<Vec<Value>> { | ||
| 127 | if arr.len() < 2 { | ||
| 128 | return Ok(vec![json!(["NOTICE", "EVENT message requires event object"])]); | ||
| 129 | } | ||
| 130 | |||
| 131 | let event: Event = serde_json::from_value(arr[1].clone())?; | ||
| 132 | let event_id = event.id; | ||
| 133 | |||
| 134 | // Verify event (signature and ID) | ||
| 135 | if event.verify().is_err() { | ||
| 136 | return Ok(vec![json!(["OK", event_id.to_hex(), false, "invalid: signature or ID verification failed"])]); | ||
| 137 | } | ||
| 138 | |||
| 139 | // Check if event already exists | ||
| 140 | if storage.get_event(&event_id.to_hex()).await.is_some() { | ||
| 141 | return Ok(vec![json!(["OK", event_id.to_hex(), true, "duplicate: event already exists"])]); | ||
| 142 | } | ||
| 143 | |||
| 144 | // Validate repository announcements (kind 30617) | ||
| 145 | if event.kind == Kind::from(KIND_REPOSITORY_ANNOUNCEMENT) { | ||
| 146 | // Get domain from storage config | ||
| 147 | let domain = storage.get_domain(); | ||
| 148 | |||
| 149 | match validate_announcement(&event, &domain) { | ||
| 150 | Ok(()) => { | ||
| 151 | info!("✅ Valid repository announcement: {} ({})", event_id, event.kind); | ||
| 152 | } | ||
| 153 | Err(e) => { | ||
| 154 | warn!("❌ Invalid repository announcement: {}", e); | ||
| 155 | return Ok(vec![json!(["OK", event_id.to_hex(), false, format!("invalid: {}", e)])]); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | // Validate repository state announcements (kind 30618) | ||
| 161 | if event.kind == Kind::from(KIND_REPOSITORY_STATE) { | ||
| 162 | match validate_state(&event) { | ||
| 163 | Ok(()) => { | ||
| 164 | info!("✅ Valid repository state: {} ({})", event_id, event.kind); | ||
| 165 | } | ||
| 166 | Err(e) => { | ||
| 167 | warn!("❌ Invalid repository state: {}", e); | ||
| 168 | return Ok(vec![json!(["OK", event_id.to_hex(), false, format!("invalid: {}", e)])]); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | // Store the event | ||
| 174 | storage.store_event(event.clone()).await?; | ||
| 175 | |||
| 176 | info!("✅ Stored event: {} (kind: {})", event_id, event.kind); | ||
| 177 | |||
| 178 | Ok(vec![json!(["OK", event_id.to_hex(), true, ""])]) | ||
| 179 | } | ||
| 180 | |||
| 181 | async fn handle_req( | ||
| 182 | arr: &[Value], | ||
| 183 | storage: &Storage, | ||
| 184 | subscriptions: &Subscriptions, | ||
| 185 | ) -> Result<Vec<Value>> { | ||
| 186 | if arr.len() < 2 { | ||
| 187 | return Ok(vec![json!(["NOTICE", "REQ message requires subscription ID"])]); | ||
| 188 | } | ||
| 189 | |||
| 190 | let sub_id = arr[1].as_str().ok_or_else(|| anyhow::anyhow!("Invalid subscription ID"))?; | ||
| 191 | |||
| 192 | // Parse filters | ||
| 193 | let mut filters = Vec::new(); | ||
| 194 | for filter_value in &arr[2..] { | ||
| 195 | let filter: Filter = serde_json::from_value(filter_value.clone())?; | ||
| 196 | filters.push(filter.clone()); | ||
| 197 | } | ||
| 198 | |||
| 199 | // Store subscription | ||
| 200 | { | ||
| 201 | let mut subs = subscriptions.write().await; | ||
| 202 | subs.insert(sub_id.to_string(), filters.clone()); | ||
| 203 | } | ||
| 204 | |||
| 205 | debug!("Created subscription: {} with {} filters", sub_id, filters.len()); | ||
| 206 | |||
| 207 | // Query and send matching events | ||
| 208 | let mut responses = Vec::new(); | ||
| 209 | |||
| 210 | for filter in filters { | ||
| 211 | let events = storage.query_events(|event| { | ||
| 212 | matches_filter(event, &filter) | ||
| 213 | }).await; | ||
| 214 | |||
| 215 | for event in events { | ||
| 216 | responses.push(json!(["EVENT", sub_id, event])); | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | // Send EOSE (End of Stored Events) | ||
| 221 | responses.push(json!(["EOSE", sub_id])); | ||
| 222 | |||
| 223 | debug!("Subscription {} returned {} events", sub_id, responses.len() - 1); | ||
| 224 | |||
| 225 | Ok(responses) | ||
| 226 | } | ||
| 227 | |||
| 228 | async fn handle_close(arr: &[Value], subscriptions: &Subscriptions) -> Result<Vec<Value>> { | ||
| 229 | if arr.len() < 2 { | ||
| 230 | return Ok(vec![json!(["NOTICE", "CLOSE message requires subscription ID"])]); | ||
| 231 | } | ||
| 232 | |||
| 233 | let sub_id = arr[1].as_str().ok_or_else(|| anyhow::anyhow!("Invalid subscription ID"))?; | ||
| 234 | |||
| 235 | { | ||
| 236 | let mut subs = subscriptions.write().await; | ||
| 237 | subs.remove(sub_id); | ||
| 238 | } | ||
| 239 | |||
| 240 | debug!("Closed subscription: {}", sub_id); | ||
| 241 | |||
| 242 | Ok(vec![]) | ||
| 243 | } | ||
| 244 | |||
| 245 | fn matches_filter(event: &Event, filter: &Filter) -> bool { | ||
| 246 | // Check IDs | ||
| 247 | if let Some(ref ids) = filter.ids { | ||
| 248 | if !ids.is_empty() && !ids.contains(&event.id) { | ||
| 249 | return false; | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | // Check authors | ||
| 254 | if let Some(ref authors) = filter.authors { | ||
| 255 | if !authors.is_empty() && !authors.contains(&event.pubkey) { | ||
| 256 | return false; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | // Check kinds | ||
| 261 | if let Some(ref kinds) = filter.kinds { | ||
| 262 | if !kinds.is_empty() && !kinds.contains(&event.kind) { | ||
| 263 | return false; | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | // Check since | ||
| 268 | if let Some(since) = filter.since { | ||
| 269 | if event.created_at < since { | ||
| 270 | return false; | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | // Check until | ||
| 275 | if let Some(until) = filter.until { | ||
| 276 | if event.created_at > until { | ||
| 277 | return false; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | // TODO: Check tags (#e, #p, etc.) | ||
| 282 | |||
| 283 | true | ||
| 284 | } | ||
| 285 | |||
| 286 | #[cfg(test)] | ||
| 287 | mod tests { | ||
| 288 | use super::*; | ||
| 289 | use nostr_sdk::{EventBuilder, Keys, Kind}; | ||
| 290 | |||
| 291 | #[test] | ||
| 292 | fn test_matches_filter_by_id() { | ||
| 293 | let keys = Keys::generate(); | ||
| 294 | let event = EventBuilder::text_note("test") | ||
| 295 | .sign_with_keys(&keys) | ||
| 296 | .unwrap(); | ||
| 297 | |||
| 298 | // Filter matching the event ID | ||
| 299 | let filter = Filter::new().id(event.id); | ||
| 300 | assert!(matches_filter(&event, &filter)); | ||
| 301 | |||
| 302 | // Filter not matching | ||
| 303 | let other_id = EventId::all_zeros(); | ||
| 304 | let filter = Filter::new().id(other_id); | ||
| 305 | assert!(!matches_filter(&event, &filter)); | ||
| 306 | } | ||
| 307 | |||
| 308 | #[test] | ||
| 309 | fn test_matches_filter_by_author() { | ||
| 310 | let keys = Keys::generate(); | ||
| 311 | let event = EventBuilder::text_note("test") | ||
| 312 | .sign_with_keys(&keys) | ||
| 313 | .unwrap(); | ||
| 314 | |||
| 315 | // Filter matching the author | ||
| 316 | let filter = Filter::new().author(keys.public_key()); | ||
| 317 | assert!(matches_filter(&event, &filter)); | ||
| 318 | |||
| 319 | // Filter not matching | ||
| 320 | let other_keys = Keys::generate(); | ||
| 321 | let filter = Filter::new().author(other_keys.public_key()); | ||
| 322 | assert!(!matches_filter(&event, &filter)); | ||
| 323 | } | ||
| 324 | |||
| 325 | #[test] | ||
| 326 | fn test_matches_filter_by_kind() { | ||
| 327 | let keys = Keys::generate(); | ||
| 328 | let event = EventBuilder::text_note("test") | ||
| 329 | .sign_with_keys(&keys) | ||
| 330 | .unwrap(); | ||
| 331 | |||
| 332 | // Filter matching the kind | ||
| 333 | let filter = Filter::new().kind(Kind::TextNote); | ||
| 334 | assert!(matches_filter(&event, &filter)); | ||
| 335 | |||
| 336 | // Filter not matching | ||
| 337 | let filter = Filter::new().kind(Kind::Metadata); | ||
| 338 | assert!(!matches_filter(&event, &filter)); | ||
| 339 | } | ||
| 340 | } | ||
diff --git a/src/storage/mod.rs b/src/storage/mod.rs deleted file mode 100644 index eab8211..0000000 --- a/src/storage/mod.rs +++ /dev/null | |||
| @@ -1,132 +0,0 @@ | |||
| 1 | use anyhow::Result; | ||
| 2 | use nostr_sdk::Event; | ||
| 3 | use std::collections::HashMap; | ||
| 4 | use std::sync::Arc; | ||
| 5 | use tokio::sync::RwLock; | ||
| 6 | |||
| 7 | use crate::config::Config; | ||
| 8 | |||
| 9 | /// Simple in-memory storage for events | ||
| 10 | /// TODO: Persist to disk for production use | ||
| 11 | #[derive(Clone)] | ||
| 12 | pub struct Storage { | ||
| 13 | events: Arc<RwLock<HashMap<String, Event>>>, | ||
| 14 | data_path: String, | ||
| 15 | domain: String, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl Storage { | ||
| 19 | pub fn new(config: &Config) -> Result<Self> { | ||
| 20 | // Create data directory if it doesn't exist | ||
| 21 | std::fs::create_dir_all(&config.relay_data_path)?; | ||
| 22 | |||
| 23 | Ok(Storage { | ||
| 24 | events: Arc::new(RwLock::new(HashMap::new())), | ||
| 25 | data_path: config.relay_data_path.clone(), | ||
| 26 | domain: config.domain.clone(), | ||
| 27 | }) | ||
| 28 | } | ||
| 29 | |||
| 30 | pub fn get_domain(&self) -> String { | ||
| 31 | self.domain.clone() | ||
| 32 | } | ||
| 33 | |||
| 34 | pub async fn store_event(&self, event: Event) -> Result<()> { | ||
| 35 | let mut events = self.events.write().await; | ||
| 36 | events.insert(event.id.to_hex(), event); | ||
| 37 | Ok(()) | ||
| 38 | } | ||
| 39 | |||
| 40 | pub async fn get_event(&self, event_id: &str) -> Option<Event> { | ||
| 41 | let events = self.events.read().await; | ||
| 42 | events.get(event_id).cloned() | ||
| 43 | } | ||
| 44 | |||
| 45 | pub async fn query_events<F>(&self, filter: F) -> Vec<Event> | ||
| 46 | where | ||
| 47 | F: Fn(&Event) -> bool, | ||
| 48 | { | ||
| 49 | let events = self.events.read().await; | ||
| 50 | events.values().filter(|e| filter(e)).cloned().collect() | ||
| 51 | } | ||
| 52 | |||
| 53 | pub async fn count_events(&self) -> usize { | ||
| 54 | let events = self.events.read().await; | ||
| 55 | events.len() | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | #[cfg(test)] | ||
| 60 | mod tests { | ||
| 61 | use super::*; | ||
| 62 | use nostr_sdk::{EventBuilder, Keys, Kind}; | ||
| 63 | |||
| 64 | #[tokio::test] | ||
| 65 | async fn test_store_and_retrieve() { | ||
| 66 | let config = Config { | ||
| 67 | domain: "test".to_string(), | ||
| 68 | owner_npub: "npub1test".to_string(), | ||
| 69 | relay_name: "test".to_string(), | ||
| 70 | relay_description: "test".to_string(), | ||
| 71 | git_data_path: "./test_data/git".to_string(), | ||
| 72 | relay_data_path: "./test_data/relay".to_string(), | ||
| 73 | bind_address: "127.0.0.1:8080".to_string(), | ||
| 74 | }; | ||
| 75 | |||
| 76 | let storage = Storage::new(&config).unwrap(); | ||
| 77 | |||
| 78 | // Create a test event | ||
| 79 | let keys = Keys::generate(); | ||
| 80 | let event = EventBuilder::text_note("test content") | ||
| 81 | .sign_with_keys(&keys) | ||
| 82 | .unwrap(); | ||
| 83 | |||
| 84 | // Store it | ||
| 85 | storage.store_event(event.clone()).await.unwrap(); | ||
| 86 | |||
| 87 | // Retrieve it | ||
| 88 | let retrieved = storage.get_event(&event.id.to_hex()).await; | ||
| 89 | assert!(retrieved.is_some()); | ||
| 90 | assert_eq!(retrieved.unwrap().id, event.id); | ||
| 91 | |||
| 92 | // Count events | ||
| 93 | assert_eq!(storage.count_events().await, 1); | ||
| 94 | } | ||
| 95 | |||
| 96 | #[tokio::test] | ||
| 97 | async fn test_query_events() { | ||
| 98 | let config = Config { | ||
| 99 | domain: "test".to_string(), | ||
| 100 | owner_npub: "npub1test".to_string(), | ||
| 101 | relay_name: "test".to_string(), | ||
| 102 | relay_description: "test".to_string(), | ||
| 103 | git_data_path: "./test_data/git".to_string(), | ||
| 104 | relay_data_path: "./test_data/relay".to_string(), | ||
| 105 | bind_address: "127.0.0.1:8080".to_string(), | ||
| 106 | }; | ||
| 107 | |||
| 108 | let storage = Storage::new(&config).unwrap(); | ||
| 109 | |||
| 110 | // Create multiple events | ||
| 111 | let keys = Keys::generate(); | ||
| 112 | let event1 = EventBuilder::text_note("message 1") | ||
| 113 | .sign_with_keys(&keys) | ||
| 114 | .unwrap(); | ||
| 115 | let event2 = EventBuilder::text_note("message 2") | ||
| 116 | .sign_with_keys(&keys) | ||
| 117 | .unwrap(); | ||
| 118 | |||
| 119 | storage.store_event(event1.clone()).await.unwrap(); | ||
| 120 | storage.store_event(event2.clone()).await.unwrap(); | ||
| 121 | |||
| 122 | // Query all events | ||
| 123 | let all_events = storage.query_events(|_| true).await; | ||
| 124 | assert_eq!(all_events.len(), 2); | ||
| 125 | |||
| 126 | // Query by kind | ||
| 127 | let text_notes = storage | ||
| 128 | .query_events(|e| e.kind == Kind::TextNote) | ||
| 129 | .await; | ||
| 130 | assert_eq!(text_notes.len(), 2); | ||
| 131 | } | ||
| 132 | } | ||
diff --git a/templates/landing.html b/templates/landing.html new file mode 100644 index 0000000..84be8ad --- /dev/null +++ b/templates/landing.html | |||
| @@ -0,0 +1,151 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html lang="en"> | ||
| 3 | <head> | ||
| 4 | <meta charset="UTF-8"> | ||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| 6 | <title>{relay_name}</title> | ||
| 7 | <style> | ||
| 8 | * {{ | ||
| 9 | margin: 0; | ||
| 10 | padding: 0; | ||
| 11 | box-sizing: border-box; | ||
| 12 | }} | ||
| 13 | body {{ | ||
| 14 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; | ||
| 15 | line-height: 1.6; | ||
| 16 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | ||
| 17 | color: #333; | ||
| 18 | min-height: 100vh; | ||
| 19 | display: flex; | ||
| 20 | align-items: center; | ||
| 21 | justify-content: center; | ||
| 22 | padding: 20px; | ||
| 23 | }} | ||
| 24 | .container {{ | ||
| 25 | max-width: 800px; | ||
| 26 | background: white; | ||
| 27 | padding: 40px; | ||
| 28 | border-radius: 12px; | ||
| 29 | box-shadow: 0 20px 60px rgba(0,0,0,0.3); | ||
| 30 | }} | ||
| 31 | h1 {{ | ||
| 32 | color: #667eea; | ||
| 33 | margin-bottom: 10px; | ||
| 34 | font-size: 2.5em; | ||
| 35 | }} | ||
| 36 | h2 {{ | ||
| 37 | color: #764ba2; | ||
| 38 | margin-top: 30px; | ||
| 39 | margin-bottom: 15px; | ||
| 40 | font-size: 1.5em; | ||
| 41 | border-bottom: 2px solid #667eea; | ||
| 42 | padding-bottom: 10px; | ||
| 43 | }} | ||
| 44 | .subtitle {{ | ||
| 45 | color: #666; | ||
| 46 | margin-bottom: 30px; | ||
| 47 | font-size: 1.1em; | ||
| 48 | }} | ||
| 49 | .info-grid {{ | ||
| 50 | display: grid; | ||
| 51 | grid-template-columns: 140px 1fr; | ||
| 52 | gap: 15px; | ||
| 53 | margin: 20px 0; | ||
| 54 | }} | ||
| 55 | .info-label {{ | ||
| 56 | font-weight: 600; | ||
| 57 | color: #555; | ||
| 58 | }} | ||
| 59 | .info-value {{ | ||
| 60 | color: #333; | ||
| 61 | }} | ||
| 62 | code {{ | ||
| 63 | background: #f4f4f4; | ||
| 64 | padding: 3px 8px; | ||
| 65 | border-radius: 4px; | ||
| 66 | font-family: 'Courier New', monospace; | ||
| 67 | color: #667eea; | ||
| 68 | font-size: 0.9em; | ||
| 69 | }} | ||
| 70 | ul {{ | ||
| 71 | margin: 15px 0; | ||
| 72 | padding-left: 30px; | ||
| 73 | }} | ||
| 74 | li {{ | ||
| 75 | margin: 8px 0; | ||
| 76 | }} | ||
| 77 | .feature-box {{ | ||
| 78 | background: #f9f9f9; | ||
| 79 | padding: 20px; | ||
| 80 | border-radius: 8px; | ||
| 81 | margin: 15px 0; | ||
| 82 | border-left: 4px solid #667eea; | ||
| 83 | }} | ||
| 84 | .footer {{ | ||
| 85 | margin-top: 40px; | ||
| 86 | padding-top: 20px; | ||
| 87 | border-top: 1px solid #eee; | ||
| 88 | text-align: center; | ||
| 89 | color: #999; | ||
| 90 | font-size: 0.9em; | ||
| 91 | }} | ||
| 92 | .badge {{ | ||
| 93 | display: inline-block; | ||
| 94 | background: #667eea; | ||
| 95 | color: white; | ||
| 96 | padding: 4px 12px; | ||
| 97 | border-radius: 12px; | ||
| 98 | font-size: 0.85em; | ||
| 99 | margin-right: 8px; | ||
| 100 | }} | ||
| 101 | </style> | ||
| 102 | </head> | ||
| 103 | <body> | ||
| 104 | <div class="container"> | ||
| 105 | <h1>{relay_name}</h1> | ||
| 106 | <p class="subtitle">{relay_description}</p> | ||
| 107 | |||
| 108 | <h2>📡 Connection Information</h2> | ||
| 109 | <div class="info-grid"> | ||
| 110 | <div class="info-label">Domain:</div> | ||
| 111 | <div class="info-value"><code>{domain}</code></div> | ||
| 112 | |||
| 113 | <div class="info-label">WebSocket:</div> | ||
| 114 | <div class="info-value"><code>ws://{bind_address}/</code></div> | ||
| 115 | |||
| 116 | <div class="info-label">HTTP:</div> | ||
| 117 | <div class="info-value"><code>http://{bind_address}/</code></div> | ||
| 118 | </div> | ||
| 119 | |||
| 120 | <h2>🔌 Supported NIPs</h2> | ||
| 121 | <div class="feature-box"> | ||
| 122 | <ul> | ||
| 123 | <li><span class="badge">NIP-01</span> Basic protocol flow and event structure</li> | ||
| 124 | <li><span class="badge">NIP-11</span> Relay information document</li> | ||
| 125 | <li><span class="badge">NIP-34</span> Git repository events and announcements</li> | ||
| 126 | </ul> | ||
| 127 | </div> | ||
| 128 | |||
| 129 | <h2>🎯 GRASP Features</h2> | ||
| 130 | <div class="feature-box"> | ||
| 131 | <ul> | ||
| 132 | <li><strong>Repository Announcements</strong> (kind 30617) - Declare Git repositories with validation</li> | ||
| 133 | <li><strong>Repository State</strong> (kind 30618) - Track repository state changes</li> | ||
| 134 | <li><strong>NIP-34 Validation</strong> - Automatic validation of repository events</li> | ||
| 135 | <li><strong>Git HTTP Backend</strong> - Coming soon in Phase 2</li> | ||
| 136 | </ul> | ||
| 137 | </div> | ||
| 138 | |||
| 139 | <h2>📚 Documentation</h2> | ||
| 140 | <p>For more information about GRASP (Git Relays Authorized via Signed-Nostr Proofs):</p> | ||
| 141 | <ul> | ||
| 142 | <li><a href="https://gitworkshop.dev/danconwaydev.com/grasp/01.md" target="_blank">GRASP-01 Specification</a></li> | ||
| 143 | <li><a href="https://github.com/nostr-protocol/nips/blob/master/34.md" target="_blank">NIP-34: Git Stuff</a></li> | ||
| 144 | </ul> | ||
| 145 | |||
| 146 | <div class="footer"> | ||
| 147 | <p>Powered by <strong>ngit-grasp</strong> with <strong>nostr-relay-builder</strong></p> | ||
| 148 | </div> | ||
| 149 | </div> | ||
| 150 | </body> | ||
| 151 | </html> \ No newline at end of file | ||
diff --git a/tests/nip01_compliance.rs b/tests/nip01_compliance.rs index 3e2fdd3..05957fd 100644 --- a/tests/nip01_compliance.rs +++ b/tests/nip01_compliance.rs | |||
| @@ -39,22 +39,22 @@ use grasp_audit::*; | |||
| 39 | async fn test_nip01_smoke() { | 39 | async fn test_nip01_smoke() { |
| 40 | // Start test relay | 40 | // Start test relay |
| 41 | let relay = TestRelay::start().await; | 41 | let relay = TestRelay::start().await; |
| 42 | 42 | ||
| 43 | // Create audit client in CI mode (isolated, no cleanup needed) | 43 | // Create audit client in CI mode (isolated, no cleanup needed) |
| 44 | let config = AuditConfig::ci(); | 44 | let config = AuditConfig::ci(); |
| 45 | let client = AuditClient::new(relay.url(), config) | 45 | let client = AuditClient::new(relay.url(), config) |
| 46 | .await | 46 | .await |
| 47 | .expect("Failed to create audit client"); | 47 | .expect("Failed to create audit client"); |
| 48 | 48 | ||
| 49 | // Run all NIP-01 smoke tests | 49 | // Run all NIP-01 smoke tests |
| 50 | let results = specs::Nip01SmokeTests::run_all(&client).await; | 50 | let results = specs::Nip01SmokeTests::run_all(&client).await; |
| 51 | 51 | ||
| 52 | // Print detailed report | 52 | // Print detailed report |
| 53 | results.print_report(); | 53 | results.print_report(); |
| 54 | 54 | ||
| 55 | // Stop relay | 55 | // Stop relay |
| 56 | relay.stop().await; | 56 | relay.stop().await; |
| 57 | 57 | ||
| 58 | // Assert all tests passed | 58 | // Assert all tests passed |
| 59 | assert!( | 59 | assert!( |
| 60 | results.all_passed(), | 60 | results.all_passed(), |
| @@ -70,20 +70,20 @@ async fn test_nip01_smoke() { | |||
| 70 | /// for more granular testing or debugging. | 70 | /// for more granular testing or debugging. |
| 71 | #[tokio::test] | 71 | #[tokio::test] |
| 72 | async fn test_nip01_individual_tests() { | 72 | async fn test_nip01_individual_tests() { |
| 73 | use grasp_audit::specs::nip01_smoke::Nip01SmokeTests; | 73 | use grasp_audit::specs::grasp01::Nip01SmokeTests; |
| 74 | 74 | ||
| 75 | let relay = TestRelay::start().await; | 75 | let relay = TestRelay::start().await; |
| 76 | let config = AuditConfig::ci(); | 76 | let config = AuditConfig::ci(); |
| 77 | let client = AuditClient::new(relay.url(), config) | 77 | let client = AuditClient::new(relay.url(), config) |
| 78 | .await | 78 | .await |
| 79 | .expect("Failed to create audit client"); | 79 | .expect("Failed to create audit client"); |
| 80 | 80 | ||
| 81 | // We can't call private methods, so we'll run the full suite | 81 | // We can't call private methods, so we'll run the full suite |
| 82 | // This test is mainly to show the pattern | 82 | // This test is mainly to show the pattern |
| 83 | let all_results = Nip01SmokeTests::run_all(&client).await; | 83 | let all_results = Nip01SmokeTests::run_all(&client).await; |
| 84 | 84 | ||
| 85 | relay.stop().await; | 85 | relay.stop().await; |
| 86 | 86 | ||
| 87 | // Verify | 87 | // Verify |
| 88 | assert!(all_results.all_passed()); | 88 | assert!(all_results.all_passed()); |
| 89 | } | 89 | } |
| @@ -99,25 +99,25 @@ async fn test_relay_validates_events() { | |||
| 99 | let client = AuditClient::new(relay.url(), config) | 99 | let client = AuditClient::new(relay.url(), config) |
| 100 | .await | 100 | .await |
| 101 | .expect("Failed to create audit client"); | 101 | .expect("Failed to create audit client"); |
| 102 | 102 | ||
| 103 | // The validation tests are part of the smoke tests | 103 | // The validation tests are part of the smoke tests |
| 104 | let results = specs::Nip01SmokeTests::run_all(&client).await; | 104 | let results = specs::Nip01SmokeTests::run_all(&client).await; |
| 105 | 105 | ||
| 106 | // Check that validation tests exist and pass | 106 | // Check that validation tests exist and pass |
| 107 | let validation_tests: Vec<_> = results | 107 | let validation_tests: Vec<_> = results |
| 108 | .results | 108 | .results |
| 109 | .iter() | 109 | .iter() |
| 110 | .filter(|t| t.spec_ref.contains("validation")) | 110 | .filter(|t| t.spec_ref.contains("validation")) |
| 111 | .collect(); | 111 | .collect(); |
| 112 | 112 | ||
| 113 | relay.stop().await; | 113 | relay.stop().await; |
| 114 | 114 | ||
| 115 | // Should have validation tests | 115 | // Should have validation tests |
| 116 | assert!( | 116 | assert!( |
| 117 | !validation_tests.is_empty(), | 117 | !validation_tests.is_empty(), |
| 118 | "No validation tests found in NIP-01 smoke tests" | 118 | "No validation tests found in NIP-01 smoke tests" |
| 119 | ); | 119 | ); |
| 120 | 120 | ||
| 121 | // All validation tests should pass | 121 | // All validation tests should pass |
| 122 | for test in validation_tests { | 122 | for test in validation_tests { |
| 123 | assert!( | 123 | assert!( |
| @@ -137,18 +137,18 @@ async fn test_relay_lifecycle() { | |||
| 137 | // Start relay | 137 | // Start relay |
| 138 | let relay = TestRelay::start().await; | 138 | let relay = TestRelay::start().await; |
| 139 | let url = relay.url().to_string(); | 139 | let url = relay.url().to_string(); |
| 140 | 140 | ||
| 141 | // Verify we can connect | 141 | // Verify we can connect |
| 142 | let config = AuditConfig::ci(); | 142 | let config = AuditConfig::ci(); |
| 143 | let client = AuditClient::new(&url, config) | 143 | let client = AuditClient::new(&url, config) |
| 144 | .await | 144 | .await |
| 145 | .expect("Failed to connect to relay"); | 145 | .expect("Failed to connect to relay"); |
| 146 | 146 | ||
| 147 | assert!(client.is_connected().await, "Client should be connected"); | 147 | assert!(client.is_connected().await, "Client should be connected"); |
| 148 | 148 | ||
| 149 | // Stop relay | 149 | // Stop relay |
| 150 | relay.stop().await; | 150 | relay.stop().await; |
| 151 | 151 | ||
| 152 | // Note: We can't easily verify disconnection without modifying grasp-audit | 152 | // Note: We can't easily verify disconnection without modifying grasp-audit |
| 153 | // to expose connection state after relay shutdown. That's okay - the | 153 | // to expose connection state after relay shutdown. That's okay - the |
| 154 | // important part is that the relay starts and stops cleanly. | 154 | // important part is that the relay starts and stops cleanly. |
| @@ -162,28 +162,28 @@ async fn test_parallel_relays() { | |||
| 162 | // Start two relays simultaneously | 162 | // Start two relays simultaneously |
| 163 | let relay1 = TestRelay::start().await; | 163 | let relay1 = TestRelay::start().await; |
| 164 | let relay2 = TestRelay::start().await; | 164 | let relay2 = TestRelay::start().await; |
| 165 | 165 | ||
| 166 | // Should have different URLs (different ports) | 166 | // Should have different URLs (different ports) |
| 167 | assert_ne!( | 167 | assert_ne!( |
| 168 | relay1.url(), | 168 | relay1.url(), |
| 169 | relay2.url(), | 169 | relay2.url(), |
| 170 | "Relays should use different ports" | 170 | "Relays should use different ports" |
| 171 | ); | 171 | ); |
| 172 | 172 | ||
| 173 | // Both should be connectable | 173 | // Both should be connectable |
| 174 | let config = AuditConfig::ci(); | 174 | let config = AuditConfig::ci(); |
| 175 | 175 | ||
| 176 | let client1 = AuditClient::new(relay1.url(), config.clone()) | 176 | let client1 = AuditClient::new(relay1.url(), config.clone()) |
| 177 | .await | 177 | .await |
| 178 | .expect("Failed to connect to relay 1"); | 178 | .expect("Failed to connect to relay 1"); |
| 179 | 179 | ||
| 180 | let client2 = AuditClient::new(relay2.url(), config) | 180 | let client2 = AuditClient::new(relay2.url(), config) |
| 181 | .await | 181 | .await |
| 182 | .expect("Failed to connect to relay 2"); | 182 | .expect("Failed to connect to relay 2"); |
| 183 | 183 | ||
| 184 | assert!(client1.is_connected().await); | 184 | assert!(client1.is_connected().await); |
| 185 | assert!(client2.is_connected().await); | 185 | assert!(client2.is_connected().await); |
| 186 | 186 | ||
| 187 | // Clean up | 187 | // Clean up |
| 188 | relay1.stop().await; | 188 | relay1.stop().await; |
| 189 | relay2.stop().await; | 189 | relay2.stop().await; |
diff --git a/tests/nip34_announcements.rs b/tests/nip34_announcements.rs index 6e83bb6..535425d 100644 --- a/tests/nip34_announcements.rs +++ b/tests/nip34_announcements.rs | |||
| @@ -36,7 +36,9 @@ const KIND_REPOSITORY_ANNOUNCEMENT: u16 = 30617; | |||
| 36 | const KIND_REPOSITORY_STATE: u16 = 30618; | 36 | const KIND_REPOSITORY_STATE: u16 = 30618; |
| 37 | 37 | ||
| 38 | /// Helper to connect to a test relay | 38 | /// Helper to connect to a test relay |
| 39 | async fn connect_to_relay(url: &str) -> tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>> { | 39 | async fn connect_to_relay( |
| 40 | url: &str, | ||
| 41 | ) -> tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>> { | ||
| 40 | let (ws, _) = connect_async(url) | 42 | let (ws, _) = connect_async(url) |
| 41 | .await | 43 | .await |
| 42 | .expect("Failed to connect to relay"); | 44 | .expect("Failed to connect to relay"); |
| @@ -54,10 +56,7 @@ fn create_announcement( | |||
| 54 | let mut tags = vec![Tag::custom(TagKind::d(), vec![identifier.to_string()])]; | 56 | let mut tags = vec![Tag::custom(TagKind::d(), vec![identifier.to_string()])]; |
| 55 | 57 | ||
| 56 | for url in clone_urls { | 58 | for url in clone_urls { |
| 57 | tags.push(Tag::custom( | 59 | tags.push(Tag::custom(TagKind::Clone, vec![url])); |
| 58 | TagKind::Clone, | ||
| 59 | vec![url], | ||
| 60 | )); | ||
| 61 | } | 60 | } |
| 62 | 61 | ||
| 63 | for relay in relays { | 62 | for relay in relays { |
| @@ -94,10 +93,10 @@ fn create_state(keys: &Keys, identifier: &str, branches: Vec<(&str, &str)>) -> n | |||
| 94 | #[tokio::test] | 93 | #[tokio::test] |
| 95 | async fn test_relay_accepts_connection() { | 94 | async fn test_relay_accepts_connection() { |
| 96 | let relay = TestRelay::start().await; | 95 | let relay = TestRelay::start().await; |
| 97 | 96 | ||
| 98 | // Try to connect | 97 | // Try to connect |
| 99 | let ws = connect_to_relay(relay.url()).await; | 98 | let ws = connect_to_relay(relay.url()).await; |
| 100 | 99 | ||
| 101 | drop(ws); // Clean disconnect | 100 | drop(ws); // Clean disconnect |
| 102 | } | 101 | } |
| 103 | 102 | ||
| @@ -119,14 +118,14 @@ async fn test_accepts_valid_announcement() { | |||
| 119 | 118 | ||
| 120 | // Send event | 119 | // Send event |
| 121 | let event_msg = json!(["EVENT", event]); | 120 | let event_msg = json!(["EVENT", event]); |
| 122 | ws.send(Message::Text(event_msg.to_string())) | 121 | ws.send(Message::Text(event_msg.to_string().into())) |
| 123 | .await | 122 | .await |
| 124 | .expect("Failed to send event"); | 123 | .expect("Failed to send event"); |
| 125 | 124 | ||
| 126 | // Read response | 125 | // Read response |
| 127 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 126 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 128 | let response: Value = serde_json::from_str(&text).expect("Failed to parse response"); | 127 | let response: Value = serde_json::from_str(&text).expect("Failed to parse response"); |
| 129 | 128 | ||
| 130 | // Should be ["OK", event_id, true, ""] | 129 | // Should be ["OK", event_id, true, ""] |
| 131 | assert_eq!(response[0], "OK"); | 130 | assert_eq!(response[0], "OK"); |
| 132 | assert_eq!(response[1], event.id.to_hex()); | 131 | assert_eq!(response[1], event.id.to_hex()); |
| @@ -146,9 +145,7 @@ async fn test_rejects_announcement_without_clone() { | |||
| 146 | let relay = TestRelay::start().await; | 145 | let relay = TestRelay::start().await; |
| 147 | let keys = Keys::generate(); | 146 | let keys = Keys::generate(); |
| 148 | 147 | ||
| 149 | let (mut ws, _) = connect_async(relay.url()) | 148 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 150 | .await | ||
| 151 | .expect("Failed to connect"); | ||
| 152 | 149 | ||
| 153 | // Missing clone tag | 150 | // Missing clone tag |
| 154 | let event = create_announcement( | 151 | let event = create_announcement( |
| @@ -160,18 +157,18 @@ async fn test_rejects_announcement_without_clone() { | |||
| 160 | ); | 157 | ); |
| 161 | 158 | ||
| 162 | let event_msg = json!(["EVENT", event]); | 159 | let event_msg = json!(["EVENT", event]); |
| 163 | ws.send(Message::Text(event_msg.to_string())) | 160 | ws.send(Message::Text(event_msg.to_string().into())) |
| 164 | .await | 161 | .await |
| 165 | .expect("Failed to send event"); | 162 | .expect("Failed to send event"); |
| 166 | 163 | ||
| 167 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 164 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 168 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); | 165 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 169 | 166 | ||
| 170 | // Should be rejected | 167 | // Should be rejected |
| 171 | assert_eq!(response[0], "OK"); | 168 | assert_eq!(response[0], "OK"); |
| 172 | assert_eq!(response[1], event.id.to_hex()); | 169 | assert_eq!(response[1], event.id.to_hex()); |
| 173 | assert_eq!(response[2], false, "Event should be rejected"); | 170 | assert_eq!(response[2], false, "Event should be rejected"); |
| 174 | 171 | ||
| 175 | let message = response[3].as_str().unwrap(); | 172 | let message = response[3].as_str().unwrap(); |
| 176 | assert!( | 173 | assert!( |
| 177 | message.contains("clone") || message.contains("invalid"), | 174 | message.contains("clone") || message.contains("invalid"), |
| @@ -190,9 +187,7 @@ async fn test_rejects_announcement_without_relay() { | |||
| 190 | let relay = TestRelay::start().await; | 187 | let relay = TestRelay::start().await; |
| 191 | let keys = Keys::generate(); | 188 | let keys = Keys::generate(); |
| 192 | 189 | ||
| 193 | let (mut ws, _) = connect_async(relay.url()) | 190 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 194 | .await | ||
| 195 | .expect("Failed to connect"); | ||
| 196 | 191 | ||
| 197 | // Missing relay tag | 192 | // Missing relay tag |
| 198 | let event = create_announcement( | 193 | let event = create_announcement( |
| @@ -204,18 +199,18 @@ async fn test_rejects_announcement_without_relay() { | |||
| 204 | ); | 199 | ); |
| 205 | 200 | ||
| 206 | let event_msg = json!(["EVENT", event]); | 201 | let event_msg = json!(["EVENT", event]); |
| 207 | ws.send(Message::Text(event_msg.to_string())) | 202 | ws.send(Message::Text(event_msg.to_string().into())) |
| 208 | .await | 203 | .await |
| 209 | .expect("Failed to send event"); | 204 | .expect("Failed to send event"); |
| 210 | 205 | ||
| 211 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 206 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 212 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); | 207 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 213 | 208 | ||
| 214 | // Should be rejected | 209 | // Should be rejected |
| 215 | assert_eq!(response[0], "OK"); | 210 | assert_eq!(response[0], "OK"); |
| 216 | assert_eq!(response[1], event.id.to_hex()); | 211 | assert_eq!(response[1], event.id.to_hex()); |
| 217 | assert_eq!(response[2], false, "Event should be rejected"); | 212 | assert_eq!(response[2], false, "Event should be rejected"); |
| 218 | 213 | ||
| 219 | let message = response[3].as_str().unwrap(); | 214 | let message = response[3].as_str().unwrap(); |
| 220 | assert!( | 215 | assert!( |
| 221 | message.contains("relays") || message.contains("invalid"), | 216 | message.contains("relays") || message.contains("invalid"), |
| @@ -233,9 +228,7 @@ async fn test_rejects_announcement_for_other_service() { | |||
| 233 | let relay = TestRelay::start().await; | 228 | let relay = TestRelay::start().await; |
| 234 | let keys = Keys::generate(); | 229 | let keys = Keys::generate(); |
| 235 | 230 | ||
| 236 | let (mut ws, _) = connect_async(relay.url()) | 231 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 237 | .await | ||
| 238 | .expect("Failed to connect"); | ||
| 239 | 232 | ||
| 240 | // Lists different service | 233 | // Lists different service |
| 241 | let event = create_announcement( | 234 | let event = create_announcement( |
| @@ -247,13 +240,13 @@ async fn test_rejects_announcement_for_other_service() { | |||
| 247 | ); | 240 | ); |
| 248 | 241 | ||
| 249 | let event_msg = json!(["EVENT", event]); | 242 | let event_msg = json!(["EVENT", event]); |
| 250 | ws.send(Message::Text(event_msg.to_string())) | 243 | ws.send(Message::Text(event_msg.to_string().into())) |
| 251 | .await | 244 | .await |
| 252 | .expect("Failed to send event"); | 245 | .expect("Failed to send event"); |
| 253 | 246 | ||
| 254 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 247 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 255 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); | 248 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 256 | 249 | ||
| 257 | // Should be rejected | 250 | // Should be rejected |
| 258 | assert_eq!(response[0], "OK"); | 251 | assert_eq!(response[0], "OK"); |
| 259 | assert_eq!(response[1], event.id.to_hex()); | 252 | assert_eq!(response[1], event.id.to_hex()); |
| @@ -269,9 +262,7 @@ async fn test_accepts_valid_state() { | |||
| 269 | let relay = TestRelay::start().await; | 262 | let relay = TestRelay::start().await; |
| 270 | let keys = Keys::generate(); | 263 | let keys = Keys::generate(); |
| 271 | 264 | ||
| 272 | let (mut ws, _) = connect_async(relay.url()) | 265 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 273 | .await | ||
| 274 | .expect("Failed to connect"); | ||
| 275 | 266 | ||
| 276 | let event = create_state( | 267 | let event = create_state( |
| 277 | &keys, | 268 | &keys, |
| @@ -280,13 +271,13 @@ async fn test_accepts_valid_state() { | |||
| 280 | ); | 271 | ); |
| 281 | 272 | ||
| 282 | let event_msg = json!(["EVENT", event]); | 273 | let event_msg = json!(["EVENT", event]); |
| 283 | ws.send(Message::Text(event_msg.to_string())) | 274 | ws.send(Message::Text(event_msg.to_string().into())) |
| 284 | .await | 275 | .await |
| 285 | .expect("Failed to send event"); | 276 | .expect("Failed to send event"); |
| 286 | 277 | ||
| 287 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 278 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 288 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); | 279 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 289 | 280 | ||
| 290 | // Should be accepted | 281 | // Should be accepted |
| 291 | assert_eq!(response[0], "OK"); | 282 | assert_eq!(response[0], "OK"); |
| 292 | assert_eq!(response[1], event.id.to_hex()); | 283 | assert_eq!(response[1], event.id.to_hex()); |
| @@ -302,9 +293,7 @@ async fn test_accepts_state_with_multiple_branches() { | |||
| 302 | let relay = TestRelay::start().await; | 293 | let relay = TestRelay::start().await; |
| 303 | let keys = Keys::generate(); | 294 | let keys = Keys::generate(); |
| 304 | 295 | ||
| 305 | let (mut ws, _) = connect_async(relay.url()) | 296 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 306 | .await | ||
| 307 | .expect("Failed to connect"); | ||
| 308 | 297 | ||
| 309 | let event = create_state( | 298 | let event = create_state( |
| 310 | &keys, | 299 | &keys, |
| @@ -317,13 +306,13 @@ async fn test_accepts_state_with_multiple_branches() { | |||
| 317 | ); | 306 | ); |
| 318 | 307 | ||
| 319 | let event_msg = json!(["EVENT", event]); | 308 | let event_msg = json!(["EVENT", event]); |
| 320 | ws.send(Message::Text(event_msg.to_string())) | 309 | ws.send(Message::Text(event_msg.to_string().into())) |
| 321 | .await | 310 | .await |
| 322 | .expect("Failed to send event"); | 311 | .expect("Failed to send event"); |
| 323 | 312 | ||
| 324 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 313 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 325 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); | 314 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 326 | 315 | ||
| 327 | assert_eq!(response[0], "OK"); | 316 | assert_eq!(response[0], "OK"); |
| 328 | assert_eq!(response[2], true, "State event should be accepted"); | 317 | assert_eq!(response[2], true, "State event should be accepted"); |
| 329 | } else { | 318 | } else { |
| @@ -337,9 +326,7 @@ async fn test_rejects_state_without_identifier() { | |||
| 337 | let relay = TestRelay::start().await; | 326 | let relay = TestRelay::start().await; |
| 338 | let keys = Keys::generate(); | 327 | let keys = Keys::generate(); |
| 339 | 328 | ||
| 340 | let (mut ws, _) = connect_async(relay.url()) | 329 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 341 | .await | ||
| 342 | .expect("Failed to connect"); | ||
| 343 | 330 | ||
| 344 | // Create state without identifier | 331 | // Create state without identifier |
| 345 | let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_STATE), "") | 332 | let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_STATE), "") |
| @@ -347,18 +334,18 @@ async fn test_rejects_state_without_identifier() { | |||
| 347 | .expect("Failed to sign event"); | 334 | .expect("Failed to sign event"); |
| 348 | 335 | ||
| 349 | let event_msg = json!(["EVENT", event]); | 336 | let event_msg = json!(["EVENT", event]); |
| 350 | ws.send(Message::Text(event_msg.to_string())) | 337 | ws.send(Message::Text(event_msg.to_string().into())) |
| 351 | .await | 338 | .await |
| 352 | .expect("Failed to send event"); | 339 | .expect("Failed to send event"); |
| 353 | 340 | ||
| 354 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 341 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 355 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); | 342 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 356 | 343 | ||
| 357 | // Should be rejected | 344 | // Should be rejected |
| 358 | assert_eq!(response[0], "OK"); | 345 | assert_eq!(response[0], "OK"); |
| 359 | assert_eq!(response[1], event.id.to_hex()); | 346 | assert_eq!(response[1], event.id.to_hex()); |
| 360 | assert_eq!(response[2], false, "Event should be rejected"); | 347 | assert_eq!(response[2], false, "Event should be rejected"); |
| 361 | 348 | ||
| 362 | let message = response[3].as_str().unwrap(); | 349 | let message = response[3].as_str().unwrap(); |
| 363 | assert!( | 350 | assert!( |
| 364 | message.contains("identifier") || message.contains("invalid"), | 351 | message.contains("identifier") || message.contains("invalid"), |
| @@ -376,21 +363,22 @@ async fn test_query_announcements() { | |||
| 376 | let relay = TestRelay::start().await; | 363 | let relay = TestRelay::start().await; |
| 377 | let keys = Keys::generate(); | 364 | let keys = Keys::generate(); |
| 378 | 365 | ||
| 379 | let (mut ws, _) = connect_async(relay.url()) | 366 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 380 | .await | ||
| 381 | .expect("Failed to connect"); | ||
| 382 | 367 | ||
| 383 | // Send an announcement | 368 | // Send an announcement |
| 384 | let event = create_announcement( | 369 | let event = create_announcement( |
| 385 | &keys, | 370 | &keys, |
| 386 | &relay.domain(), | 371 | &relay.domain(), |
| 387 | "query-test-repo", | 372 | "query-test-repo", |
| 388 | vec![format!("https://{}/alice/query-test-repo.git", relay.domain())], | 373 | vec![format!( |
| 374 | "https://{}/alice/query-test-repo.git", | ||
| 375 | relay.domain() | ||
| 376 | )], | ||
| 389 | vec![format!("wss://{}", relay.domain())], | 377 | vec![format!("wss://{}", relay.domain())], |
| 390 | ); | 378 | ); |
| 391 | 379 | ||
| 392 | let event_msg = json!(["EVENT", event]); | 380 | let event_msg = json!(["EVENT", event]); |
| 393 | ws.send(Message::Text(event_msg.to_string())) | 381 | ws.send(Message::Text(event_msg.to_string().into())) |
| 394 | .await | 382 | .await |
| 395 | .expect("Failed to send event"); | 383 | .expect("Failed to send event"); |
| 396 | 384 | ||
| @@ -409,7 +397,7 @@ async fn test_query_announcements() { | |||
| 409 | } | 397 | } |
| 410 | ]); | 398 | ]); |
| 411 | 399 | ||
| 412 | ws.send(Message::Text(req.to_string())) | 400 | ws.send(Message::Text(req.to_string().into())) |
| 413 | .await | 401 | .await |
| 414 | .expect("Failed to send REQ"); | 402 | .expect("Failed to send REQ"); |
| 415 | 403 | ||
| @@ -420,7 +408,7 @@ async fn test_query_announcements() { | |||
| 420 | for _ in 0..10 { | 408 | for _ in 0..10 { |
| 421 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 409 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 422 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); | 410 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 423 | 411 | ||
| 424 | if response[0] == "EVENT" { | 412 | if response[0] == "EVENT" { |
| 425 | assert_eq!(response[1], "test-sub"); | 413 | assert_eq!(response[1], "test-sub"); |
| 426 | found_event = true; | 414 | found_event = true; |
| @@ -442,9 +430,7 @@ async fn test_query_states() { | |||
| 442 | let relay = TestRelay::start().await; | 430 | let relay = TestRelay::start().await; |
| 443 | let keys = Keys::generate(); | 431 | let keys = Keys::generate(); |
| 444 | 432 | ||
| 445 | let (mut ws, _) = connect_async(relay.url()) | 433 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 446 | .await | ||
| 447 | .expect("Failed to connect"); | ||
| 448 | 434 | ||
| 449 | // Send a state event | 435 | // Send a state event |
| 450 | let event = create_state( | 436 | let event = create_state( |
| @@ -454,7 +440,7 @@ async fn test_query_states() { | |||
| 454 | ); | 440 | ); |
| 455 | 441 | ||
| 456 | let event_msg = json!(["EVENT", event]); | 442 | let event_msg = json!(["EVENT", event]); |
| 457 | ws.send(Message::Text(event_msg.to_string())) | 443 | ws.send(Message::Text(event_msg.to_string().into())) |
| 458 | .await | 444 | .await |
| 459 | .expect("Failed to send event"); | 445 | .expect("Failed to send event"); |
| 460 | 446 | ||
| @@ -473,7 +459,7 @@ async fn test_query_states() { | |||
| 473 | } | 459 | } |
| 474 | ]); | 460 | ]); |
| 475 | 461 | ||
| 476 | ws.send(Message::Text(req.to_string())) | 462 | ws.send(Message::Text(req.to_string().into())) |
| 477 | .await | 463 | .await |
| 478 | .expect("Failed to send REQ"); | 464 | .expect("Failed to send REQ"); |
| 479 | 465 | ||
| @@ -484,7 +470,7 @@ async fn test_query_states() { | |||
| 484 | for _ in 0..10 { | 470 | for _ in 0..10 { |
| 485 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 471 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 486 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); | 472 | let response: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 487 | 473 | ||
| 488 | if response[0] == "EVENT" { | 474 | if response[0] == "EVENT" { |
| 489 | assert_eq!(response[1], "test-sub"); | 475 | assert_eq!(response[1], "test-sub"); |
| 490 | found_event = true; | 476 | found_event = true; |
| @@ -506,21 +492,22 @@ async fn test_duplicate_announcement() { | |||
| 506 | let relay = TestRelay::start().await; | 492 | let relay = TestRelay::start().await; |
| 507 | let keys = Keys::generate(); | 493 | let keys = Keys::generate(); |
| 508 | 494 | ||
| 509 | let (mut ws, _) = connect_async(relay.url()) | 495 | let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect"); |
| 510 | .await | ||
| 511 | .expect("Failed to connect"); | ||
| 512 | 496 | ||
| 513 | let event = create_announcement( | 497 | let event = create_announcement( |
| 514 | &keys, | 498 | &keys, |
| 515 | &relay.domain(), | 499 | &relay.domain(), |
| 516 | "duplicate-test", | 500 | "duplicate-test", |
| 517 | vec![format!("https://{}/alice/duplicate-test.git", relay.domain())], | 501 | vec![format!( |
| 502 | "https://{}/alice/duplicate-test.git", | ||
| 503 | relay.domain() | ||
| 504 | )], | ||
| 518 | vec![format!("wss://{}", relay.domain())], | 505 | vec![format!("wss://{}", relay.domain())], |
| 519 | ); | 506 | ); |
| 520 | 507 | ||
| 521 | // Send first time | 508 | // Send first time |
| 522 | let event_msg = json!(["EVENT", event]); | 509 | let event_msg = json!(["EVENT", event]); |
| 523 | ws.send(Message::Text(event_msg.to_string())) | 510 | ws.send(Message::Text(event_msg.to_string().into())) |
| 524 | .await | 511 | .await |
| 525 | .expect("Failed to send event"); | 512 | .expect("Failed to send event"); |
| 526 | 513 | ||
| @@ -531,14 +518,14 @@ async fn test_duplicate_announcement() { | |||
| 531 | 518 | ||
| 532 | // Send second time (duplicate) | 519 | // Send second time (duplicate) |
| 533 | let event_msg = json!(["EVENT", event]); | 520 | let event_msg = json!(["EVENT", event]); |
| 534 | ws.send(Message::Text(event_msg.to_string())) | 521 | ws.send(Message::Text(event_msg.to_string().into())) |
| 535 | .await | 522 | .await |
| 536 | .expect("Failed to send event"); | 523 | .expect("Failed to send event"); |
| 537 | 524 | ||
| 538 | if let Some(Ok(Message::Text(text))) = ws.next().await { | 525 | if let Some(Ok(Message::Text(text))) = ws.next().await { |
| 539 | let response2: Value = serde_json::from_str(&text).expect("Failed to parse"); | 526 | let response2: Value = serde_json::from_str(&text).expect("Failed to parse"); |
| 540 | assert_eq!(response2[2], true, "Duplicate should be acknowledged"); | 527 | assert_eq!(response2[2], true, "Duplicate should be acknowledged"); |
| 541 | 528 | ||
| 542 | let message = response2[3].as_str().unwrap(); | 529 | let message = response2[3].as_str().unwrap(); |
| 543 | assert!( | 530 | assert!( |
| 544 | message.contains("duplicate") || message.is_empty(), | 531 | message.contains("duplicate") || message.is_empty(), |