From 7d0ef62c426872d712c98eb14a7ab7682c8100c9 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Thu, 9 Mar 2023 08:35:24 +0100 Subject: [PATCH] use webassembly with the server acting only as an intermediate --- .gitignore | 4 +- .idea/.gitignore | 8 + .idea/modules.xml | 8 + .idea/poc.iml | 16 ++ .idea/vcs.xml | 6 + .woodpecker.yml | 2 +- Cargo.lock | 422 ++++++++++++++++++++++++---- Cargo.toml | 22 +- Dockerfile | 10 +- assets/index.js | 87 ------ dist_text/Cargo.toml | 7 + dist_text/src/lib.rs | 3 + dist_text/src/text.rs | 180 ++++++++++++ dist_text_js/Cargo.toml | 13 + dist_text_js/src/lib.rs | 38 +++ src/action.rs | 48 ---- src/actions/delete.rs | 45 --- src/actions/insert.rs | 38 --- src/actions/mod.rs | 6 - woweb/.idea/.gitignore | 8 + woweb/.idea/modules.xml | 8 + woweb/.idea/vcs.xml | 6 + woweb/.idea/woweb-poc.iml | 11 + woweb/Cargo.toml | 19 ++ {assets => woweb/assets}/index.html | 2 +- woweb/assets/index.js | 90 ++++++ woweb/build.rs | 29 ++ {src => woweb/src}/main.rs | 7 +- {src => woweb/src}/ws.rs | 32 ++- 29 files changed, 850 insertions(+), 325 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/poc.iml create mode 100644 .idea/vcs.xml delete mode 100644 assets/index.js create mode 100644 dist_text/Cargo.toml create mode 100644 dist_text/src/lib.rs create mode 100644 dist_text/src/text.rs create mode 100644 dist_text_js/Cargo.toml create mode 100644 dist_text_js/src/lib.rs delete mode 100644 src/action.rs delete mode 100644 src/actions/delete.rs delete mode 100644 src/actions/insert.rs delete mode 100644 src/actions/mod.rs create mode 100644 woweb/.idea/.gitignore create mode 100644 woweb/.idea/modules.xml create mode 100644 woweb/.idea/vcs.xml create mode 100644 woweb/.idea/woweb-poc.iml create mode 100644 woweb/Cargo.toml rename {assets => woweb/assets}/index.html (82%) create mode 100644 woweb/assets/index.js create mode 100644 woweb/build.rs rename {src => woweb/src}/main.rs (93%) rename {src => woweb/src}/ws.rs (72%) diff --git a/.gitignore b/.gitignore index 44bb574..a036acb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *~ .DS_Store *.swp -/target +**/target +**/Cargo.lock +woweb/assets/dist_text* diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -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 diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4312957 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/poc.iml b/.idea/poc.iml new file mode 100644 index 0000000..53e954b --- /dev/null +++ b/.idea/poc.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml index 014ae3a..8fec64f 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -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: diff --git a/Cargo.lock b/Cargo.lock index e379c25..93086bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 752e976..be4055b 100644 --- a/Cargo.toml +++ b/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" +] diff --git a/Dockerfile b/Dockerfile index c877132..af8536c 100644 --- a/Dockerfile +++ b/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"] diff --git a/assets/index.js b/assets/index.js deleted file mode 100644 index ce08f4d..0000000 --- a/assets/index.js +++ /dev/null @@ -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(); diff --git a/dist_text/Cargo.toml b/dist_text/Cargo.toml new file mode 100644 index 0000000..640b012 --- /dev/null +++ b/dist_text/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "dist_text" +version = "0.1.0" +edition = "2021" + +[dependencies] +crdts = "7.3.0" diff --git a/dist_text/src/lib.rs b/dist_text/src/lib.rs new file mode 100644 index 0000000..99540ba --- /dev/null +++ b/dist_text/src/lib.rs @@ -0,0 +1,3 @@ +pub mod text; + +pub use crdts; \ No newline at end of file diff --git a/dist_text/src/text.rs b/dist_text/src/text.rs new file mode 100644 index 0000000..e850594 --- /dev/null +++ b/dist_text/src/text.rs @@ -0,0 +1,180 @@ +use std::fmt::{Display, Formatter}; +use crdts::{CmRDT, List}; +use crdts::list::Op; + +#[derive(Clone)] +pub struct Text { + inner: List, +} + +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 = 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>) { + 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> { + self.insert(start, end, "\n", src) + } + + pub fn insert(&mut self, start: usize, end: usize, data: &str, src: &str) -> Vec> { + let mut ops: Vec> = Vec::new(); + + if start < end { + let mut delete_ops = self.delete(start, end, src); + ops.append(&mut delete_ops); + } + + let data: Vec = 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> { + 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> { + 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> { + 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> = 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!"); + } +} diff --git a/dist_text_js/Cargo.toml b/dist_text_js/Cargo.toml new file mode 100644 index 0000000..cecd323 --- /dev/null +++ b/dist_text_js/Cargo.toml @@ -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" \ No newline at end of file diff --git a/dist_text_js/src/lib.rs b/dist_text_js/src/lib.rs new file mode 100644 index 0000000..a129264 --- /dev/null +++ b/dist_text_js/src/lib.rs @@ -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> = 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() + } +} diff --git a/src/action.rs b/src/action.rs deleted file mode 100644 index ee5f6c2..0000000 --- a/src/action.rs +++ /dev/null @@ -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, - action: ActionType, - start: usize, - end: usize, -} - -impl Action { - pub(crate) async fn execute(&self, state: Arc) { - { - let mut doc = state.doc.write().await; - let action: Box = 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()); - } - } -} diff --git a/src/actions/delete.rs b/src/actions/delete.rs deleted file mode 100644 index 5880178..0000000 --- a/src/actions/delete.rs +++ /dev/null @@ -1,45 +0,0 @@ -use super::ActionRunner; - -pub(crate) struct DeleteContentBackward; - -pub(crate) struct DeleteContentForward; - -fn delete(start: usize, end: usize, mut doc: Vec) -> Vec { - 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, doc: Vec) -> Vec { - 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, doc: Vec) -> Vec { - if start >= doc.len() { return doc; } - - let end = if start == end { start + 1 } else { end }; - delete(start, end, doc) - } -} diff --git a/src/actions/insert.rs b/src/actions/insert.rs deleted file mode 100644 index a99ad61..0000000 --- a/src/actions/insert.rs +++ /dev/null @@ -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, mut doc: Vec) -> Vec { - 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, doc: Vec) -> Vec { - insert(start, end, data, doc) - } -} - -impl ActionRunner for InsertFromPaste { - fn run(&self, start: usize, end: usize, data: Vec, doc: Vec) -> Vec { - insert(start, end, data, doc) - } -} - -impl ActionRunner for InsertLineBreak { - fn run(&self, start: usize, _end: usize, _data: Vec, doc: Vec) -> Vec { - insert(start, start, Vec::from(NL_BYTES), doc) - } -} diff --git a/src/actions/mod.rs b/src/actions/mod.rs deleted file mode 100644 index d33bf93..0000000 --- a/src/actions/mod.rs +++ /dev/null @@ -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, doc: Vec) -> Vec; -} diff --git a/woweb/.idea/.gitignore b/woweb/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/woweb/.idea/.gitignore @@ -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 diff --git a/woweb/.idea/modules.xml b/woweb/.idea/modules.xml new file mode 100644 index 0000000..ea8c3ec --- /dev/null +++ b/woweb/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/woweb/.idea/vcs.xml b/woweb/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/woweb/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/woweb/.idea/woweb-poc.iml b/woweb/.idea/woweb-poc.iml new file mode 100644 index 0000000..c254557 --- /dev/null +++ b/woweb/.idea/woweb-poc.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/woweb/Cargo.toml b/woweb/Cargo.toml new file mode 100644 index 0000000..8c001b1 --- /dev/null +++ b/woweb/Cargo.toml @@ -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" diff --git a/assets/index.html b/woweb/assets/index.html similarity index 82% rename from assets/index.html rename to woweb/assets/index.html index b2d722f..ce0b292 100644 --- a/assets/index.html +++ b/woweb/assets/index.html @@ -6,6 +6,6 @@ - + diff --git a/woweb/assets/index.js b/woweb/assets/index.js new file mode 100644 index 0000000..18aa9e7 --- /dev/null +++ b/woweb/assets/index.js @@ -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(); diff --git a/woweb/build.rs b/woweb/build.rs new file mode 100644 index 0000000..d835b8f --- /dev/null +++ b/woweb/build.rs @@ -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"); +} diff --git a/src/main.rs b/woweb/src/main.rs similarity index 93% rename from src/main.rs rename to woweb/src/main.rs index c7a7faa..8cacf4e 100644 --- a/src/main.rs +++ b/woweb/src/main.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>, + doc: RwLock, tx: broadcast::Sender, } @@ -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, }); diff --git a/src/ws.rs b/woweb/src/ws.rs similarity index 72% rename from src/ws.rs rename to woweb/src/ws.rs index 68f4279..97d5e15 100644 --- a/src/ws.rs +++ b/woweb/src/ws.rs @@ -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>, +} + +#[derive(Clone, Serialize, Deserialize)] +pub(crate) struct Ops { + pub(crate) client: String, + ops: Vec>, } pub(crate) async fn route( @@ -31,10 +39,11 @@ async fn handle_socket(stream: WebSocket, state: Arc) { { 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) { 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::(&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::(&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) { tokio::select! { _ = (&mut send_task) => recv_task.abort(), _ = (&mut recv_task) => send_task.abort(), - }; + } + ; }