use webassembly with the server acting only as an intermediate

This commit is contained in:
Sebastian Hugentobler 2023-03-09 08:35:24 +01:00
parent 9aa1130c35
commit 7d0ef62c42
Signed by: shu
GPG Key ID: BB32CF3CA052C2F0
29 changed files with 850 additions and 325 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
*~
.DS_Store
*.swp
/target
**/target
**/Cargo.lock
woweb/assets/dist_text*

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/poc.iml" filepath="$PROJECT_DIR$/.idea/poc.iml" />
</modules>
</component>
</project>

16
.idea/poc.iml Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/dist_list/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dist_text/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dist_text_js/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/woweb/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/dist_list/target" />
<excludeFolder url="file://$MODULE_DIR$/woweb/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -4,7 +4,7 @@ pipeline:
when:
event: tag
settings:
repo: docker.io/thallian/woweb-poc
repo: docker.io/thallian/woweb
tags: latest,${CI_COMMIT_SHA:0:8},${CI_COMMIT_TAG=pre}
dockerfile: Dockerfile
username:

422
Cargo.lock generated
View File

@ -3,10 +3,19 @@
version = 3
[[package]]
name = "async-trait"
version = "0.1.64"
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "async-trait"
version = "0.1.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
dependencies = [
"proc-macro2",
"quote",
@ -21,9 +30,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.7"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
checksum = "8582122b8edba2af43eaf6b80dbfd33f421b5a0eb3a3113d21bc096ac5b44faf"
dependencies = [
"async-trait",
"axum-core",
@ -51,16 +60,16 @@ dependencies = [
"tokio",
"tokio-tungstenite",
"tower",
"tower-http",
"tower-http 0.4.0",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34"
checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e"
dependencies = [
"async-trait",
"bytes",
@ -87,7 +96,7 @@ dependencies = [
"pin-project-lite",
"tokio",
"tower",
"tower-http",
"tower-http 0.3.5",
"tower-layer",
"tower-service",
]
@ -119,6 +128,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -146,6 +161,24 @@ dependencies = [
"libc",
]
[[package]]
name = "crdts"
version = "7.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e30e1170960eddf5392c448ff5b190a49cfcb28d6c79f789f2b4e30b571f8f8"
dependencies = [
"num",
"quickcheck",
"serde",
"tiny-keccak",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -166,6 +199,33 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "dist_text"
version = "0.1.0"
dependencies = [
"crdts",
]
[[package]]
name = "dist_text_js"
version = "0.1.0"
dependencies = [
"dist_text",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"log",
"regex",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -280,6 +340,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.8"
@ -288,7 +359,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@ -404,6 +475,15 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -480,8 +560,8 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.45.0",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
[[package]]
@ -494,6 +574,85 @@ dependencies = [
"winapi",
]
[[package]]
name = "num"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-complex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5"
dependencies = [
"num-traits",
"serde",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
@ -536,7 +695,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.45.0",
"windows-sys",
]
[[package]]
@ -592,6 +751,18 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quickcheck"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f"
dependencies = [
"env_logger",
"log",
"rand 0.7.3",
"rand_core 0.5.1",
]
[[package]]
name = "quote"
version = "1.0.23"
@ -601,6 +772,19 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -608,8 +792,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
@ -619,7 +813,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
@ -628,7 +831,16 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
"getrandom 0.2.8",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
@ -646,6 +858,8 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
@ -666,15 +880,15 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustversion"
version = "1.0.11"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "ryu"
version = "1.0.12"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "scopeguard"
@ -684,18 +898,29 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
version = "1.0.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
name = "serde-wasm-bindgen"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f"
dependencies = [
"proc-macro2",
"quote",
@ -704,9 +929,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.93"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",
@ -715,9 +940,9 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341"
checksum = "db0969fff533976baadd92e08b1d102c5a3d8a8049eadfd69d4d1e3c5b2ed189"
dependencies = [
"serde",
]
@ -780,9 +1005,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.7"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
@ -790,9 +1015,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.107"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
@ -807,18 +1032,18 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "thiserror"
version = "1.0.38"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
dependencies = [
"proc-macro2",
"quote",
@ -835,6 +1060,15 @@ dependencies = [
"once_cell",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -852,9 +1086,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg",
"bytes",
@ -867,7 +1101,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.42.0",
"windows-sys",
]
[[package]]
@ -942,12 +1176,30 @@ dependencies = [
"pin-project-lite",
"tokio",
"tokio-util",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-http"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
@ -1041,7 +1293,7 @@ dependencies = [
"http",
"httparse",
"log",
"rand",
"rand 0.8.5",
"sha1",
"thiserror",
"url",
@ -1071,9 +1323,9 @@ checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-ident"
version = "1.0.6"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-normalization"
@ -1129,12 +1381,72 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "winapi"
version = "0.3.9"
@ -1157,21 +1469,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
@ -1239,17 +1536,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "woweb-poc"
name = "woweb"
version = "0.1.0"
dependencies = [
"axum",
"axum-extra",
"dist_text",
"futures",
"headers",
"serde",
"serde_json",
"tokio",
"tower-http",
"tower-http 0.3.5",
"tracing",
"tracing-subscriber",
"unicode-segmentation",

View File

@ -1,17 +1,7 @@
[package]
name = "woweb-poc"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
axum = { version = "0.6.7", features = ["ws", "headers"] }
axum-extra = { version = "0.5.0", features = ["spa"] }
futures = "0.3.26"
headers = "0.3.8"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
tokio = { version = "1.25.0", features = ["full"] }
tower-http = { version = "0.3.5", features = ["trace"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
unicode-segmentation = "1.10.1"
members = [
"dist_text",
"dist_text_js",
"woweb"
]

View File

@ -7,20 +7,20 @@ ADD . /src
WORKDIR /src
RUN cargo build --release
RUN strip target/release/woweb-poc
RUN strip target/release/woweb
RUN adduser -D woweb
RUN sed -i 's|ws://localhost:3000/ws|wss://woweb.vanwa.ch/ws|' assets/index.js
RUN sed -i 's|ws://localhost:3000/ws|wss://woweb.vanwa.ch/ws|' woweb/assets/index.js
FROM scratch
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /src/target/release/woweb-poc "/opt/woweb-poc"
COPY --from=builder /src/assets "/opt/assets"
COPY --from=builder /src/target/release/woweb "/opt/woweb"
COPY --from=builder /src/woweb/assets "/opt/assets"
EXPOSE 3000
WORKDIR /opt
USER woweb
CMD ["/opt/woweb-poc", "0.0.0.0:3000"]
CMD ["/opt/woweb", "0.0.0.0:3000"]

View File

@ -1,87 +0,0 @@
const areaId = "doctext";
const validEvents = [
"insertText",
"insertFromPaste",
"insertLineBreak",
"insertCompositionText",
"deleteContentBackward",
"deleteContentForward",
];
let selectionStart = 0;
let selectionEnd = 0;
let area;
let ws;
const uuid = self.crypto.randomUUID();
const wsUrl = "ws://localhost:3000/ws";
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
function setup() {
area = document.querySelector(`#${areaId}`);
ws = new WebSocket(wsUrl);
setupUi();
setupWs();
}
function setupUi() {
document.addEventListener("selectionchange", onSelectionChange, false);
area.addEventListener("beforeinput", onInput, false);
}
function setupWs() {
ws.onclose = function (e) {
console.log(e);
setTimeout(() => {
ws = new WebSocket(wsUrl);
}, 2000);
};
ws.onmessage = function (e) {
let payload = JSON.parse(e.data);
if (payload.client !== uuid) {
console.log(payload.doc);
area.value = payload.doc;
}
};
}
function onSelectionChange() {
const activeElement = document.activeElement;
if (activeElement && activeElement.id === areaId) {
console.debug("onSelectionChange", area);
selectionStart = area.selectionStart;
selectionEnd = area.selectionEnd;
}
}
function onInput(event) {
if (!validEvents.includes(event.inputType)) return;
const payload = {
client: uuid,
action: event.inputType,
data: event.data,
start: selectionStart,
end: selectionEnd,
};
ws.send(JSON.stringify(payload));
console.log(selectionStart, selectionEnd);
// workaround for differences between firefox and chrome
// chrome does not fire selectionchange events on backspace/delete events,
// while firefox does
if (event.inputType === "deleteContentBackward") {
selectionStart = area.selectionStart - 1;
selectionEnd = area.selectionEnd - 1;
}
}
setup();

7
dist_text/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "dist_text"
version = "0.1.0"
edition = "2021"
[dependencies]
crdts = "7.3.0"

3
dist_text/src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod text;
pub use crdts;

180
dist_text/src/text.rs Normal file
View File

@ -0,0 +1,180 @@
use std::fmt::{Display, Formatter};
use crdts::{CmRDT, List};
use crdts::list::Op;
#[derive(Clone)]
pub struct Text {
inner: List<u16, String>,
}
impl Default for Text {
fn default() -> Self {
Self {
inner: List::new()
}
}
}
impl Display for Text {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str_bytes: Vec<u16> = self.inner.to_owned().read_into();
let str_repr = String::from_utf16_lossy(&str_bytes);
write!(f, "{}", str_repr)
}
}
impl Text {
pub fn apply_ops(&mut self, ops: Vec<Op<u16, String>>) {
ops.iter().for_each(move |op| self.inner.apply(op.to_owned()));
}
pub fn insert_linebreak(&mut self, start: usize, end: usize, src: &str) -> Vec<Op<u16, String>> {
self.insert(start, end, "\n", src)
}
pub fn insert(&mut self, start: usize, end: usize, data: &str, src: &str) -> Vec<Op<u16, String>> {
let mut ops: Vec<Op<u16, String>> = Vec::new();
if start < end {
let mut delete_ops = self.delete(start, end, src);
ops.append(&mut delete_ops);
}
let data: Vec<u16> = data.encode_utf16().collect();
let mut insert_idx = start;
for byte in data.iter() {
let op = self.inner.insert_index(insert_idx, *byte, src.to_owned());
self.inner.apply(op.to_owned());
insert_idx += 1;
ops.push(op);
}
ops
}
pub fn delete_backward(&mut self, start: usize, end: usize, src: &str) -> Vec<Op<u16, String>> {
if (start == 0 && start == end) || end > self.inner.len() { return Vec::new(); }
let (end, start) = if start == end { (start, start - 1) } else { (end, start) };
self.delete(start, end, src)
}
pub fn delete_forward(&mut self, start: usize, end: usize, src: &str) -> Vec<Op<u16, String>> {
if start >= self.inner.len() { return Vec::new(); }
let end = if start == end { start + 1 } else { end };
self.delete(start, end, src)
}
pub fn delete(&mut self, start: usize, end: usize, src: &str) -> Vec<Op<u16, String>> {
let mut start_idx = start;
let mut end_idx = end;
while let Some(byte) = self.inner.position(start_idx) {
if !matches!(byte, 0xDC00..=0xDFFF) {
break;
}
start_idx -= 1;
}
if let Some(end_byte) = self.inner.position(end_idx - 1) {
if end_idx < self.inner.len() && end_byte > &0xD800 && end_byte < &0xDC00 {
end_idx += 1;
}
}
let mut ops: Vec<Op<u16, String>> = Vec::new();
for _ in start_idx..end_idx {
if let Some(op) = self.inner.delete_index(start_idx, src.to_owned()) {
self.inner.apply(op.to_owned());
ops.push(op);
}
}
ops
}
}
#[cfg(test)]
mod tests {
use crate::text::Text;
#[test]
fn insert_delete() {
let mut t = Text::new();
t.insert(0, 0, "Hello", "A");
t.insert(5, 5, "world!", "A");
t.insert(5, 5, ", ", "B");
assert_eq!(t.to_string(), "Hello, world!");
t.insert(7, 12, "🌍", "A");
assert_eq!(t.to_string(), "Hello, 🌍!");
t.delete(8, 9, "B");
t.insert(7, 7, "🚀", "C");
assert_eq!(t.to_string(), "Hello, 🚀!");
}
#[test]
fn backspace() {
let mut t = Text::new();
t.insert(0, 0, "Hello", "A");
t.delete_backward(5, 5, "A");
assert_eq!(t.to_string(), "Hell");
t.delete_backward(0, 4, "A");
assert_eq!(t.to_string(), "");
}
#[test]
fn delete() {
let mut t = Text::new();
t.insert(0, 0, "Hello", "A");
t.delete_forward(4, 4, "A");
assert_eq!(t.to_string(), "Hell");
t.delete_forward(0, 4, "A");
assert_eq!(t.to_string(), "");
}
#[test]
fn linebreak() {
let mut t = Text::new();
t.insert(0, 0, "Hello, world!", "A");
t.insert_linebreak(6, 6, "A");
assert_eq!(t.to_string(), "Hello,\n world!");
t.delete_backward(7, 7, "B");
t.insert_linebreak(6, 7, "A");
assert_eq!(t.to_string(), "Hello,\nworld!");
}
#[test]
fn apply_ops() {
let mut t_a = Text::new();
let ops_a = t_a.insert(0, 0, "Hello, world!", "A");
let mut t_b = Text::new();
t_b.apply_ops(ops_a);
assert_eq!(t_b.to_string(), "Hello, world!");
let ops_a = t_a.insert(7,7, "distributed ", "A");
t_b.apply_ops(ops_a);
let ops_b = t_b.insert(7,7, "cruel ", "B");
t_a.apply_ops(ops_b);
assert_eq!(t_a.to_string(), "Hello, cruel distributed world!");
assert_eq!(t_b.to_string(), "Hello, cruel distributed world!");
}
}

13
dist_text_js/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "dist_text_js"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
dist_text = { path = "../dist_text" }
serde = { version = "1.0.153", features = ["derive"] }
serde-wasm-bindgen = "0.5.0"
wasm-bindgen = "0.2.84"

38
dist_text_js/src/lib.rs Normal file
View File

@ -0,0 +1,38 @@
use wasm_bindgen::prelude::*;
use dist_text::crdts::list::Op;
use dist_text::text::Text;
#[wasm_bindgen(getter_with_clone)]
pub struct State {
pub text: String,
#[wasm_bindgen(skip)]
pub inner: Text,
}
#[wasm_bindgen]
impl State {
// wasm does not support trait impls yet, hence the default implementation here
pub fn default() -> Self {
Self { text: String::default(), inner: Text::default() }
}
pub fn apply(&mut self, ops: JsValue) -> String {
let ops: Vec<Op<u16, String>> = serde_wasm_bindgen::from_value(ops).unwrap_or(Vec::new());
self.inner.apply_ops(ops);
self.inner.to_string()
}
pub fn execute(&mut self, action: &str, start: usize, end: usize, data: &str, src: &str) -> JsValue {
let ops = match action {
"insertText" | "insertFromPaste" => self.inner.insert(start, end, data, src),
"insertLineBreak" => self.inner.insert_linebreak(start, end, src),
"deleteContentBackward" => self.inner.delete_backward(start, end, src),
"deleteContentForward" => self.inner.delete_forward(start, end, src),
&_ => Vec::default()
};
serde_wasm_bindgen::to_value(&ops).unwrap_or_default()
}
}

View File

@ -1,48 +0,0 @@
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::actions::delete::{DeleteContentBackward, DeleteContentForward};
use crate::actions::insert::{InsertText, InsertFromPaste, InsertLineBreak};
use crate::actions::ActionRunner;
use crate::AppState;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum ActionType {
InsertText,
InsertFromPaste,
InsertLineBreak,
DeleteContentBackward,
DeleteContentForward,
}
#[derive(Serialize, Deserialize)]
pub(crate) struct Action {
pub(crate) client: String,
data: Option<String>,
action: ActionType,
start: usize,
end: usize,
}
impl Action {
pub(crate) async fn execute(&self, state: Arc<AppState>) {
{
let mut doc = state.doc.write().await;
let action: Box<dyn ActionRunner> = match self.action {
ActionType::InsertText => Box::new(InsertText),
ActionType::InsertFromPaste => Box::new(InsertFromPaste),
ActionType::InsertLineBreak => Box::new(InsertLineBreak),
ActionType::DeleteContentBackward => Box::new(DeleteContentBackward),
ActionType::DeleteContentForward => Box::new(DeleteContentForward),
};
let data = match self.data.to_owned() {
None => Vec::new(),
Some(data) => data.encode_utf16().collect()
};
*doc = action.run(self.start, self.end, data, doc.to_owned());
}
}
}

View File

@ -1,45 +0,0 @@
use super::ActionRunner;
pub(crate) struct DeleteContentBackward;
pub(crate) struct DeleteContentForward;
fn delete(start: usize, end: usize, mut doc: Vec<u16>) -> Vec<u16> {
if start > doc.len() + 1 {
return doc;
}
let mut start_idx = start;
let mut end_idx = end;
unsafe {
while start_idx > 0 && matches!(doc.get_unchecked(start_idx), 0xDC00..=0xDFFF) {
start_idx -= 1;
}
let end_byte = doc.get_unchecked(end_idx - 1);
if end_idx < doc.len() && end_byte > &0xD800 && end_byte < &0xDC00 {
end_idx += 1;
}
}
doc.drain(start_idx..end_idx);
doc
}
impl ActionRunner for DeleteContentBackward {
fn run(&self, start: usize, end: usize, _data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
if (start == 0 && start == end ) || end > doc.len() { return doc; }
let (end, start) = if start == end { (start, start - 1) } else { (end, start) };
delete(start, end, doc)
}
}
impl ActionRunner for DeleteContentForward {
fn run(&self, start: usize, end: usize, _data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
if start >= doc.len() { return doc; }
let end = if start == end { start + 1 } else { end };
delete(start, end, doc)
}
}

View File

@ -1,38 +0,0 @@
use super::ActionRunner;
pub(crate) struct InsertText;
pub(crate) struct InsertFromPaste;
pub(crate) struct InsertLineBreak;
const NL_BYTES: [u16; 1] = [10];
fn insert(start: usize, end: usize, data: Vec<u16>, mut doc: Vec<u16>) -> Vec<u16> {
if doc.len() < start || data.is_empty() {
return doc;
}
if start < end {
doc.drain(start..end);
}
doc.splice(start..start, data);
doc
}
impl ActionRunner for InsertText {
fn run(&self, start: usize, end: usize, data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
insert(start, end, data, doc)
}
}
impl ActionRunner for InsertFromPaste {
fn run(&self, start: usize, end: usize, data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
insert(start, end, data, doc)
}
}
impl ActionRunner for InsertLineBreak {
fn run(&self, start: usize, _end: usize, _data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
insert(start, start, Vec::from(NL_BYTES), doc)
}
}

View File

@ -1,6 +0,0 @@
pub(crate) mod delete;
pub(crate) mod insert;
pub(crate) trait ActionRunner {
fn run(&self, start: usize, end: usize, data: Vec<u16>, doc: Vec<u16>) -> Vec<u16>;
}

8
woweb/.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
woweb/.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/woweb-poc.iml" filepath="$PROJECT_DIR$/.idea/woweb-poc.iml" />
</modules>
</component>
</project>

6
woweb/.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

11
woweb/.idea/woweb-poc.iml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

19
woweb/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "woweb"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
dist_text = { path = "../dist_text" }
axum = { version = "0.6.7", features = ["ws", "headers"] }
axum-extra = { version = "0.5.0", features = ["spa"] }
futures = "0.3.26"
headers = "0.3.8"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
tokio = { version = "1.25.0", features = ["full"] }
tower-http = { version = "0.3.5", features = ["trace"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
unicode-segmentation = "1.10.1"

View File

@ -6,6 +6,6 @@
</head>
<body>
<textarea id="doctext" name="story" rows="5" cols="33">Things just happen, what the hell.</textarea>
<script src="/index.js"></script>
<script type="module" src="/index.js"></script>
</body>
</html>

90
woweb/assets/index.js Normal file
View File

@ -0,0 +1,90 @@
import init, { State } from "/dist_text_js.js";
await init();
const areaId = "doctext";
const validEvents = [
"insertText",
"insertFromPaste",
"insertLineBreak",
"insertCompositionText",
"deleteContentBackward",
"deleteContentForward",
];
let selectionStart = 0;
let selectionEnd = 0;
let area;
let ws;
const uuid = self.crypto.randomUUID();
const wsUrl = "ws://localhost:3000/ws";
const state = State.default();
function setup() {
area = document.querySelector(`#${areaId}`);
ws = new WebSocket(wsUrl);
setupUi();
setupWs();
}
function setupUi() {
document.addEventListener("selectionchange", onSelectionChange, false);
area.addEventListener("beforeinput", onInput, false);
}
function setupWs() {
ws.onclose = function (e) {
console.log(e);
setTimeout(() => {
ws = new WebSocket(wsUrl);
}, 2000);
};
ws.onmessage = function (e) {
let payload = JSON.parse(e.data);
if (payload.client !== uuid) {
const newText = state.apply(payload.ops);
state.text = payload.ops.length > 0 ? newText : payload.doc;
area.value = state.text;
}
};
}
function onSelectionChange() {
const activeElement = document.activeElement;
if (activeElement && activeElement.id === areaId) {
selectionStart = area.selectionStart;
selectionEnd = area.selectionEnd;
}
}
function onInput(event) {
const data = event.data ? event.data : "";
let ops = state.execute(event.inputType, selectionStart, selectionEnd, data, uuid);
if (!validEvents.includes(event.inputType)) return;
const payload = {
client: uuid,
ops
};
ws.send(JSON.stringify(payload));
console.log(selectionStart, selectionEnd);
// workaround for differences between firefox and chrome
// chrome does not fire selectionchange events on backspace/delete events,
// while firefox does
if (event.inputType === "deleteContentBackward") {
selectionStart = area.selectionStart - 1;
selectionEnd = area.selectionEnd - 1;
}
}
setup();

29
woweb/build.rs Normal file
View File

@ -0,0 +1,29 @@
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;
fn main() {
let woweb_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let woweb_path = Path::new(&woweb_dir);
let woweb_asset_path = woweb_path.join("assets");
let dist_text_path = woweb_path.parent().unwrap().join("dist_text_js");
let dist_text_pkg_path = dist_text_path.join("pkg");
Command::new("wasm-pack")
.args(&["build", "--target", "web", "--release", "--no-typescript"])
.current_dir(dist_text_path)
.status()
.unwrap();
let js_path_src = dist_text_pkg_path.join("dist_text_js.js");
let js_path_dest = woweb_asset_path.join("dist_text_js.js");
let wasm_path_src = dist_text_pkg_path.join("dist_text_js_bg.wasm");
let wasm_path_dest = woweb_asset_path.join("dist_text_js_bg.wasm");
let _ = fs::copy(js_path_src, js_path_dest);
let _ = fs::copy(wasm_path_src, wasm_path_dest);
println!("cargo:rerun-if-changed=../dist_text_js/**/*.rs");
}

View File

@ -7,13 +7,12 @@ use std::sync::Arc;
use tokio::sync::{broadcast, RwLock};
use tower_http::trace::TraceLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use dist_text::text::Text;
mod action;
mod actions;
mod ws;
struct AppState {
doc: RwLock<Vec<u16>>,
doc: RwLock<Text>,
tx: broadcast::Sender<String>,
}
@ -29,7 +28,7 @@ async fn main() {
let (tx, _rx) = broadcast::channel(100);
let app_state = Arc::new(AppState {
doc: RwLock::new(Vec::new()),
doc: RwLock::new(Text::default()),
tx,
});

View File

@ -10,13 +10,21 @@ use axum::{
};
use futures::{sink::SinkExt, stream::StreamExt};
use dist_text::crdts::list::Op;
use crate::{action::Action, AppState};
use crate::AppState;
#[derive(Serialize, Deserialize)]
struct NewState {
client: String,
doc: String,
ops: Vec<Op<u16, String>>,
}
#[derive(Clone, Serialize, Deserialize)]
pub(crate) struct Ops {
pub(crate) client: String,
ops: Vec<Op<u16, String>>,
}
pub(crate) async fn route(
@ -31,10 +39,11 @@ async fn handle_socket(stream: WebSocket, state: Arc<AppState>) {
{
let doc = state.doc.read().await;
let doc: String = String::from_utf16_lossy(&doc);
let doc: String = doc.to_string();
let new_state = NewState {
client: String::from("0000"),
doc: doc.to_string(),
ops: Vec::new(),
};
let payload = serde_json::to_string(&new_state).unwrap();
let _ = sender.send(Message::Text(payload)).await;
@ -54,17 +63,17 @@ async fn handle_socket(stream: WebSocket, state: Arc<AppState>) {
let state = state.clone();
let mut recv_task = tokio::spawn(async move {
while let Some(Ok(Message::Text(text))) = receiver.next().await {
if let Ok(action) = serde_json::from_str::<Action>(&text) {
let client = action.client.clone();
tracing::debug!("update from {}", client.clone());
action.execute(state.clone()).await;
if let Ok(new_ops) = serde_json::from_str::<Ops>(&text) {
tracing::debug!("update from {}", new_ops.client.clone());
let doc = state.doc.read().await;
let mut doc = doc.to_owned();
doc.apply_ops(new_ops.ops.to_owned());
let new_state = NewState {
client,
doc: String::from_utf16_lossy(&doc),
client: new_ops.client,
doc: doc.to_string(),
ops: new_ops.ops,
};
let payload = serde_json::to_string(&new_state).unwrap();
let _ = tx.send(payload);
@ -75,5 +84,6 @@ async fn handle_socket(stream: WebSocket, state: Arc<AppState>) {
tokio::select! {
_ = (&mut send_task) => recv_task.abort(),
_ = (&mut recv_task) => send_task.abort(),
};
}
;
}