use webassembly with the server acting only as an intermediate
This commit is contained in:
parent
9aa1130c35
commit
7d0ef62c42
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
*~
|
||||
.DS_Store
|
||||
*.swp
|
||||
/target
|
||||
**/target
|
||||
**/Cargo.lock
|
||||
woweb/assets/dist_text*
|
||||
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal 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
8
.idea/modules.xml
Normal 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
16
.idea/poc.iml
Normal 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
6
.idea/vcs.xml
Normal 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>
|
@ -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
422
Cargo.lock
generated
@ -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",
|
||||
|
22
Cargo.toml
22
Cargo.toml
@ -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"
|
||||
]
|
||||
|
10
Dockerfile
10
Dockerfile
@ -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"]
|
||||
|
@ -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
7
dist_text/Cargo.toml
Normal 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
3
dist_text/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod text;
|
||||
|
||||
pub use crdts;
|
180
dist_text/src/text.rs
Normal file
180
dist_text/src/text.rs
Normal 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
13
dist_text_js/Cargo.toml
Normal 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
38
dist_text_js/src/lib.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
8
woweb/.idea/.gitignore
vendored
Normal 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
8
woweb/.idea/modules.xml
Normal 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
6
woweb/.idea/vcs.xml
Normal 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
11
woweb/.idea/woweb-poc.iml
Normal 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
19
woweb/Cargo.toml
Normal 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"
|
@ -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
90
woweb/assets/index.js
Normal 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
29
woweb/build.rs
Normal 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");
|
||||
}
|
@ -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,
|
||||
});
|
||||
|
@ -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(),
|
||||
};
|
||||
}
|
||||
;
|
||||
}
|
Loading…
Reference in New Issue
Block a user