├── .github ├── CODEOWNERS └── workflows │ └── release.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples ├── connect.rs └── recieve_messages.rs └── src ├── errors.rs ├── leap ├── mod.rs └── types.rs ├── lib.rs ├── manager ├── mod.rs └── types.rs ├── messenger ├── mod.rs └── types.rs ├── runner └── mod.rs └── shard ├── error.rs ├── mod.rs ├── socket.rs └── types.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @pxseu -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | jobs: 8 | draft-release: 9 | name: Create Release 10 | runs-on: ubuntu-latest 11 | outputs: 12 | tag_name: ${{ steps.tag.outputs.tag_name }} 13 | steps: 14 | - name: Checkout the repo 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 1 18 | 19 | - name: Get tag data 20 | id: tag 21 | run: | 22 | # replace the following commands to use the new GITHUB_OUTPUT syntax 23 | 24 | echo "tag_name=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT 25 | 26 | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$ ]]; then 27 | echo "pre_release=true" >> $GITHUB_OUTPUT 28 | fi 29 | 30 | - name: Create new release 31 | uses: "marvinpinto/action-automatic-releases@latest" 32 | with: 33 | repo_token: ${{ secrets.GITHUB_TOKEN }} 34 | prerelease: ${{ steps.tag.outputs.pre_release == 'true' }} 35 | title: "Version: ${{ steps.tag.outputs.tag_name }}" 36 | draft: true 37 | 38 | publish-release: 39 | name: Publish Release 40 | needs: ["draft-release"] 41 | runs-on: ubuntu-latest 42 | continue-on-error: true 43 | environment: prod 44 | steps: 45 | - name: Checkout the repo 46 | uses: actions/checkout@v3 47 | with: 48 | fetch-depth: 1 49 | 50 | - name: Install Rust 51 | uses: actions-rs/toolchain@v1 52 | with: 53 | toolchain: stable 54 | profile: minimal 55 | override: true 56 | 57 | - name: Login to Crates 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: login 61 | args: ${{ secrets.CARGO_REGISTRY_TOKEN }} 62 | 63 | - name: Publish Crates 64 | uses: actions-rs/cargo@v1 65 | with: 66 | command: publish 67 | args: --no-verify 68 | 69 | - name: Update Release 70 | run: gh release edit ${{ needs.draft-release.outputs.tag_name }} --draft=false --repo=${{ github.repository }} 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | hop.yml 3 | .env -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | hex_literal_case = "Upper" 3 | imports_granularity = "Module" 4 | newline_style = "Unix" 5 | normalize_comments = true 6 | normalize_doc_attributes = true 7 | reorder_impl_items = true 8 | group_imports = "StdExternalCrate" 9 | use_field_init_shorthand = true 10 | use_try_shorthand = true 11 | wrap_comments = true 12 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "async-compression" 22 | version = "0.4.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "bb42b2197bf15ccb092b62c74515dbd8b86d0effd934795f6687c93b6e679a2c" 25 | dependencies = [ 26 | "flate2", 27 | "futures-core", 28 | "memchr", 29 | "pin-project-lite", 30 | "tokio", 31 | ] 32 | 33 | [[package]] 34 | name = "async-trait" 35 | version = "0.1.73" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" 38 | dependencies = [ 39 | "proc-macro2", 40 | "quote", 41 | "syn", 42 | ] 43 | 44 | [[package]] 45 | name = "async-tungstenite" 46 | version = "0.23.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc" 49 | dependencies = [ 50 | "futures-io", 51 | "futures-util", 52 | "log", 53 | "native-tls", 54 | "pin-project-lite", 55 | "rustls-native-certs", 56 | "tokio", 57 | "tokio-native-tls", 58 | "tokio-rustls", 59 | "tungstenite", 60 | "webpki-roots", 61 | ] 62 | 63 | [[package]] 64 | name = "autocfg" 65 | version = "1.1.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 68 | 69 | [[package]] 70 | name = "backtrace" 71 | version = "0.3.69" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 74 | dependencies = [ 75 | "addr2line", 76 | "cc", 77 | "cfg-if", 78 | "libc", 79 | "miniz_oxide", 80 | "object", 81 | "rustc-demangle", 82 | ] 83 | 84 | [[package]] 85 | name = "base64" 86 | version = "0.21.4" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" 89 | 90 | [[package]] 91 | name = "bitflags" 92 | version = "1.3.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 95 | 96 | [[package]] 97 | name = "bitflags" 98 | version = "2.4.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 101 | 102 | [[package]] 103 | name = "block-buffer" 104 | version = "0.10.4" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 107 | dependencies = [ 108 | "generic-array", 109 | ] 110 | 111 | [[package]] 112 | name = "bumpalo" 113 | version = "3.14.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 116 | 117 | [[package]] 118 | name = "byteorder" 119 | version = "1.5.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 122 | 123 | [[package]] 124 | name = "bytes" 125 | version = "1.5.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 128 | 129 | [[package]] 130 | name = "cc" 131 | version = "1.0.83" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 134 | dependencies = [ 135 | "libc", 136 | ] 137 | 138 | [[package]] 139 | name = "cfg-if" 140 | version = "1.0.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 143 | 144 | [[package]] 145 | name = "core-foundation" 146 | version = "0.9.3" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 149 | dependencies = [ 150 | "core-foundation-sys", 151 | "libc", 152 | ] 153 | 154 | [[package]] 155 | name = "core-foundation-sys" 156 | version = "0.8.4" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 159 | 160 | [[package]] 161 | name = "cpufeatures" 162 | version = "0.2.9" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" 165 | dependencies = [ 166 | "libc", 167 | ] 168 | 169 | [[package]] 170 | name = "crc32fast" 171 | version = "1.3.2" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 174 | dependencies = [ 175 | "cfg-if", 176 | ] 177 | 178 | [[package]] 179 | name = "crypto-common" 180 | version = "0.1.6" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 183 | dependencies = [ 184 | "generic-array", 185 | "typenum", 186 | ] 187 | 188 | [[package]] 189 | name = "data-encoding" 190 | version = "2.4.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" 193 | 194 | [[package]] 195 | name = "digest" 196 | version = "0.10.7" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 199 | dependencies = [ 200 | "block-buffer", 201 | "crypto-common", 202 | ] 203 | 204 | [[package]] 205 | name = "errno" 206 | version = "0.3.4" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" 209 | dependencies = [ 210 | "errno-dragonfly", 211 | "libc", 212 | "windows-sys", 213 | ] 214 | 215 | [[package]] 216 | name = "errno-dragonfly" 217 | version = "0.1.2" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 220 | dependencies = [ 221 | "cc", 222 | "libc", 223 | ] 224 | 225 | [[package]] 226 | name = "fastrand" 227 | version = "2.0.1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 230 | 231 | [[package]] 232 | name = "fern" 233 | version = "0.6.2" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" 236 | dependencies = [ 237 | "log", 238 | ] 239 | 240 | [[package]] 241 | name = "flate2" 242 | version = "1.0.27" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" 245 | dependencies = [ 246 | "crc32fast", 247 | "miniz_oxide", 248 | ] 249 | 250 | [[package]] 251 | name = "fnv" 252 | version = "1.0.7" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 255 | 256 | [[package]] 257 | name = "foreign-types" 258 | version = "0.3.2" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 261 | dependencies = [ 262 | "foreign-types-shared", 263 | ] 264 | 265 | [[package]] 266 | name = "foreign-types-shared" 267 | version = "0.1.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 270 | 271 | [[package]] 272 | name = "form_urlencoded" 273 | version = "1.2.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 276 | dependencies = [ 277 | "percent-encoding", 278 | ] 279 | 280 | [[package]] 281 | name = "futures" 282 | version = "0.3.28" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 285 | dependencies = [ 286 | "futures-channel", 287 | "futures-core", 288 | "futures-io", 289 | "futures-sink", 290 | "futures-task", 291 | "futures-util", 292 | ] 293 | 294 | [[package]] 295 | name = "futures-channel" 296 | version = "0.3.28" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 299 | dependencies = [ 300 | "futures-core", 301 | "futures-sink", 302 | ] 303 | 304 | [[package]] 305 | name = "futures-core" 306 | version = "0.3.28" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 309 | 310 | [[package]] 311 | name = "futures-io" 312 | version = "0.3.28" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 315 | 316 | [[package]] 317 | name = "futures-sink" 318 | version = "0.3.28" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 321 | 322 | [[package]] 323 | name = "futures-task" 324 | version = "0.3.28" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 327 | 328 | [[package]] 329 | name = "futures-util" 330 | version = "0.3.28" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 333 | dependencies = [ 334 | "futures-channel", 335 | "futures-core", 336 | "futures-io", 337 | "futures-sink", 338 | "futures-task", 339 | "memchr", 340 | "pin-project-lite", 341 | "pin-utils", 342 | "slab", 343 | ] 344 | 345 | [[package]] 346 | name = "generic-array" 347 | version = "0.14.7" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 350 | dependencies = [ 351 | "typenum", 352 | "version_check", 353 | ] 354 | 355 | [[package]] 356 | name = "getrandom" 357 | version = "0.2.10" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 360 | dependencies = [ 361 | "cfg-if", 362 | "libc", 363 | "wasi", 364 | ] 365 | 366 | [[package]] 367 | name = "gimli" 368 | version = "0.28.0" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 371 | 372 | [[package]] 373 | name = "hermit-abi" 374 | version = "0.3.3" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 377 | 378 | [[package]] 379 | name = "http" 380 | version = "0.2.9" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 383 | dependencies = [ 384 | "bytes", 385 | "fnv", 386 | "itoa", 387 | ] 388 | 389 | [[package]] 390 | name = "httparse" 391 | version = "1.8.0" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 394 | 395 | [[package]] 396 | name = "idna" 397 | version = "0.4.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 400 | dependencies = [ 401 | "unicode-bidi", 402 | "unicode-normalization", 403 | ] 404 | 405 | [[package]] 406 | name = "itoa" 407 | version = "1.0.9" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 410 | 411 | [[package]] 412 | name = "js-sys" 413 | version = "0.3.64" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 416 | dependencies = [ 417 | "wasm-bindgen", 418 | ] 419 | 420 | [[package]] 421 | name = "lazy_static" 422 | version = "1.4.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 425 | 426 | [[package]] 427 | name = "leap_client_rs" 428 | version = "0.1.5" 429 | dependencies = [ 430 | "async-compression", 431 | "async-trait", 432 | "async-tungstenite", 433 | "fern", 434 | "futures", 435 | "log", 436 | "serde", 437 | "serde_json", 438 | "serde_repr", 439 | "tokio", 440 | ] 441 | 442 | [[package]] 443 | name = "libc" 444 | version = "0.2.149" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" 447 | 448 | [[package]] 449 | name = "linux-raw-sys" 450 | version = "0.4.8" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" 453 | 454 | [[package]] 455 | name = "lock_api" 456 | version = "0.4.10" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 459 | dependencies = [ 460 | "autocfg", 461 | "scopeguard", 462 | ] 463 | 464 | [[package]] 465 | name = "log" 466 | version = "0.4.20" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 469 | 470 | [[package]] 471 | name = "memchr" 472 | version = "2.6.4" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 475 | 476 | [[package]] 477 | name = "miniz_oxide" 478 | version = "0.7.1" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 481 | dependencies = [ 482 | "adler", 483 | ] 484 | 485 | [[package]] 486 | name = "mio" 487 | version = "0.8.8" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 490 | dependencies = [ 491 | "libc", 492 | "wasi", 493 | "windows-sys", 494 | ] 495 | 496 | [[package]] 497 | name = "native-tls" 498 | version = "0.2.11" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 501 | dependencies = [ 502 | "lazy_static", 503 | "libc", 504 | "log", 505 | "openssl", 506 | "openssl-probe", 507 | "openssl-sys", 508 | "schannel", 509 | "security-framework", 510 | "security-framework-sys", 511 | "tempfile", 512 | ] 513 | 514 | [[package]] 515 | name = "num_cpus" 516 | version = "1.16.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 519 | dependencies = [ 520 | "hermit-abi", 521 | "libc", 522 | ] 523 | 524 | [[package]] 525 | name = "object" 526 | version = "0.32.1" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 529 | dependencies = [ 530 | "memchr", 531 | ] 532 | 533 | [[package]] 534 | name = "once_cell" 535 | version = "1.18.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 538 | 539 | [[package]] 540 | name = "openssl" 541 | version = "0.10.57" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" 544 | dependencies = [ 545 | "bitflags 2.4.0", 546 | "cfg-if", 547 | "foreign-types", 548 | "libc", 549 | "once_cell", 550 | "openssl-macros", 551 | "openssl-sys", 552 | ] 553 | 554 | [[package]] 555 | name = "openssl-macros" 556 | version = "0.1.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 559 | dependencies = [ 560 | "proc-macro2", 561 | "quote", 562 | "syn", 563 | ] 564 | 565 | [[package]] 566 | name = "openssl-probe" 567 | version = "0.1.5" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 570 | 571 | [[package]] 572 | name = "openssl-sys" 573 | version = "0.9.93" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" 576 | dependencies = [ 577 | "cc", 578 | "libc", 579 | "pkg-config", 580 | "vcpkg", 581 | ] 582 | 583 | [[package]] 584 | name = "parking_lot" 585 | version = "0.12.1" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 588 | dependencies = [ 589 | "lock_api", 590 | "parking_lot_core", 591 | ] 592 | 593 | [[package]] 594 | name = "parking_lot_core" 595 | version = "0.9.8" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 598 | dependencies = [ 599 | "cfg-if", 600 | "libc", 601 | "redox_syscall", 602 | "smallvec", 603 | "windows-targets", 604 | ] 605 | 606 | [[package]] 607 | name = "percent-encoding" 608 | version = "2.3.0" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 611 | 612 | [[package]] 613 | name = "pin-project-lite" 614 | version = "0.2.13" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 617 | 618 | [[package]] 619 | name = "pin-utils" 620 | version = "0.1.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 623 | 624 | [[package]] 625 | name = "pkg-config" 626 | version = "0.3.27" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 629 | 630 | [[package]] 631 | name = "ppv-lite86" 632 | version = "0.2.17" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 635 | 636 | [[package]] 637 | name = "proc-macro2" 638 | version = "1.0.68" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" 641 | dependencies = [ 642 | "unicode-ident", 643 | ] 644 | 645 | [[package]] 646 | name = "quote" 647 | version = "1.0.33" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 650 | dependencies = [ 651 | "proc-macro2", 652 | ] 653 | 654 | [[package]] 655 | name = "rand" 656 | version = "0.8.5" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 659 | dependencies = [ 660 | "libc", 661 | "rand_chacha", 662 | "rand_core", 663 | ] 664 | 665 | [[package]] 666 | name = "rand_chacha" 667 | version = "0.3.1" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 670 | dependencies = [ 671 | "ppv-lite86", 672 | "rand_core", 673 | ] 674 | 675 | [[package]] 676 | name = "rand_core" 677 | version = "0.6.4" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 680 | dependencies = [ 681 | "getrandom", 682 | ] 683 | 684 | [[package]] 685 | name = "redox_syscall" 686 | version = "0.3.5" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 689 | dependencies = [ 690 | "bitflags 1.3.2", 691 | ] 692 | 693 | [[package]] 694 | name = "ring" 695 | version = "0.16.20" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 698 | dependencies = [ 699 | "cc", 700 | "libc", 701 | "once_cell", 702 | "spin", 703 | "untrusted", 704 | "web-sys", 705 | "winapi", 706 | ] 707 | 708 | [[package]] 709 | name = "rustc-demangle" 710 | version = "0.1.23" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 713 | 714 | [[package]] 715 | name = "rustix" 716 | version = "0.38.17" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" 719 | dependencies = [ 720 | "bitflags 2.4.0", 721 | "errno", 722 | "libc", 723 | "linux-raw-sys", 724 | "windows-sys", 725 | ] 726 | 727 | [[package]] 728 | name = "rustls" 729 | version = "0.21.7" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" 732 | dependencies = [ 733 | "log", 734 | "ring", 735 | "rustls-webpki", 736 | "sct", 737 | ] 738 | 739 | [[package]] 740 | name = "rustls-native-certs" 741 | version = "0.6.3" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" 744 | dependencies = [ 745 | "openssl-probe", 746 | "rustls-pemfile", 747 | "schannel", 748 | "security-framework", 749 | ] 750 | 751 | [[package]] 752 | name = "rustls-pemfile" 753 | version = "1.0.3" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" 756 | dependencies = [ 757 | "base64", 758 | ] 759 | 760 | [[package]] 761 | name = "rustls-webpki" 762 | version = "0.101.6" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" 765 | dependencies = [ 766 | "ring", 767 | "untrusted", 768 | ] 769 | 770 | [[package]] 771 | name = "ryu" 772 | version = "1.0.15" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 775 | 776 | [[package]] 777 | name = "schannel" 778 | version = "0.1.22" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" 781 | dependencies = [ 782 | "windows-sys", 783 | ] 784 | 785 | [[package]] 786 | name = "scopeguard" 787 | version = "1.2.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 790 | 791 | [[package]] 792 | name = "sct" 793 | version = "0.7.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 796 | dependencies = [ 797 | "ring", 798 | "untrusted", 799 | ] 800 | 801 | [[package]] 802 | name = "security-framework" 803 | version = "2.9.2" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" 806 | dependencies = [ 807 | "bitflags 1.3.2", 808 | "core-foundation", 809 | "core-foundation-sys", 810 | "libc", 811 | "security-framework-sys", 812 | ] 813 | 814 | [[package]] 815 | name = "security-framework-sys" 816 | version = "2.9.1" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" 819 | dependencies = [ 820 | "core-foundation-sys", 821 | "libc", 822 | ] 823 | 824 | [[package]] 825 | name = "serde" 826 | version = "1.0.188" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 829 | dependencies = [ 830 | "serde_derive", 831 | ] 832 | 833 | [[package]] 834 | name = "serde_derive" 835 | version = "1.0.188" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 838 | dependencies = [ 839 | "proc-macro2", 840 | "quote", 841 | "syn", 842 | ] 843 | 844 | [[package]] 845 | name = "serde_json" 846 | version = "1.0.107" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 849 | dependencies = [ 850 | "itoa", 851 | "ryu", 852 | "serde", 853 | ] 854 | 855 | [[package]] 856 | name = "serde_repr" 857 | version = "0.1.16" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" 860 | dependencies = [ 861 | "proc-macro2", 862 | "quote", 863 | "syn", 864 | ] 865 | 866 | [[package]] 867 | name = "sha1" 868 | version = "0.10.6" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 871 | dependencies = [ 872 | "cfg-if", 873 | "cpufeatures", 874 | "digest", 875 | ] 876 | 877 | [[package]] 878 | name = "signal-hook-registry" 879 | version = "1.4.1" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 882 | dependencies = [ 883 | "libc", 884 | ] 885 | 886 | [[package]] 887 | name = "slab" 888 | version = "0.4.9" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 891 | dependencies = [ 892 | "autocfg", 893 | ] 894 | 895 | [[package]] 896 | name = "smallvec" 897 | version = "1.11.1" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" 900 | 901 | [[package]] 902 | name = "socket2" 903 | version = "0.5.4" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" 906 | dependencies = [ 907 | "libc", 908 | "windows-sys", 909 | ] 910 | 911 | [[package]] 912 | name = "spin" 913 | version = "0.5.2" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 916 | 917 | [[package]] 918 | name = "syn" 919 | version = "2.0.38" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" 922 | dependencies = [ 923 | "proc-macro2", 924 | "quote", 925 | "unicode-ident", 926 | ] 927 | 928 | [[package]] 929 | name = "tempfile" 930 | version = "3.8.0" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 933 | dependencies = [ 934 | "cfg-if", 935 | "fastrand", 936 | "redox_syscall", 937 | "rustix", 938 | "windows-sys", 939 | ] 940 | 941 | [[package]] 942 | name = "thiserror" 943 | version = "1.0.49" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" 946 | dependencies = [ 947 | "thiserror-impl", 948 | ] 949 | 950 | [[package]] 951 | name = "thiserror-impl" 952 | version = "1.0.49" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" 955 | dependencies = [ 956 | "proc-macro2", 957 | "quote", 958 | "syn", 959 | ] 960 | 961 | [[package]] 962 | name = "tinyvec" 963 | version = "1.6.0" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 966 | dependencies = [ 967 | "tinyvec_macros", 968 | ] 969 | 970 | [[package]] 971 | name = "tinyvec_macros" 972 | version = "0.1.1" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 975 | 976 | [[package]] 977 | name = "tokio" 978 | version = "1.32.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" 981 | dependencies = [ 982 | "backtrace", 983 | "bytes", 984 | "libc", 985 | "mio", 986 | "num_cpus", 987 | "parking_lot", 988 | "pin-project-lite", 989 | "signal-hook-registry", 990 | "socket2", 991 | "tokio-macros", 992 | "windows-sys", 993 | ] 994 | 995 | [[package]] 996 | name = "tokio-macros" 997 | version = "2.1.0" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 1000 | dependencies = [ 1001 | "proc-macro2", 1002 | "quote", 1003 | "syn", 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "tokio-native-tls" 1008 | version = "0.3.1" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1011 | dependencies = [ 1012 | "native-tls", 1013 | "tokio", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "tokio-rustls" 1018 | version = "0.24.1" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 1021 | dependencies = [ 1022 | "rustls", 1023 | "tokio", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "tungstenite" 1028 | version = "0.20.1" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" 1031 | dependencies = [ 1032 | "byteorder", 1033 | "bytes", 1034 | "data-encoding", 1035 | "http", 1036 | "httparse", 1037 | "log", 1038 | "native-tls", 1039 | "rand", 1040 | "rustls", 1041 | "sha1", 1042 | "thiserror", 1043 | "url", 1044 | "utf-8", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "typenum" 1049 | version = "1.17.0" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1052 | 1053 | [[package]] 1054 | name = "unicode-bidi" 1055 | version = "0.3.13" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1058 | 1059 | [[package]] 1060 | name = "unicode-ident" 1061 | version = "1.0.12" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1064 | 1065 | [[package]] 1066 | name = "unicode-normalization" 1067 | version = "0.1.22" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1070 | dependencies = [ 1071 | "tinyvec", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "untrusted" 1076 | version = "0.7.1" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1079 | 1080 | [[package]] 1081 | name = "url" 1082 | version = "2.4.1" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 1085 | dependencies = [ 1086 | "form_urlencoded", 1087 | "idna", 1088 | "percent-encoding", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "utf-8" 1093 | version = "0.7.6" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1096 | 1097 | [[package]] 1098 | name = "vcpkg" 1099 | version = "0.2.15" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1102 | 1103 | [[package]] 1104 | name = "version_check" 1105 | version = "0.9.4" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1108 | 1109 | [[package]] 1110 | name = "wasi" 1111 | version = "0.11.0+wasi-snapshot-preview1" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1114 | 1115 | [[package]] 1116 | name = "wasm-bindgen" 1117 | version = "0.2.87" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 1120 | dependencies = [ 1121 | "cfg-if", 1122 | "wasm-bindgen-macro", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "wasm-bindgen-backend" 1127 | version = "0.2.87" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 1130 | dependencies = [ 1131 | "bumpalo", 1132 | "log", 1133 | "once_cell", 1134 | "proc-macro2", 1135 | "quote", 1136 | "syn", 1137 | "wasm-bindgen-shared", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "wasm-bindgen-macro" 1142 | version = "0.2.87" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 1145 | dependencies = [ 1146 | "quote", 1147 | "wasm-bindgen-macro-support", 1148 | ] 1149 | 1150 | [[package]] 1151 | name = "wasm-bindgen-macro-support" 1152 | version = "0.2.87" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 1155 | dependencies = [ 1156 | "proc-macro2", 1157 | "quote", 1158 | "syn", 1159 | "wasm-bindgen-backend", 1160 | "wasm-bindgen-shared", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "wasm-bindgen-shared" 1165 | version = "0.2.87" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 1168 | 1169 | [[package]] 1170 | name = "web-sys" 1171 | version = "0.3.64" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" 1174 | dependencies = [ 1175 | "js-sys", 1176 | "wasm-bindgen", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "webpki-roots" 1181 | version = "0.25.2" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" 1184 | 1185 | [[package]] 1186 | name = "winapi" 1187 | version = "0.3.9" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1190 | dependencies = [ 1191 | "winapi-i686-pc-windows-gnu", 1192 | "winapi-x86_64-pc-windows-gnu", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "winapi-i686-pc-windows-gnu" 1197 | version = "0.4.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1200 | 1201 | [[package]] 1202 | name = "winapi-x86_64-pc-windows-gnu" 1203 | version = "0.4.0" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1206 | 1207 | [[package]] 1208 | name = "windows-sys" 1209 | version = "0.48.0" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1212 | dependencies = [ 1213 | "windows-targets", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "windows-targets" 1218 | version = "0.48.5" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1221 | dependencies = [ 1222 | "windows_aarch64_gnullvm", 1223 | "windows_aarch64_msvc", 1224 | "windows_i686_gnu", 1225 | "windows_i686_msvc", 1226 | "windows_x86_64_gnu", 1227 | "windows_x86_64_gnullvm", 1228 | "windows_x86_64_msvc", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "windows_aarch64_gnullvm" 1233 | version = "0.48.5" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1236 | 1237 | [[package]] 1238 | name = "windows_aarch64_msvc" 1239 | version = "0.48.5" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1242 | 1243 | [[package]] 1244 | name = "windows_i686_gnu" 1245 | version = "0.48.5" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1248 | 1249 | [[package]] 1250 | name = "windows_i686_msvc" 1251 | version = "0.48.5" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1254 | 1255 | [[package]] 1256 | name = "windows_x86_64_gnu" 1257 | version = "0.48.5" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1260 | 1261 | [[package]] 1262 | name = "windows_x86_64_gnullvm" 1263 | version = "0.48.5" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1266 | 1267 | [[package]] 1268 | name = "windows_x86_64_msvc" 1269 | version = "0.48.5" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1272 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "leap_client_rs" 3 | version = "0.1.5" 4 | edition = "2021" 5 | license = "MPL-2.0" 6 | authors = ["Hop Inc."] 7 | description = "Rust client library for the Leap Edge socket service" 8 | repository = "https://github.com/hopinc/leap_client_rs#readme" 9 | homepage = "https://hop.io" 10 | build = "build.rs" 11 | keywords = ["hop", "leap_edge", "client", "channels", "async"] 12 | categories = ["network-programming", "asynchronous", "tokio"] 13 | 14 | [dependencies] 15 | serde_json = "1.0" 16 | async-trait = "0.1" 17 | serde_repr = "0.1" 18 | log = "0.4" 19 | 20 | [dependencies.futures] 21 | version = "0.3" 22 | default-features = false 23 | features = ["std"] 24 | 25 | [dependencies.async-tungstenite] 26 | version = "0.23" 27 | default-features = false 28 | features = ["tokio-runtime"] 29 | 30 | [dependencies.serde] 31 | version = "1.0" 32 | features = ["derive"] 33 | 34 | [dependencies.async-compression] 35 | optional = true 36 | version = "0.4" 37 | features = ["tokio", "zlib"] 38 | 39 | [dependencies.tokio] 40 | version = "1.20" 41 | features = ["macros", "net", "time", "sync", "io-util", "rt"] 42 | 43 | [features] 44 | # default to webpki roots for easy cross compilation 45 | default = ["rustls-tls-webpki-roots", "zlib"] 46 | 47 | # compression reduces the size of the websocket payloads 48 | zlib = ["async-compression"] 49 | 50 | # allow for multiple different TLS backends 51 | native-tls = ["async-tungstenite/tokio-native-tls"] 52 | rustls-tls-native-roots = ["async-tungstenite/tokio-rustls-native-certs"] 53 | rustls-tls-webpki-roots = ["async-tungstenite/tokio-rustls-webpki-roots"] 54 | 55 | [dev-dependencies] 56 | fern = "0.6" 57 | 58 | [dev-dependencies.tokio] 59 | version = "1.20" 60 | features = ["full"] 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leap_edge_rs 2 | 3 | Utility library for connecting and receiving events from [Leap Edge](https://docs.hop.io/other-products/channels/internals/leap). Used for Channels and Pipe. 4 | 5 | ## Installation 6 | 7 | Add this to your `Cargo.toml`: 8 | 9 | ```toml 10 | [dependencies] 11 | leap_edge_rs = "0.1" 12 | ``` 13 | 14 | or if you want to add features: 15 | 16 | ```toml 17 | [dependencies.leap_edge_rs] 18 | version = "0.1" 19 | default-features = false 20 | features = ["rustls-tls-webpki-roots", "zlib"] 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Subscribe to a channel 26 | 27 | ```rust 28 | use leap_edge_rs::{LeapEdge, LeapOptions, leap::types::Event}; 29 | 30 | #[tokio::main] 31 | async fn main() -> Result<(), std::io::Error> { 32 | let leap = LeapEdge::new(LeapOptions { 33 | project: "my-project", 34 | ..Default::default() 35 | }).await?; 36 | 37 | leap.channel_subscribe("my-channel").await?; 38 | 39 | while let Some(event) = leap.listen().await { 40 | println!("{:?}", event); 41 | } 42 | } 43 | ``` 44 | 45 | ### Get all events: 46 | 47 | ```rust 48 | use leap_edge_rs::{LeapEdge, LeapOptions}; 49 | 50 | #[tokio::main] 51 | async fn main() -> Result<(), std::io::Error> { 52 | let leap = LeapEdge::new(LeapOptions { 53 | project: "my-project", 54 | ..Default::default() 55 | }).await?; 56 | 57 | while let Some(event) = leap.listen().await { 58 | println!("{:?}", event); 59 | } 60 | } 61 | ``` 62 | 63 | ### Get only messages or direct messages: 64 | 65 | ```rust 66 | use leap_edge_rs::{LeapEdge, LeapOptions, leap::types::Event}; 67 | 68 | #[tokio::main] 69 | async fn main() -> Result<(), std::io::Error> { 70 | let leap = LeapEdge::new(LeapOptions { 71 | project: "my-project", 72 | ..Default::default() 73 | }).await?; 74 | 75 | while let Some(event) = leap.listen().await { 76 | match event { 77 | Event::Message(message) | Event::DirectMessage(message) => println!("{:?}", message), 78 | 79 | _ => {} 80 | } 81 | } 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all( 2 | not(feature = "native-tls"), 3 | not(feature = "rustls-tls-native-roots"), 4 | not(feature = "rustls-tls-webpki-roots") 5 | ))] 6 | compile_error!("No TLS backend is enabled. Please enable one of the following backends: native-tls, rustls-tls-native-roots, rustls-tls-webpki-roots"); 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /examples/connect.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 2 | 3 | use leap_client_rs::{LeapEdge, LeapOptions}; 4 | use tokio::time::sleep; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), std::io::Error> { 8 | fern::Dispatch::new() 9 | .format(|out, message, _| { 10 | out.finish(format_args!( 11 | "{} - {}", 12 | SystemTime::now() 13 | .duration_since(UNIX_EPOCH) 14 | .unwrap() 15 | .as_millis(), 16 | message 17 | )) 18 | }) 19 | .level(log::LevelFilter::Debug) 20 | .chain(std::io::stdout()) 21 | .apply() 22 | .ok(); 23 | 24 | let mut manager = LeapEdge::new(LeapOptions { 25 | project: &std::env::var("PROJECT").unwrap(), 26 | token: std::env::var("TOKEN").ok().as_deref(), 27 | ..Default::default() 28 | }) 29 | .await?; 30 | 31 | if let Ok(channel) = std::env::var("CHANNEL") { 32 | manager.channel_subscribe(&channel).await?; 33 | } 34 | 35 | while (manager.listen().await).is_some() {} 36 | 37 | sleep(Duration::from_secs(5)).await; 38 | 39 | log::debug!("Done :D"); 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /examples/recieve_messages.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use leap_client_rs::{leap::types::Event, LeapEdge, LeapOptions}; 4 | use tokio::time::sleep; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), std::io::Error> { 8 | let mut manager = LeapEdge::new(LeapOptions { 9 | project: &std::env::var("PROJECT").unwrap(), 10 | token: std::env::var("TOKEN").ok().as_deref(), 11 | ..Default::default() 12 | }) 13 | .await?; 14 | 15 | if let Ok(channel) = std::env::var("CHANNEL") { 16 | manager.channel_subscribe(&channel).await?; 17 | } 18 | 19 | while let Some(event) = manager.listen().await { 20 | if matches!(event, Event::Message(_) | Event::DirectMessage(_)) { 21 | println!("{event:?}"); 22 | } 23 | } 24 | 25 | sleep(Duration::from_secs(5)).await; 26 | 27 | log::debug!("Done :D"); 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | use std::io::{Error as IoError, ErrorKind as IoErrorKind}; 4 | 5 | use async_tungstenite::tungstenite::error::Error as TungsteniteError; 6 | use futures::channel::mpsc::SendError; 7 | use serde_json::Error as JsonError; 8 | 9 | use crate::shard::error::Error as GatewayError; 10 | 11 | pub(crate) type Result = std::result::Result; 12 | 13 | #[derive(Debug)] 14 | pub enum Error { 15 | Io(IoError), 16 | Json(JsonError), 17 | Gateway(GatewayError), 18 | Tungstenite(TungsteniteError), 19 | } 20 | 21 | impl From for Error { 22 | fn from(e: IoError) -> Self { 23 | Self::Io(e) 24 | } 25 | } 26 | 27 | impl From for Error { 28 | fn from(e: JsonError) -> Self { 29 | Self::Json(e) 30 | } 31 | } 32 | 33 | impl From for Error { 34 | fn from(e: GatewayError) -> Self { 35 | Self::Gateway(e) 36 | } 37 | } 38 | 39 | impl From for Error { 40 | fn from(e: TungsteniteError) -> Self { 41 | Self::Tungstenite(e) 42 | } 43 | } 44 | 45 | impl From for Error { 46 | fn from(e: SendError) -> Self { 47 | Self::Io(IoError::new(std::io::ErrorKind::Other, e)) 48 | } 49 | } 50 | 51 | impl fmt::Display for Error { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | match self { 54 | Self::Io(inner) => fmt::Display::fmt(&inner, f), 55 | Self::Json(inner) => fmt::Display::fmt(&inner, f), 56 | Self::Tungstenite(inner) => fmt::Display::fmt(&inner, f), 57 | Self::Gateway(inner) => fmt::Display::fmt(&inner, f), 58 | } 59 | } 60 | } 61 | 62 | impl StdError for Error { 63 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 64 | match self { 65 | Self::Io(inner) => Some(inner), 66 | Self::Json(inner) => Some(inner), 67 | Self::Tungstenite(inner) => Some(inner), 68 | Self::Gateway(inner) => Some(inner), 69 | } 70 | } 71 | } 72 | 73 | // might not be needed for most but 74 | // i feel like this is a nice addition 75 | impl From for IoError { 76 | fn from(e: Error) -> Self { 77 | IoError::new(IoErrorKind::Other, e) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/leap/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod types; 2 | 3 | use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; 4 | use futures::{SinkExt, StreamExt}; 5 | use serde::Serialize; 6 | use serde_json::{json, Value}; 7 | use tokio::spawn; 8 | 9 | use self::types::{Event, EventCapsule}; 10 | use crate::errors::Result; 11 | use crate::manager::types::ShardManagerMessage; 12 | use crate::manager::{ManagerOptions, ShardManager}; 13 | 14 | pub struct LeapOptions<'a> { 15 | pub token: Option<&'a str>, 16 | pub project: &'a str, 17 | pub ws_url: &'a str, 18 | } 19 | 20 | impl Default for LeapOptions<'_> { 21 | fn default() -> Self { 22 | Self { 23 | token: None, 24 | project: "", 25 | ws_url: "wss://leap.hop.io/ws", 26 | } 27 | } 28 | } 29 | 30 | pub struct LeapEdge { 31 | manager_tx: UnboundedSender, 32 | leap_rx: UnboundedReceiver, 33 | } 34 | 35 | impl LeapEdge { 36 | /// Creates a new Leap Edge instance. 37 | /// 38 | /// # Errors 39 | /// - If the manager channel cannot connect to the Leap Edge. 40 | pub async fn new(options: LeapOptions<'_>) -> Result { 41 | let (leap_tx, leap_rx) = unbounded(); 42 | 43 | let mut manager = ShardManager::new(ManagerOptions { 44 | project: options.project, 45 | ws_url: options.ws_url, 46 | token: options.token, 47 | event_tx: leap_tx.clone(), 48 | }) 49 | .await?; 50 | 51 | let manager_tx = manager.get_manager_tx(); 52 | 53 | spawn(async move { 54 | if let Err(why) = manager.run().await { 55 | log::debug!("[Manager] Stopped: {why:?}"); 56 | } else { 57 | log::debug!("[Manager] Stopped"); 58 | } 59 | }); 60 | 61 | Ok(Self { 62 | manager_tx, 63 | leap_rx, 64 | }) 65 | } 66 | 67 | /// Send a service message to Leap Edge. 68 | /// 69 | /// # Errors 70 | /// - If the message cannot be sent. 71 | #[inline] 72 | pub async fn send_service_message(&mut self, message: D) -> Result<()> 73 | where 74 | D: Serialize, 75 | { 76 | self.manager_tx 77 | .send(ShardManagerMessage::Json(json!({ 78 | "op": 0, 79 | "d": message, 80 | }))) 81 | .await?; 82 | 83 | Ok(()) 84 | } 85 | 86 | /// Subscribe to a channel. 87 | /// 88 | /// # Errors 89 | /// - If the message cannot be sent. 90 | #[inline] 91 | pub async fn channel_subscribe(&mut self, channel: &str) -> Result<()> { 92 | self.channel_subscribe_with_data(channel, Value::Null).await 93 | } 94 | 95 | /// Subscribe to a channel with initial data. 96 | /// 97 | /// # Errors 98 | /// - If the message cannot be sent. 99 | #[inline] 100 | pub async fn channel_subscribe_with_data(&mut self, channel: &str, data: D) -> Result<()> 101 | where 102 | D: Serialize, 103 | { 104 | self.send_service_message(&Event::Subscribe(EventCapsule { 105 | channel: Some(channel.to_string()), 106 | data: serde_json::to_value(data)?, 107 | unicast: false, 108 | })) 109 | .await 110 | } 111 | 112 | /// Listen for all events. 113 | #[inline] 114 | pub async fn listen(&mut self) -> Option { 115 | self.leap_rx.next().await 116 | } 117 | 118 | /// Close the Leap Edge connection and all related work threads. 119 | pub async fn close(&mut self) { 120 | self.manager_tx.send(ShardManagerMessage::Close).await.ok(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/leap/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | 4 | #[derive(Debug, Clone, Deserialize, Serialize)] 5 | #[serde(tag = "e")] 6 | pub enum Event { 7 | #[serde(rename = "INIT")] 8 | Init(EventCapsule), 9 | #[serde(rename = "MESSAGE")] 10 | Message(EventCapsule), 11 | #[serde(rename = "DIRECT_MESSAGE")] 12 | DirectMessage(EventCapsule), 13 | #[serde(rename = "SUBSCRIBE")] 14 | Subscribe(EventCapsule), 15 | #[serde(rename = "AVAILABLE")] 16 | Available(EventCapsule), 17 | #[serde(rename = "UNAVAILABLE")] 18 | Unavailable(EventCapsule), 19 | #[serde(rename = "PIPE_ROOM_AVAILABLE")] 20 | PipeRoomAvailable(EventCapsule), 21 | #[serde(rename = "PIPE_ROOM_UPDATE")] 22 | PipeRoomUpdate(EventCapsule), 23 | #[serde(rename = "STATE_UPDATE")] 24 | StateUpdate(EventCapsule), 25 | } 26 | 27 | #[derive(Debug, Clone, Deserialize, Serialize)] 28 | pub struct EventCapsule { 29 | #[serde(rename = "c")] 30 | pub channel: Option, 31 | #[serde(rename = "d")] 32 | pub data: T, 33 | #[serde(rename = "u", skip_serializing)] 34 | pub unicast: bool, 35 | } 36 | 37 | #[derive(Debug, Clone, Deserialize, Serialize)] 38 | pub enum ConnectionScopes { 39 | #[serde(rename = "project")] 40 | Project, 41 | #[serde(rename = "token")] 42 | Token, 43 | #[serde(rename = "noauth")] 44 | NoAuth, 45 | } 46 | 47 | #[derive(Debug, Clone, Deserialize, Serialize)] 48 | pub struct InitEvent { 49 | pub cid: String, 50 | pub connection_count: u64, 51 | pub metadata: Option, 52 | pub scope: ConnectionScopes, 53 | pub channels: Vec, 54 | } 55 | 56 | #[derive(Debug, Clone, Deserialize, Serialize)] 57 | pub struct SingleChannel { 58 | pub channel: Channel, 59 | } 60 | 61 | #[derive(Debug, Clone, Deserialize, Serialize)] 62 | pub struct Channel { 63 | pub capabilities: Option, 64 | pub id: String, 65 | pub project_id: String, 66 | pub state: Value, 67 | #[serde(rename = "type")] 68 | pub type_: ChannelType, 69 | } 70 | 71 | #[derive(Debug, Clone, Deserialize, Serialize)] 72 | pub enum ChannelType { 73 | #[serde(rename = "private")] 74 | Private, 75 | #[serde(rename = "public")] 76 | Public, 77 | #[serde(rename = "unprotected")] 78 | Unprotected, 79 | } 80 | 81 | #[derive(Debug, Clone, Deserialize, Serialize)] 82 | pub struct CustomEvent { 83 | #[serde(rename = "e")] 84 | pub event: String, 85 | #[serde(rename = "d")] 86 | pub data: Value, 87 | } 88 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![allow(clippy::module_name_repetitions, clippy::unused_self)] 3 | 4 | pub mod errors; 5 | pub mod leap; 6 | pub(crate) mod manager; 7 | pub(crate) mod messenger; 8 | pub(crate) mod runner; 9 | pub(crate) mod shard; 10 | 11 | pub use leap::{LeapEdge, LeapOptions}; 12 | -------------------------------------------------------------------------------- /src/manager/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod types; 2 | 3 | use std::sync::Arc; 4 | 5 | use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; 6 | use futures::{SinkExt, StreamExt}; 7 | use tokio::spawn; 8 | use tokio::sync::Mutex; 9 | 10 | use self::types::{ShardManagerMessage, ShardRunnerInfo}; 11 | use crate::errors::{Error, Result}; 12 | use crate::leap::types::Event; 13 | use crate::messenger::types::ShardMessengerMessage; 14 | use crate::messenger::ShardMessenger; 15 | use crate::runner::ShardRunner; 16 | use crate::shard::error::Error as GatewayError; 17 | use crate::shard::types::InterMessage; 18 | use crate::shard::Shard; 19 | 20 | #[derive(Debug)] 21 | pub struct ManagerOptions<'a> { 22 | pub project: &'a str, 23 | pub ws_url: &'a str, 24 | pub token: Option<&'a str>, 25 | pub event_tx: UnboundedSender, 26 | } 27 | 28 | #[derive(Debug)] 29 | struct RunnerOptions<'a> { 30 | ws_url: Arc>, 31 | project: &'a str, 32 | token: Option<&'a str>, 33 | manager_tx: UnboundedSender, 34 | } 35 | 36 | #[derive(Debug)] 37 | pub struct ShardManager { 38 | pub runner_info: Arc>, 39 | manager_rx: UnboundedReceiver, 40 | manager_tx: UnboundedSender, 41 | event_tx: UnboundedSender, 42 | messenger_tx: UnboundedSender, 43 | token: Option, 44 | project: String, 45 | ws_url: Arc>, 46 | } 47 | 48 | impl ShardManager { 49 | pub async fn new(options: ManagerOptions<'_>) -> Result { 50 | let (manager_tx, manager_rx) = unbounded(); 51 | 52 | let ws_url = Arc::new(Mutex::new(options.ws_url.to_string())); 53 | 54 | let runner_info = Self::create_runner(&RunnerOptions { 55 | ws_url: ws_url.clone(), 56 | manager_tx: manager_tx.clone(), 57 | project: options.project, 58 | token: options.token, 59 | }) 60 | .await?; 61 | 62 | let mut messenger = ShardMessenger::new(runner_info.clone()).await; 63 | let messenger_tx = messenger.get_tx(); 64 | 65 | spawn(async move { 66 | if let Err(why) = messenger.run().await { 67 | log::debug!("[Messenger] Stopped: {why:?}"); 68 | } else { 69 | log::debug!("[Messenge] Stopped"); 70 | } 71 | }); 72 | 73 | Ok(Self { 74 | ws_url, 75 | manager_rx, 76 | manager_tx, 77 | messenger_tx, 78 | event_tx: options.event_tx, 79 | project: options.project.to_string(), 80 | token: options.token.map(std::string::ToString::to_string), 81 | runner_info, 82 | }) 83 | } 84 | 85 | pub async fn run(&mut self) -> Result<()> { 86 | loop { 87 | match self.manager_rx.next().await { 88 | Some(ShardManagerMessage::Event(data)) => { 89 | self.event_tx.send(data).await.ok(); 90 | } 91 | 92 | Some(ShardManagerMessage::Json(data)) => { 93 | self.messenger_tx 94 | .send(ShardMessengerMessage::Json(data)) 95 | .await 96 | .ok(); 97 | } 98 | 99 | Some(ShardManagerMessage::Restart) => { 100 | self.runner_info = Self::create_runner(&RunnerOptions { 101 | ws_url: self.ws_url.clone(), 102 | manager_tx: self.manager_tx.clone(), 103 | project: &self.project, 104 | token: self.token.as_deref(), 105 | }) 106 | .await?; 107 | } 108 | 109 | Some(ShardManagerMessage::Update(data)) => { 110 | self.messenger_tx 111 | .send(ShardMessengerMessage::Update(data.clone())) 112 | .await 113 | .ok(); 114 | 115 | let runner = ShardRunnerInfo { 116 | latency: data.latency, 117 | runner_tx: self.runner_info.lock().await.runner_tx.clone(), 118 | stage: data.stage, 119 | }; 120 | 121 | self.runner_info = Arc::new(Mutex::new(runner)); 122 | } 123 | 124 | Some(ShardManagerMessage::Close) => { 125 | self.shutdown().await; 126 | 127 | return Ok(()); 128 | } 129 | 130 | Some(ShardManagerMessage::InvalidAuthentication) => { 131 | self.shutdown().await; 132 | 133 | return Err(Error::Gateway(GatewayError::InvalidAuthentication)); 134 | } 135 | 136 | None => { 137 | return Ok(()); 138 | } 139 | } 140 | } 141 | } 142 | 143 | async fn create_runner(options: &RunnerOptions<'_>) -> Result>> { 144 | let shard = Shard::new(options.ws_url.clone(), options.project, options.token).await?; 145 | 146 | let mut runner = ShardRunner::new(options.manager_tx.clone(), shard); 147 | 148 | let stage = runner.shard.stage(); 149 | let runner_tx = runner.get_tx(); 150 | 151 | spawn(async move { 152 | if let Err(why) = runner.run().await { 153 | log::debug!("[Shard] Runner error: {:?}", why); 154 | } 155 | 156 | log::debug!("[Shard] Stopped"); 157 | }); 158 | 159 | Ok(Arc::new(Mutex::new(ShardRunnerInfo { 160 | latency: None, 161 | runner_tx, 162 | stage, 163 | }))) 164 | } 165 | 166 | async fn shutdown(&mut self) { 167 | self.messenger_tx 168 | .send(ShardMessengerMessage::Close) 169 | .await 170 | .ok(); 171 | 172 | self.runner_info 173 | .lock() 174 | .await 175 | .runner_tx 176 | .send(InterMessage::Close) 177 | .await 178 | .ok(); 179 | } 180 | 181 | pub fn get_manager_tx(&self) -> UnboundedSender { 182 | self.manager_tx.clone() 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/manager/types.rs: -------------------------------------------------------------------------------- 1 | use futures::channel::mpsc::UnboundedSender; 2 | use serde_json::Value; 3 | use tokio::time::Duration; 4 | 5 | use crate::leap::types::Event; 6 | use crate::shard::types::{ConnectionStage, InterMessage}; 7 | 8 | #[derive(Debug)] 9 | pub enum ShardManagerMessage { 10 | Restart, 11 | Event(Event), 12 | Update(ShardRunnerUpdate), 13 | Json(Value), 14 | Close, 15 | InvalidAuthentication, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct ShardRunnerInfo { 20 | /// The latency between when a heartbeat was sent and when the 21 | /// acknowledgement was received. 22 | pub latency: Option, 23 | /// The channel used to communicate with the shard runner, telling it 24 | /// what to do with regards to its status. 25 | pub runner_tx: UnboundedSender, 26 | /// The current connection stage of the shard. 27 | pub stage: ConnectionStage, 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | pub struct ShardRunnerUpdate { 32 | pub latency: Option, 33 | pub stage: ConnectionStage, 34 | } 35 | -------------------------------------------------------------------------------- /src/messenger/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod types; 2 | 3 | use std::sync::Arc; 4 | 5 | use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; 6 | use futures::{SinkExt, StreamExt}; 7 | use tokio::sync::Mutex; 8 | 9 | use self::types::ShardMessengerMessage; 10 | use crate::errors::Result; 11 | use crate::manager::types::ShardRunnerInfo; 12 | use crate::shard::types::{ConnectionStage, InterMessage}; 13 | 14 | pub struct ShardMessenger { 15 | messenger_rx: UnboundedReceiver, 16 | messenger_tx: UnboundedSender, 17 | runner_tx: UnboundedSender, 18 | stage: ConnectionStage, 19 | } 20 | 21 | impl ShardMessenger { 22 | pub async fn new(runner_info: Arc>) -> Self { 23 | let runner = runner_info.lock().await; 24 | let (messenger_tx, messenger_rx) = unbounded(); 25 | 26 | Self { 27 | messenger_rx, 28 | messenger_tx, 29 | runner_tx: runner.runner_tx.clone(), 30 | stage: runner.stage.clone(), 31 | } 32 | } 33 | 34 | pub async fn run(&mut self) -> Result<()> { 35 | loop { 36 | if !self.handle_event().await { 37 | return Ok(()); 38 | } 39 | } 40 | } 41 | 42 | async fn handle_event(&mut self) -> bool { 43 | match self.messenger_rx.next().await { 44 | Some(ShardMessengerMessage::Update(event)) => { 45 | self.stage = event.stage; 46 | 47 | true 48 | } 49 | 50 | Some(ShardMessengerMessage::Json(data)) => { 51 | if self.stage.is_connected() { 52 | self.runner_tx.send(InterMessage::Json(data)).await.is_ok() 53 | } else { 54 | self.messenger_tx 55 | .send(ShardMessengerMessage::Json(data)) 56 | .await 57 | .is_ok() 58 | } 59 | } 60 | 61 | Some(ShardMessengerMessage::Close) | None => false, 62 | } 63 | } 64 | 65 | pub fn get_tx(&self) -> UnboundedSender { 66 | self.messenger_tx.clone() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/messenger/types.rs: -------------------------------------------------------------------------------- 1 | use serde_json::Value; 2 | 3 | use crate::manager::types::ShardRunnerUpdate; 4 | 5 | #[derive(Debug)] 6 | pub enum ShardMessengerMessage { 7 | Json(Value), 8 | Update(ShardRunnerUpdate), 9 | Close, 10 | } 11 | -------------------------------------------------------------------------------- /src/runner/mod.rs: -------------------------------------------------------------------------------- 1 | use async_tungstenite::tungstenite::Error as TungsteniteError; 2 | use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; 3 | use futures::SinkExt; 4 | use serde::Deserialize; 5 | 6 | use crate::errors::{Error, Result}; 7 | use crate::leap::types::Event; 8 | use crate::manager::types::{ShardManagerMessage, ShardRunnerUpdate}; 9 | use crate::shard::error::Error as GatewayError; 10 | use crate::shard::socket::{RecieverExt, SenderExt}; 11 | use crate::shard::types::{GatewayEvent, InterMessage, ReconnectType, ShardAction}; 12 | use crate::shard::Shard; 13 | 14 | pub struct ShardRunner { 15 | manager_tx: UnboundedSender, 16 | runner_rx: UnboundedReceiver, 17 | runner_tx: UnboundedSender, 18 | pub shard: Shard, 19 | } 20 | 21 | impl ShardRunner { 22 | pub fn new(manager_tx: UnboundedSender, shard: Shard) -> Self { 23 | let (runner_tx, runner_rx) = unbounded(); 24 | 25 | Self { 26 | manager_tx, 27 | runner_rx, 28 | runner_tx, 29 | shard, 30 | } 31 | } 32 | 33 | pub async fn run(&mut self) -> Result<()> { 34 | loop { 35 | if !self.recieve_internal().await? { 36 | return Ok(()); 37 | } 38 | 39 | if !self.shard.check_heartbeat().await { 40 | return self.request_restart().await; 41 | } 42 | 43 | let previous_stage = self.shard.stage(); 44 | let (event, action, success) = self.recieve_event().await?; 45 | let current_stage = self.shard.stage(); 46 | 47 | if previous_stage != current_stage { 48 | self.update_manager().await?; 49 | } 50 | 51 | match action { 52 | Some(ShardAction::Reconnect(ReconnectType::Reidentify)) => { 53 | return self.request_restart().await; 54 | } 55 | 56 | Some(other) => { 57 | if let Err(e) = self.action(&other).await { 58 | log::debug!("[ShardRunner] Action error: {:?}", e); 59 | 60 | match self.shard.reconnection_type() { 61 | ReconnectType::Reidentify => return self.request_restart().await, 62 | }; 63 | } 64 | } 65 | 66 | None => {} 67 | } 68 | 69 | if let Some(event) = event { 70 | self.manager_tx 71 | .send(ShardManagerMessage::Event(event)) 72 | .await 73 | .ok(); 74 | } 75 | 76 | if !success && !self.shard.stage().is_connecting() { 77 | return self.request_restart().await; 78 | } 79 | } 80 | } 81 | 82 | async fn recieve_internal(&mut self) -> Result { 83 | loop { 84 | match self.runner_rx.try_next() { 85 | Ok(Some(message)) => { 86 | if !self.handle_rx_message(message).await { 87 | return Ok(false); 88 | } 89 | } 90 | 91 | Ok(None) => { 92 | drop(self.request_restart().await); 93 | 94 | return Ok(false); 95 | } 96 | 97 | Err(_) => break, 98 | } 99 | } 100 | 101 | Ok(true) 102 | } 103 | 104 | async fn recieve_event(&mut self) -> Result<(Option, Option, bool)> { 105 | let gateway_event = match self.shard.client.recieve_json().await { 106 | Ok(Some(event)) => GatewayEvent::deserialize(event) 107 | .map(Some) 108 | .map_err(From::from), 109 | 110 | Ok(None) => Ok(None), 111 | 112 | Err(Error::Tungstenite(TungsteniteError::Io(_))) => { 113 | return Ok((None, None, true)); 114 | } 115 | 116 | Err(why) => Err(why), 117 | }; 118 | 119 | let event = match gateway_event { 120 | Ok(Some(event)) => Ok(event), 121 | Ok(None) => return Ok((None, None, true)), 122 | Err(why) => Err(why), 123 | }; 124 | 125 | log::debug!("[Shard] GatewayEvent: {event:?}"); 126 | 127 | let action = match self.shard.handle_event(&event).await { 128 | Ok(Some(action)) => Some(action), 129 | Ok(None) => None, 130 | Err(why) => match why { 131 | Error::Gateway(GatewayError::InvalidAuthentication) => { 132 | self.manager_tx 133 | .send(ShardManagerMessage::InvalidAuthentication) 134 | .await 135 | .ok(); 136 | 137 | return Err(why); 138 | } 139 | 140 | _ => return Ok((None, None, true)), 141 | }, 142 | }; 143 | 144 | let event = match event { 145 | Ok(GatewayEvent::Dispatch(event)) => Some(event), 146 | _ => None, 147 | }; 148 | 149 | Ok((event, action, true)) 150 | } 151 | 152 | async fn action(&mut self, action: &ShardAction) -> Result<()> { 153 | match action { 154 | ShardAction::Reconnect(ReconnectType::Reidentify) => self.request_restart().await, 155 | ShardAction::Heartbeat(tag) => self.shard.heartbeat(tag.as_deref()).await, 156 | ShardAction::Identify => self.shard.identify().await.and(self.update_manager().await), 157 | ShardAction::Update => self.update_manager().await, 158 | } 159 | } 160 | 161 | async fn handle_rx_message(&mut self, message: InterMessage) -> bool { 162 | match message { 163 | InterMessage::Json(json) => self.shard.client.send_json(&json).await.is_ok(), 164 | InterMessage::Close => { 165 | self.shard.shutdown().await; 166 | 167 | false 168 | } 169 | } 170 | } 171 | 172 | async fn request_restart(&mut self) -> Result<()> { 173 | self.manager_tx 174 | .send(ShardManagerMessage::Restart) 175 | .await 176 | .ok(); 177 | 178 | Ok(()) 179 | } 180 | 181 | async fn update_manager(&mut self) -> Result<()> { 182 | log::debug!("[Shard] Latency: {:?}", self.shard.latency()); 183 | 184 | self.manager_tx 185 | .send(ShardManagerMessage::Update(ShardRunnerUpdate { 186 | latency: self.shard.latency(), 187 | stage: self.shard.stage(), 188 | })) 189 | .await 190 | .ok(); 191 | Ok(()) 192 | } 193 | 194 | pub fn get_tx(&self) -> UnboundedSender { 195 | self.runner_tx.clone() 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/shard/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | 4 | use async_tungstenite::tungstenite::protocol::CloseFrame; 5 | 6 | #[derive(Clone, Debug)] 7 | #[non_exhaustive] 8 | pub enum Error { 9 | Closed(Option>), 10 | ExpectedHello, 11 | HeartbeatFailed, 12 | InvalidAuthentication, 13 | InvalidHandshake, 14 | InvalidOpCode, 15 | ReconnectFailure, 16 | } 17 | 18 | impl fmt::Display for Error { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | match self { 21 | Self::Closed(frame) => { 22 | if let Some(CloseFrame { code, reason }) = frame { 23 | write!(f, "Connection closed with code {code}: {reason}") 24 | } else { 25 | write!(f, "Connection closed") 26 | } 27 | } 28 | Self::ExpectedHello => write!(f, "Expected a Hello"), 29 | Self::HeartbeatFailed => write!(f, "Failed sending a heartbeat"), 30 | Self::InvalidAuthentication => write!(f, "Sent invalid authentication"), 31 | Self::InvalidHandshake => write!(f, "Expected a valid Handshake"), 32 | Self::InvalidOpCode => write!(f, "Invalid OpCode"), 33 | Self::ReconnectFailure => write!(f, "Failed to Reconnect"), 34 | } 35 | } 36 | } 37 | 38 | impl StdError for Error {} 39 | 40 | // pub mod close_frames { 41 | // const UNKNOWN_ERROR: u16 = 4000; 42 | // const INVALID_AUTH: u16 = 4001; 43 | // const IDENTIFY_TIMEOUT: u16 = 4002; 44 | // const UNKNOWN_OPCODE: u16 = 4004; 45 | // const INVALID_PAYLOAD: u16 = 4005; 46 | // const BAD_ROUTE: u16 = 4006; 47 | // const OUT_OF_SYNC: u16 = 4007; 48 | // } 49 | -------------------------------------------------------------------------------- /src/shard/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod socket; 3 | pub mod types; 4 | 5 | use std::sync::Arc; 6 | use std::time::Duration; 7 | 8 | use async_tungstenite::tokio::connect_async_with_config; 9 | use async_tungstenite::tungstenite::error::Error as TungsteniteError; 10 | use async_tungstenite::tungstenite::protocol::{CloseFrame, WebSocketConfig}; 11 | use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; 12 | use futures::SinkExt; 13 | use tokio::sync::Mutex; 14 | use tokio::task::{spawn, JoinHandle}; 15 | use tokio::time::Instant; 16 | 17 | use self::error::Error as GatewayError; 18 | use self::socket::{WsStream, WsStreamExt}; 19 | use self::types::{close_codes, ConnectionStage, GatewayEvent, ReconnectType, ShardAction}; 20 | use crate::errors::{Error, Result}; 21 | use crate::leap::types::Event; 22 | 23 | #[cfg(feature = "zlib")] 24 | const ENCODING: &str = "none"; 25 | #[cfg(not(feature = "zlib"))] 26 | const ENCODING: &str = "zlib"; 27 | 28 | pub struct Shard { 29 | pub client: WsStream, 30 | heartbeat_reciever: UnboundedReceiver<()>, 31 | heartbeat_sender: UnboundedSender<()>, 32 | heartbeat_interval: Option>, 33 | heartbeat_instants: (Option, Option), 34 | last_heartbeat_acknowledged: bool, 35 | stage: ConnectionStage, 36 | pub token: Option, 37 | pub project: String, 38 | } 39 | 40 | impl Shard { 41 | pub async fn new( 42 | ws_url: Arc>, 43 | project: &str, 44 | token: Option<&str>, 45 | ) -> Result { 46 | let ws_url = ws_url.lock().await.clone(); 47 | let client = connect(&ws_url).await?; 48 | 49 | let heartbeat_instants = (None, None); 50 | let last_heartbeat_acknowledged = true; 51 | let stage = ConnectionStage::Handshake; 52 | 53 | let (heartbeat_sender, heartbeat_reciever) = unbounded::<()>(); 54 | 55 | Ok(Self { 56 | client, 57 | heartbeat_reciever, 58 | heartbeat_sender, 59 | heartbeat_interval: None, 60 | heartbeat_instants, 61 | last_heartbeat_acknowledged, 62 | stage, 63 | project: project.to_string(), 64 | token: token.map(std::string::ToString::to_string), 65 | }) 66 | } 67 | 68 | pub async fn heartbeat(&mut self, tag: Option<&str>) -> Result<()> { 69 | match self.client.send_heartbeat(tag).await { 70 | Ok(()) => { 71 | self.heartbeat_instants.0 = Some(Instant::now()); 72 | self.last_heartbeat_acknowledged = false; 73 | 74 | Ok(()) 75 | } 76 | 77 | Err(why) => { 78 | match why { 79 | Error::Tungstenite(TungsteniteError::Io(err)) => { 80 | if err.raw_os_error() != Some(32) { 81 | log::debug!("[Shard] Err heartbeating: {err:?}"); 82 | } 83 | } 84 | 85 | other => { 86 | log::warn!("[Shard] Other err w/ keepalive: {other:?}"); 87 | } 88 | } 89 | 90 | Err(Error::Gateway(GatewayError::HeartbeatFailed)) 91 | } 92 | } 93 | } 94 | 95 | pub async fn identify(&mut self) -> Result<()> { 96 | self.client 97 | .send_identify(&self.project, self.token.as_deref()) 98 | .await?; 99 | 100 | self.heartbeat_instants.0 = Some(Instant::now()); 101 | self.stage = ConnectionStage::Identifying; 102 | 103 | Ok(()) 104 | } 105 | 106 | pub async fn check_heartbeat(&mut self) -> bool { 107 | match self.heartbeat_reciever.try_next() { 108 | // continue to send a heartbeat if it was sent from the thread 109 | Ok(Some(_)) => {} 110 | // close since we failed to get a heartbeat from the thread 111 | Ok(None) => { 112 | log::warn!("[Shard] Err checking heartbeat: channel closed"); 113 | return false; 114 | } 115 | // no heartbeat was waiting, so continue 116 | Err(_) => return true, 117 | }; 118 | 119 | if !self.last_heartbeat_acknowledged { 120 | return false; 121 | } 122 | 123 | if let Err(why) = self.heartbeat(None).await { 124 | log::warn!("[Shard] Err heartbeating: {why:?}"); 125 | 126 | false 127 | } else { 128 | true 129 | } 130 | } 131 | 132 | pub(crate) async fn handle_event( 133 | &mut self, 134 | event: &Result, 135 | ) -> Result> { 136 | match event { 137 | Ok(GatewayEvent::Dispatch(ref event)) => Ok(self.handle_dispatch(event)), 138 | Ok(GatewayEvent::Heartbeat(tag)) => Ok(Self::handle_heartbeat_event(tag)), 139 | Ok(GatewayEvent::HeartbeatAck(..)) => { 140 | self.heartbeat_instants.1 = Some(Instant::now()); 141 | self.last_heartbeat_acknowledged = true; 142 | 143 | log::trace!("[Shard] Received heartbeat ack"); 144 | 145 | Ok(Some(ShardAction::Update)) 146 | } 147 | Ok(GatewayEvent::Hello(interval_time)) => { 148 | if interval_time > &0 { 149 | let mut cloned_sender = self.heartbeat_sender.clone(); 150 | let cloned_time = *interval_time; 151 | 152 | let heartbeat_interval = spawn(async move { 153 | let mut interval = 154 | tokio::time::interval(Duration::from_millis(cloned_time)); 155 | 156 | loop { 157 | interval.tick().await; 158 | 159 | if let Err(why) = cloned_sender.send(()).await { 160 | log::warn!("[Shard] Err sending heartbeat: {why:?}"); 161 | } 162 | } 163 | }); 164 | 165 | self.heartbeat_interval = Some(heartbeat_interval); 166 | } 167 | 168 | Ok(Some(if self.stage == ConnectionStage::Handshake { 169 | ShardAction::Identify 170 | } else { 171 | log::debug!("[Shard] Received late Hello; autoreconnecting"); 172 | 173 | ShardAction::Reconnect(self.reconnection_type()) 174 | })) 175 | } 176 | 177 | Err(Error::Gateway(GatewayError::Closed(ref data))) => self.handle_closed(data), 178 | Err(Error::Tungstenite(ref why)) => { 179 | log::warn!("[Shard] Websocket error: {why:?}"); 180 | log::info!("[Shard] Will attempt to auto-reconnect",); 181 | 182 | Ok(Some(ShardAction::Reconnect(self.reconnection_type()))) 183 | } 184 | 185 | Err(ref why) => { 186 | log::warn!("[Shard] Unhandled error: {why:?}"); 187 | 188 | Ok(None) 189 | } 190 | } 191 | } 192 | 193 | fn handle_heartbeat_event(tag: &Option) -> Option { 194 | if let Some(tag) = tag { 195 | return Some(ShardAction::Heartbeat(Some(tag.clone()))); 196 | } 197 | 198 | None 199 | } 200 | 201 | fn handle_dispatch(&mut self, event: &Event) -> Option { 202 | if matches!(event, Event::Init(_)) { 203 | self.stage = ConnectionStage::Connected; 204 | } 205 | 206 | None 207 | } 208 | 209 | fn handle_closed(&mut self, data: &Option>) -> Result> { 210 | self.stage = ConnectionStage::Disconnected; 211 | 212 | let num = data.as_ref().map(|d| d.code.into()); 213 | let clean = num == Some(1000); 214 | 215 | match num { 216 | Some(close_codes::AUTHENTICATION_FAILED) => { 217 | log::debug!("[Shard] Authentication failed; closing connection"); 218 | 219 | return Err(Error::Gateway(GatewayError::InvalidAuthentication)); 220 | } 221 | 222 | Some(other) if !clean => { 223 | log::warn!("[Shard] Received unexpected close code: {other}"); 224 | } 225 | 226 | _ => {} 227 | } 228 | 229 | Ok(Some(ShardAction::Reconnect(ReconnectType::Reidentify))) 230 | } 231 | 232 | pub fn reconnection_type(&self) -> ReconnectType { 233 | // resumes are not supported yet 234 | ReconnectType::Reidentify 235 | } 236 | 237 | pub fn stage(&self) -> ConnectionStage { 238 | self.stage.clone() 239 | } 240 | 241 | pub fn latency(&self) -> Option { 242 | match self.heartbeat_instants { 243 | (Some(start), Some(end)) => Some(end.duration_since(start)), 244 | _ => None, 245 | } 246 | } 247 | 248 | pub async fn shutdown(&mut self) { 249 | self.client.close(None).await.ok(); 250 | } 251 | } 252 | 253 | async fn connect(base_url: &str) -> Result { 254 | let url = format!("{base_url}?encoding=json&compression={ENCODING}"); 255 | 256 | let config = WebSocketConfig { 257 | max_message_size: None, 258 | max_frame_size: None, 259 | accept_unmasked_frames: false, 260 | ..Default::default() 261 | }; 262 | 263 | let (stream, _) = connect_async_with_config(url, Some(config)).await?; 264 | 265 | Ok(stream) 266 | } 267 | -------------------------------------------------------------------------------- /src/shard/socket.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "zlib")] 2 | use std::io::Cursor; 3 | 4 | #[cfg(feature = "zlib")] 5 | use async_compression::tokio::bufread::ZlibDecoder; 6 | use async_trait::async_trait; 7 | use async_tungstenite::tokio::ConnectStream; 8 | use async_tungstenite::tungstenite::Message; 9 | use async_tungstenite::WebSocketStream; 10 | use futures::{SinkExt, StreamExt}; 11 | use serde_json::{json, to_string, Value}; 12 | use tokio::io::AsyncReadExt; 13 | use tokio::time::{timeout, Duration}; 14 | 15 | use super::error::Error as GatewayError; 16 | use crate::errors::{Error, Result}; 17 | use crate::shard::types::OpCode; 18 | 19 | pub type WsStream = WebSocketStream; 20 | 21 | #[async_trait] 22 | pub trait RecieverExt { 23 | async fn recieve_json(&mut self) -> Result>; 24 | } 25 | 26 | #[async_trait] 27 | pub trait SenderExt { 28 | async fn send_json(&mut self, value: &Value) -> Result<()>; 29 | } 30 | 31 | #[async_trait] 32 | impl RecieverExt for WsStream { 33 | async fn recieve_json(&mut self) -> Result> { 34 | const TIMEOUT: Duration = Duration::from_millis(0); 35 | 36 | match timeout(TIMEOUT, self.next()).await { 37 | Ok(Some(Ok(message))) => convert_message(message).await, 38 | Ok(Some(Err(error))) => return Err(error.into()), 39 | Ok(None) | Err(_) => return Ok(None), 40 | } 41 | } 42 | } 43 | 44 | async fn convert_message(message: Message) -> Result> { 45 | let bytes = match message { 46 | #[cfg(feature = "zlib")] 47 | Message::Binary(binary) => { 48 | let mut compressed = ZlibDecoder::new(Cursor::new(binary)); 49 | let mut buffer = vec![]; 50 | 51 | compressed.read_to_end(&mut buffer).await?; 52 | 53 | buffer 54 | } 55 | 56 | Message::Text(text) => text.into_bytes(), 57 | 58 | Message::Close(frame) => { 59 | return Err(Error::Gateway(GatewayError::Closed(frame))); 60 | } 61 | 62 | _ => return Ok(None), 63 | }; 64 | 65 | log::debug!( 66 | "[Shard] Received raw data: {}", 67 | String::from_utf8_lossy(&bytes) 68 | ); 69 | 70 | Ok(Some(serde_json::from_slice(&bytes)?)) 71 | } 72 | 73 | #[async_trait] 74 | impl SenderExt for WsStream { 75 | async fn send_json(&mut self, value: &Value) -> Result<()> { 76 | log::debug!("[Shard] Sending: {value}"); 77 | 78 | Ok(to_string(value) 79 | .map(Message::Text) 80 | .map_err(Error::from) 81 | .map(|m| self.send(m))? 82 | .await?) 83 | } 84 | } 85 | 86 | #[async_trait] 87 | pub trait WsStreamExt { 88 | async fn send_heartbeat(&mut self, tag: Option<&str>) -> Result<()>; 89 | async fn send_identify(&mut self, project: &str, token: Option<&str>) -> Result<()>; 90 | } 91 | 92 | #[async_trait] 93 | impl WsStreamExt for WsStream { 94 | async fn send_heartbeat(&mut self, tag: Option<&str>) -> Result<()> { 95 | let payload = if let Some(tag) = tag { 96 | json!({ 97 | "op": OpCode::Heartbeat.number(), 98 | "d": { 99 | "tag": tag, 100 | }, 101 | }) 102 | } else { 103 | json!({ 104 | "op": OpCode::Heartbeat.number(), 105 | }) 106 | }; 107 | 108 | self.send_json(&payload).await 109 | } 110 | 111 | async fn send_identify(&mut self, project: &str, token: Option<&str>) -> Result<()> { 112 | let payload = if let Some(token) = token { 113 | json!({ 114 | "op": OpCode::Identify.number(), 115 | "d": { 116 | "project_id": project, 117 | "token": token, 118 | }, 119 | }) 120 | } else { 121 | json!({ 122 | "op": OpCode::Identify.number(), 123 | "d": { 124 | "project_id": project, 125 | }, 126 | }) 127 | }; 128 | 129 | self.send_json(&payload).await 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/shard/types.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use serde::de::Error as SerdeError; 4 | use serde::{Deserialize, Serialize}; 5 | use serde_json::Value; 6 | use serde_repr::Deserialize_repr; 7 | 8 | use crate::leap::types::Event; 9 | 10 | #[derive(Clone, Debug)] 11 | pub enum InterMessage { 12 | Json(Value), 13 | Close, 14 | } 15 | 16 | #[derive(Debug, Clone, PartialEq, Eq)] 17 | #[non_exhaustive] 18 | pub enum ConnectionStage { 19 | Connected, 20 | Handshake, 21 | Identifying, 22 | Disconnected, 23 | } 24 | 25 | impl ConnectionStage { 26 | pub fn is_connecting(&self) -> bool { 27 | matches!(self, Self::Handshake | Self::Identifying) 28 | } 29 | 30 | pub fn is_connected(&self) -> bool { 31 | matches!(self, Self::Connected) 32 | } 33 | } 34 | 35 | impl fmt::Display for ConnectionStage { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | match self { 38 | Self::Connected => write!(f, "connected"), 39 | Self::Handshake => write!(f, "handshaking"), 40 | Self::Identifying => write!(f, "identifying"), 41 | Self::Disconnected => write!(f, "disconnected"), 42 | } 43 | } 44 | } 45 | 46 | #[derive(Debug)] 47 | pub enum ShardAction { 48 | Heartbeat(Option), 49 | Identify, 50 | Reconnect(ReconnectType), 51 | Update, 52 | } 53 | 54 | #[derive(Debug)] 55 | pub enum ReconnectType { 56 | /// send IDENTIFY. 57 | Reidentify, 58 | } 59 | 60 | #[derive(Debug, Clone, Copy, Deserialize_repr)] 61 | #[repr(u8)] 62 | pub enum OpCode { 63 | Dispatch = 0, 64 | Hello = 1, 65 | Identify = 2, 66 | Heartbeat = 3, 67 | HeartbeatAck = 4, 68 | Unknown = !0, 69 | } 70 | 71 | impl OpCode { 72 | pub fn number(self) -> u8 { 73 | self as u8 74 | } 75 | } 76 | 77 | #[derive(Debug, Clone, Serialize)] 78 | #[serde(untagged)] 79 | pub enum GatewayEvent { 80 | /// server hello 81 | Hello(u64), 82 | /// general dispatch event 83 | Dispatch(Event), 84 | /// heartbeats with optional tag 85 | Heartbeat(Option), 86 | /// heartbeat ack with optional tag and latency 87 | HeartbeatAck(Option, Option), 88 | } 89 | 90 | impl<'de> Deserialize<'de> for GatewayEvent { 91 | fn deserialize(deserializer: D) -> Result 92 | where 93 | D: serde::Deserializer<'de>, 94 | { 95 | let mut gw_event = serde_json::Map::deserialize(deserializer)?; 96 | 97 | let op_code = gw_event 98 | .remove("op") 99 | .ok_or_else(|| SerdeError::custom("missing op code")) 100 | .and_then(OpCode::deserialize) 101 | .map_err(SerdeError::custom)?; 102 | 103 | let event = match op_code { 104 | OpCode::Hello => { 105 | let d = gw_event 106 | .remove("d") 107 | .ok_or_else(|| SerdeError::custom("missing d"))?; 108 | 109 | let heartbeat_interval = d 110 | .get("heartbeat_interval") 111 | .and_then(serde_json::Value::as_u64) 112 | .ok_or_else(|| SerdeError::custom("missing heartbeat_interval"))?; 113 | 114 | GatewayEvent::Hello(heartbeat_interval) 115 | } 116 | 117 | OpCode::Dispatch => { 118 | let event = gw_event 119 | .remove("d") 120 | .ok_or_else(|| SerdeError::custom("missing event data")) 121 | .and_then(Event::deserialize) 122 | .map_err(SerdeError::custom)?; 123 | 124 | GatewayEvent::Dispatch(event) 125 | } 126 | 127 | OpCode::Heartbeat => { 128 | // heartbeats sent by the gateway can have a tag 129 | let d = gw_event 130 | .remove("d") 131 | .map(serde_json::Map::deserialize) 132 | .transpose() 133 | .map_err(SerdeError::custom)?; 134 | 135 | let tag = d 136 | .and_then(|mut d| d.remove("tag")) 137 | .map(|tag| tag.as_str().unwrap().to_string()); 138 | 139 | GatewayEvent::Heartbeat(tag) 140 | } 141 | 142 | OpCode::HeartbeatAck => { 143 | // heartbeat acks sent by the gateway can have a tag and latency 144 | let d = gw_event 145 | .remove("d") 146 | .map(serde_json::Map::deserialize) 147 | .transpose() 148 | .map_err(SerdeError::custom)?; 149 | 150 | let tag = d 151 | .clone() 152 | .and_then(|mut d| d.remove("tag")) 153 | .map(|tag| tag.as_str().unwrap().to_string()); 154 | 155 | let latency = d 156 | .and_then(|mut d| d.remove("latency")) 157 | .and_then(|latency| latency.as_u64()); 158 | GatewayEvent::HeartbeatAck(tag, latency) 159 | } 160 | 161 | _ => return Err(SerdeError::custom("invalid opcode")), 162 | }; 163 | 164 | Ok(event) 165 | } 166 | } 167 | 168 | pub mod close_codes { 169 | pub const AUTHENTICATION_FAILED: u16 = 4001; 170 | } 171 | --------------------------------------------------------------------------------