diff --git a/Cargo.lock b/Cargo.lock index 586b3aa2..9adfad63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.11.1" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cceded2fb55f3c4b67068fa64962e2ca59614edc5b03167de9ff82ae803da0" +checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" dependencies = [ "actix-codec", "actix-rt", @@ -75,7 +75,7 @@ dependencies = [ "derive_more 2.0.1", "encoding_rs", "flate2", - "foldhash", + "foldhash 0.2.0", "futures-core", "h2", "http 0.2.12", @@ -87,8 +87,8 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.2", - "sha1", + "rand 0.10.2", + "sha1 0.11.0", "smallvec", "tokio", "tokio-util", @@ -160,9 +160,9 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" dependencies = [ "bytestring", "cfg-if", @@ -240,7 +240,7 @@ dependencies = [ "futures-core", "http 0.2.12", "http 1.3.1", - "impl-more", + "impl-more 0.1.9", "pin-project-lite", "tokio", "tokio-rustls 0.23.4", @@ -261,9 +261,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.11.0" +version = "4.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" +checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" dependencies = [ "actix-codec", "actix-http", @@ -280,10 +280,10 @@ dependencies = [ "cookie 0.16.2", "derive_more 2.0.1", "encoding_rs", - "foldhash", + "foldhash 0.2.0", "futures-core", "futures-util", - "impl-more", + "impl-more 0.3.2", "itoa", "language-tags", "log", @@ -296,7 +296,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.10", + "socket2 0.6.0", "time", "tracing", "url", @@ -315,12 +315,19 @@ dependencies = [ ] [[package]] -name = "addr2line" -version = "0.24.2" +name = "actix-ws" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "decf53c3cdd63dd6f289980b430238f9a2f6d19f8bce8e418272e08d3da43f0f" dependencies = [ - "gimli", + "actix-codec", + "actix-http", + "actix-web", + "bytestring", + "futures-core", + "futures-sink", + "tokio", + "tokio-util", ] [[package]] @@ -335,7 +342,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "generic-array", ] @@ -347,7 +354,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -778,21 +785,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "barrel" version = "0.7.0" @@ -850,6 +842,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block-padding" version = "0.3.3" @@ -947,6 +948,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.41" @@ -967,7 +979,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "inout", ] @@ -1040,7 +1052,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730" dependencies = [ - "const-oid", + "const-oid 0.9.6", "der", "spki", "x509-cert", @@ -1083,6 +1095,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "convert_case" version = "0.4.0" @@ -1166,6 +1184,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1192,6 +1219,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -1248,7 +1284,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "der_derive", "flagset", "pem-rfc7468", @@ -1338,11 +1374,22 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.6", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1577,6 +1624,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1747,11 +1800,23 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasi 0.14.4+wasi-0.2.4", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", +] + [[package]] name = "ghash" version = "0.5.1" @@ -1762,12 +1827,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "girlboss" version = "1.0.0-alpha.4" @@ -1812,7 +1871,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1872,7 +1931,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1949,6 +2008,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "0.14.32" @@ -2202,6 +2270,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" +[[package]] +name = "impl-more" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134d2c4324d61664107020b79019cf6a6aec153f0b79bc9619ee9e794a5fb021" + [[package]] name = "indexmap" version = "2.11.0" @@ -2242,17 +2316,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -2567,15 +2630,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "oid-registry" version = "0.8.1" @@ -2656,7 +2710,7 @@ dependencies = [ "pkcs5", "rand 0.9.2", "rc2", - "sha1", + "sha1 0.10.6", "sha2", "thiserror 2.0.16", "x509-parser", @@ -2703,7 +2757,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest", + "digest 0.10.7", "hmac", ] @@ -2784,9 +2838,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "695b3df3d3cc1015f12d70235e35b6b79befc5fa7a9b95b951eab1dd07c9efc2" dependencies = [ "cms", - "const-oid", + "const-oid 0.9.6", "der", - "digest", + "digest 0.10.7", "spki", "x509-cert", "zeroize", @@ -2850,7 +2904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2995,6 +3049,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.5.6" @@ -3029,6 +3089,17 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3082,6 +3153,12 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rc2" version = "0.8.1" @@ -3277,12 +3354,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -3604,8 +3675,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -3615,8 +3697,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] @@ -3948,29 +4030,26 @@ checksum = "b130bd8a58c163224b44e217b4239ca7b927d82bf6cc2fea1fc561d15056e3f7" [[package]] name = "tokio" -version = "1.47.1" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2 0.6.0", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -4028,6 +4107,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", "pin-project-lite", "tokio", ] @@ -4139,9 +4219,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicase" @@ -4188,7 +4268,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "subtle", ] @@ -4780,7 +4860,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" dependencies = [ - "const-oid", + "const-oid 0.9.6", "der", "spki", ] @@ -4945,6 +5025,7 @@ dependencies = [ "actix-rt", "actix-session", "actix-web", + "actix-ws", "async-stream", "awc", "barrel", diff --git a/elm/elm.json b/elm/elm.json index fc3035e7..33f5c4af 100644 --- a/elm/elm.json +++ b/elm/elm.json @@ -15,6 +15,7 @@ "bburdette/schelme": "3.0.0", "bburdette/toop": "1.2.0", "bburdette/typed-collections": "1.0.2", + "bburdette/websocket": "1.0.3", "bburdette/windowkeys": "1.0.1", "ceddlyburge/dnd-list": "1.0.0", "dillonkearns/elm-markdown": "7.0.0", diff --git a/elm/src/DataUtil.elm b/elm/src/DataUtil.elm index 0627e006..4d149fe0 100644 --- a/elm/src/DataUtil.elm +++ b/elm/src/DataUtil.elm @@ -18,6 +18,7 @@ type alias FileUrlInfo = { location : String , filelocation : String , tauri : Bool + , websockets : Bool } diff --git a/elm/src/Main.elm b/elm/src/Main.elm index 5954c2f7..a353115c 100644 --- a/elm/src/Main.elm +++ b/elm/src/Main.elm @@ -53,6 +53,7 @@ import ShowMessage import SlideShow import SpecialNotes as SN import SpecialNotesGui as SNG +import TDict import TSet import TagAThing import TagFiles @@ -65,6 +66,7 @@ import Url exposing (Url) import UserSettings import Util exposing (andMap) import View +import WebSocket import WindowKeys import ZkCommon exposing (StylePalette) @@ -93,6 +95,7 @@ type Msg | TauriAdminReplyData JD.Value | TauriPublicReplyData JD.Value | TauriTauriReplyData JD.Value + | ReceiveSocketMsg JD.Value | LoadUrl String | InternalUrl Url | TASelection JD.Value @@ -174,6 +177,7 @@ decodeFlags = |> andMap (JD.field "login" (JD.maybe DataUtil.decodeLoginData)) |> andMap (JD.field "adminsettings" OD.adminSettingsDecoder) |> andMap (JD.field "tauri" JD.bool) + |> andMap (JD.field "websockets" JD.bool) |> andMap (JD.field "mobile" JD.bool) @@ -189,6 +193,7 @@ type alias Flags = , login : Maybe DataUtil.LoginData , adminsettings : OD.AdminSettings , tauri : Bool + , websockets : Bool , mobile : Bool } @@ -208,7 +213,7 @@ type LocalValAction = LocalValAction { for : String , name : String - , action : Maybe String -> ( Model, Cmd Msg ) + , action : Model -> Maybe String -> ( Model, Cmd Msg ) } @@ -233,7 +238,7 @@ type alias Model = , ziClosures : Dict Int (Result Http.Error ( Time.Posix, Data.PrivateReply ) -> Msg) , mobile : Bool , spmodel : SP.Model - , localValAction : Maybe LocalValAction + , localValAction : Dict String LocalValAction , zknSearchResult : Data.ZkListNoteSearchResult } @@ -869,6 +874,9 @@ showMessage msg = TauriTauriReplyData _ -> "TauriTauriReplyData" + ReceiveSocketMsg _ -> + "ReceiveSocketMsg" + SelectDialogMsg _ -> "SelectDialogMsg" @@ -1381,6 +1389,17 @@ sendZIMsg fui msg = if fui.tauri then sendZIMsgTauri <| PrivateClosureRequest Nothing msg + else if fui.websockets then + sendSocketCommand + (WebSocket.encodeCmd <| + WebSocket.Send + { name = "private" + , content = + JE.encode 0 + (Data.privateClosureRequestEncoder (PrivateClosureRequest Nothing msg)) + } + ) + else HE.postJsonTask { url = fui.location ++ "/private" @@ -2201,10 +2220,10 @@ actualupdate msg model = ( nmd, cmd ) ( ReceiveLocalVal lv, _ ) -> - case model.localValAction of + case Dict.get lv.name model.localValAction of Just (LocalValAction lva) -> if lv.for == lva.for && lv.name == lva.name then - lva.action lv.value + lva.action { model | localValAction = Dict.remove lv.name model.localValAction } lv.value else ( model, Cmd.none ) @@ -2236,6 +2255,48 @@ actualupdate msg model = , Cmd.none ) + ( ReceiveSocketMsg jd, _ ) -> + case JD.decodeValue WebSocket.decodeMsg jd of + Ok (WebSocket.Error wsm) -> + ( displayMessageDialog model <| "websocket error: " ++ wsm.error + , Cmd.none + ) + + Ok (WebSocket.Data wsm) -> + if wsm.name == "private" then + case JD.decodeString (makeTDDecoder Data.privateClosureReplyDecoder) wsm.data of + Ok td -> + case td.data.closureId of + Just id -> + case Dict.get id model.ziClosures of + Just closure -> + let + cmsg = + closure (Ok ( td.utc, td.data.reply )) + in + actualupdate cmsg model + + Nothing -> + actualupdate (ZkReplyData (Ok ( td.utc, td.data.reply ))) model + + Nothing -> + actualupdate (ZkReplyData (Ok ( td.utc, td.data.reply ))) model + + Err e -> + ( displayMessageDialog model <| JD.errorToString e ++ "\n" ++ JE.encode 2 jd + , Cmd.none + ) + + else + ( displayMessageDialog model <| "unknown websocket connection - \"" ++ wsm.name ++ "\"" + , Cmd.none + ) + + Err e -> + ( displayMessageDialog model <| JD.errorToString e ++ "\n" ++ JE.encode 2 jd + , Cmd.none + ) + ( TauriUserReplyData jd, _ ) -> case JD.decodeValue OD.userResponseDecoder jd of Ok d -> @@ -2722,7 +2783,7 @@ actualupdate msg model = Data.PbyZkNoteAndLinks znl -> let action = - \mbstate -> + \amodel mbstate -> let znas = { znal = znl, mbstate = mbstate } @@ -2743,21 +2804,21 @@ actualupdate msg model = ngets = makePubNoteCacheGets model znl.zknote.content in - ( { model | state = vstate } + ( { amodel | state = vstate } , Cmd.batch ngets ) lid = SNG.localDataId znl.zknote.id in - ( { model | localValAction = Just <| LocalValAction { for = "lva", name = lid, action = action } } + ( { model | localValAction = Dict.insert lid (LocalValAction { for = "lva", name = lid, action = action }) model.localValAction } , LS.getLocalVal { for = "lva", name = lid } ) Data.PbyZkNoteAndLinksWhat znlw -> let action = - \mbstate -> + \amodel mbstate -> let znas : DataUtil.ZkNoteAndStateWhat znas = @@ -2765,12 +2826,12 @@ actualupdate msg model = , what = znlw.what } in - onZkNoteStatePbWhat model pt znas + onZkNoteStatePbWhat amodel pt znas lid = SNG.localDataId znlw.znl.zknote.id in - ( { model | localValAction = Just <| LocalValAction { for = "lva", name = lid, action = action } } + ( { model | localValAction = Dict.insert lid (LocalValAction { for = "lva", name = lid, action = action }) model.localValAction } , LS.getLocalVal { for = "lva", name = lid } ) @@ -3255,7 +3316,7 @@ actualupdate msg model = Data.PvyZkNoteAndLinksWhat znew -> let action = - \mbstate -> + \amodel mbstate -> let znas : DataUtil.ZkNoteAndStateWhat znas = @@ -3263,12 +3324,12 @@ actualupdate msg model = , what = znew.what } in - onZkNoteStateEditWhat model pt znas + onZkNoteStateEditWhat amodel pt znas lid = SNG.localDataId znew.znl.zknote.id in - ( { model | localValAction = Just <| LocalValAction { for = "lva", name = lid, action = action } } + ( { model | localValAction = Dict.insert lid (LocalValAction { for = "lva", name = lid, action = action }) model.localValAction } , LS.getLocalVal { for = "lva", name = lid } ) @@ -5223,6 +5284,7 @@ init flags url key zone fontsize = { location = flags.location , filelocation = flags.filelocation , tauri = flags.tauri + , websockets = flags.websockets } , navkey = key , seed = seed @@ -5241,7 +5303,7 @@ init flags url key zone fontsize = , ziClosures = Dict.empty , mobile = flags.mobile , spmodel = SP.initModel - , localValAction = Nothing + , localValAction = Dict.empty , zknSearchResult = { notes = [] , offset = 0 @@ -5270,6 +5332,24 @@ init flags url key zone fontsize = , { key = "l", ctrl = True, alt = True, shift = False, preventDefault = True } ] + opensock = + if flags.websockets then + sendSocketCommand + (WebSocket.encodeCmd <| + WebSocket.Connect + { name = "private" + , address = + flags.location + -- http -> ws, https -> wss + |> String.replace "http" "ws" + |> (\s -> s ++ "/privatews") + , protocol = "" + } + ) + + else + Cmd.none + ( m, c ) = initToRoute imodel imodel.initialRoute in @@ -5278,6 +5358,7 @@ init flags url key zone fontsize = [ c , geterrornote , setkeys + , opensock ] ) @@ -5349,6 +5430,7 @@ main = , receiveUITauriResponse TauriUserReplyData , receivePITauriResponse TauriPublicReplyData , receiveTITauriResponse TauriTauriReplyData + , receiveSocketMsg ReceiveSocketMsg ] ++ rdysubs , onUrlRequest = urlRequest @@ -5412,3 +5494,9 @@ port sendKeyCommand : JE.Value -> Cmd msg skcommand : WindowKeys.WindowKeyCmd -> Cmd Msg skcommand = WindowKeys.send sendKeyCommand + + +port receiveSocketMsg : (JD.Value -> msg) -> Sub msg + + +port sendSocketCommand : JE.Value -> Cmd msg diff --git a/orgauth b/orgauth index 2ab12256..766b86fe 160000 --- a/orgauth +++ b/orgauth @@ -1 +1 @@ -Subproject commit 2ab122564b991f20186257cef3298d323b102d97 +Subproject commit 766b86fed2f30ea0dcd6d6e811ebe874a0abf293 diff --git a/server-lib/Cargo.toml b/server-lib/Cargo.toml index e2e60356..153d6ac9 100644 --- a/server-lib/Cargo.toml +++ b/server-lib/Cargo.toml @@ -50,3 +50,4 @@ girlboss = {version = "1.0.0-alpha.4", features = ["actix-rt"]} nom = "8.0.0" # lapin = { version = "3.7.2", default-features = false, features = [ "rustls--ring", "default-runtime" ] } lapin = { version = "2.5.5", default-features = true } +actix-ws = "0.4.0" diff --git a/server-lib/src/error.rs b/server-lib/src/error.rs index 3d459e27..da158bdf 100644 --- a/server-lib/src/error.rs +++ b/server-lib/src/error.rs @@ -1,5 +1,6 @@ use actix_session; use actix_web::error as awe; +use actix_web::ResponseError; use cookie; use girlboss; use regex; @@ -49,6 +50,8 @@ pub fn annotate_string(s: String, source: Error) -> Error { annotate(Error::String(s), source) } +impl ResponseError for Error {} + impl fmt::Display for AnnotatedE { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} \n source: {}", self.error, self.source) diff --git a/server-lib/src/interfaces.rs b/server-lib/src/interfaces.rs index 250e0620..e41970e1 100644 --- a/server-lib/src/interfaces.rs +++ b/server-lib/src/interfaces.rs @@ -81,7 +81,7 @@ pub fn login_data_for_token( pub async fn connect_and_make_lapin_info( state: &State, - token: Option, + token: &Option, ) -> Option { // TODO: maybe attempt reconnect only every X seconds, so as not to // slow processing during rmq outage. @@ -236,7 +236,7 @@ pub async fn zk_interface_loggedin_streaming( pub async fn zk_interface_loggedin( state: &State, conn: &Connection, - token: Option, + token: &Option, uid: UserId, msg: &PrivateRequest, ) -> Result { @@ -351,8 +351,9 @@ pub async fn zk_interface_loggedin( let jid = new_jobid(state, uid); let lgb = state.girlboss.clone(); let server = state.server.clone(); - let li = connect_and_make_lapin_info(state, token.clone()).await; + let li = connect_and_make_lapin_info(state, token).await; let lapin_channelx = li.map(|li| li.channel).clone(); + let token = token.clone(); std::thread::spawn(move || { let rt = actix_rt::System::new(); @@ -410,7 +411,7 @@ pub async fn zk_interface_loggedin( jid, server, lapin_channelx, - token, + token.clone(), )); rt.run() .map_err(|e| { diff --git a/server-lib/src/lib.rs b/server-lib/src/lib.rs index b4fe23a6..f62e3169 100644 --- a/server-lib/src/lib.rs +++ b/server-lib/src/lib.rs @@ -19,11 +19,13 @@ use actix_session::{ use actix_web::{ cookie::{self, Key}, dev::Server, - web, App, HttpRequest, HttpResponse, HttpServer, Result, + rt, web, App, HttpRequest, HttpResponse, HttpServer, ResponseError, Result, }; +use actix_ws::AggregatedMessage; use chrono; use clap::Arg; use config::Config; +use futures_util::StreamExt as _; use futures_util::TryStreamExt as _; use girlboss::Girlboss; use interfaces::connect_and_make_lapin_info; @@ -37,16 +39,17 @@ pub use orgauth::{ }; pub use rusqlite; use rusqlite::Connection; +use serde::{Deserialize, Serialize}; use serde_json; use simple_error::simple_error; use sqldata::{get_single_value, local_server_id, LapinInfo}; -use std::fs::File; use std::io::{stdin, Write}; use std::path::Path; use std::path::PathBuf; use std::str::FromStr; use std::{env, sync::RwLock}; use std::{error::Error, sync::Arc}; +use std::{fs::File, time::SystemTime}; use timer; use tokio_util::io::StreamReader; use tracing_actix_web::TracingLogger; @@ -56,7 +59,9 @@ pub use zkprotocol::content as zc; pub use zkprotocol::messages::PrivateStreamingMessage; pub use zkprotocol::search as zs; use zkprotocol::{ - private::{PrivateError, PrivateReply, PrivateRequest}, + private::{ + PrivateClosureReply, PrivateClosureRequest, PrivateError, PrivateReply, PrivateRequest, + }, public::{PublicError, PublicReply, PublicRequest}, upload::UploadReply, }; @@ -235,7 +240,7 @@ fn session_user( None => Err(zkerr::Error::NotLoggedIn)?, Some(token) => orgauth::dbfun::read_user_by_token_api( &conn, - token, + &token, state.config.orgauth_config.login_token_expiration_ms, state.config.orgauth_config.regen_login_tokens, ) @@ -323,7 +328,7 @@ async fn receive_files( mut payload: Multipart, req: HttpRequest, ) -> HttpResponse { - let li = connect_and_make_lapin_info(config.get_ref(), get_cookie_id(&req)).await; + let li = connect_and_make_lapin_info(config.get_ref(), &get_cookie_id(&req)).await; match make_file_notes(session, &config, &li, &mut payload).await { Ok(r) => HttpResponse::Ok().json(r), Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)), @@ -428,6 +433,120 @@ fn get_cookie_id(req: &HttpRequest) -> Option { req.cookie("id").map(|c| c.value().to_string()) } +#[derive(Serialize, Deserialize)] +pub struct PrivateTimedData { + utcmillis: u128, + data: PrivateClosureReply, +} +async fn private_ws( + session: Session, + data: web::Data, + req: HttpRequest, + stream: web::Payload, +) -> Result { + info!("private_ws"); + let mut state = data.clone(); + + let token = get_cookie_id(&req); + + let token_uuid = match session.get::("token")? { + Some(tu) => tu, + None => { + return Ok( + HttpResponse::Ok().json(PrivateReply::PvyServerError(PrivateError::PveNotLoggedIn)), + ) + } + }; + + let (res, mut wssession, stream) = actix_ws::handle(&req, stream)?; + + let mut stream = stream + .aggregate_continuations() + // aggregate continuation frames up to 1MiB + .max_continuation_size(2_usize.pow(20)); + + rt::spawn(async move { + while let Some(msg) = stream.next().await { + match async { + match msg { + Ok(AggregatedMessage::Text(t)) => { + let pcr: PrivateClosureRequest = serde_json::from_str(&t)?; + match ( + zk_interface_check(&token_uuid, &mut state, &token, pcr.request).await, + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|n| n.as_millis()), + ) { + (Ok(sr), Ok(t)) => { + let pcreply = PrivateTimedData { + utcmillis: t, + data: PrivateClosureReply { + closure_id: pcr.closure_id, + reply: sr, + }, + }; + + wssession.text(serde_json::to_string(&pcreply)?).await? + } + (_, Err(e)) => { + error!("'private' err: {:?}", e); + wssession + .text(serde_json::to_string(&PrivateReply::PvyServerError( + PrivateError::PveString(e.to_string()), + ))?) + .await?; + } + (Err(e), _) => { + error!("'private' err: {:?}", e); + wssession + .text(serde_json::to_string(&PrivateReply::PvyServerError( + PrivateError::PveString(e.to_string()), + ))?) + .await?; + } + } + } + Ok(AggregatedMessage::Binary(b)) => { + wssession.binary(b).await?; + } + Ok(AggregatedMessage::Ping(msg)) => { + wssession.pong(&msg).await?; + } + Ok(AggregatedMessage::Pong(msg)) => { + wssession.ping(&msg).await?; // ping? + } + Ok(AggregatedMessage::Close(_msg)) => { + // wssession.close(msg); + return Ok::<(), Box>(()); + } + + Err(e) => { + error!("error {e}"); + } + }; + Ok(()) + } + .await + { + Ok(()) => (), + Err(e) => { + error!("'user' err: {:?}", e); + let se = UserResponse::UrpServerError(e.to_string()); + match serde_json::to_string(&se) { + Ok(s) => { + // we're ignoring it if reporting the error fails too. + let _ = wssession.text(s).await; + } + Err(_) => (), + } + } + } + } + }); + + Ok(res) +} + async fn private( session: Session, data: web::Data, @@ -437,8 +556,19 @@ async fn private( let mut state = data.clone(); let token = get_cookie_id(&req); + let token_uuid = match session.get::("token") { + Ok(None) => { + return HttpResponse::Ok().json(PrivateReply::PvyServerError(PrivateError::PveNotLoggedIn)) + } + Ok(Some(token_uuid)) => token_uuid, + Err(e) => { + return HttpResponse::Ok().json(PrivateReply::PvyServerError(PrivateError::PveString( + e.to_string(), + ))) + } + }; - match zk_interface_check(&session, &mut state, token, item.into_inner()).await { + match zk_interface_check(&token_uuid, &mut state, &token, item.into_inner()).await { Ok(sr) => HttpResponse::Ok().json(sr), Err(e) => { error!("'private' err: {:?}", e); @@ -466,33 +596,28 @@ async fn private_streaming( } async fn zk_interface_check( - session: &Session, + token_uuid: &Uuid, state: &State, - token: Option, + token: &Option, msg: PrivateRequest, ) -> Result { - match session.get::("token")? { - None => Ok(PrivateReply::PvyServerError(PrivateError::PveNotLoggedIn)), - Some(token_uuid) => { - let conn = sqldata::connection_open(state.config.orgauth_config.db.as_path())?; - match orgauth::dbfun::read_user_by_token_api( - &conn, - token_uuid, - state.config.orgauth_config.login_token_expiration_ms, - state.config.orgauth_config.regen_login_tokens, - ) { - Err(e) => { - info!("read_user_by_token_api error2: {:?}, {:?}", token_uuid, e); + let conn = sqldata::connection_open(state.config.orgauth_config.db.as_path())?; + match orgauth::dbfun::read_user_by_token_api( + &conn, + token_uuid, + state.config.orgauth_config.login_token_expiration_ms, + state.config.orgauth_config.regen_login_tokens, + ) { + Err(e) => { + info!("read_user_by_token_api error2: {:?}, {:?}", token_uuid, e); - Ok(PrivateReply::PvyServerError(PrivateError::PveLoginError( - e.to_string(), - ))) - } - Ok(userdata) => { - // finally! processing messages as logged in user. - interfaces::zk_interface_loggedin(state, &conn, token, userdata.id, &msg).await - } - } + Ok(PrivateReply::PvyServerError(PrivateError::PveLoginError( + e.to_string(), + ))) + } + Ok(userdata) => { + // finally! processing messages as logged in user. + interfaces::zk_interface_loggedin(state, &conn, token, userdata.id, &msg).await } } } @@ -509,7 +634,7 @@ async fn zk_interface_check_streaming( let conn = sqldata::connection_open(config.orgauth_config.db.as_path())?; match orgauth::dbfun::read_user_by_token_api( &conn, - token, + &token, config.orgauth_config.login_token_expiration_ms, config.orgauth_config.regen_login_tokens, ) { @@ -536,7 +661,7 @@ async fn private_upstreaming( body: web::Payload, req: HttpRequest, ) -> HttpResponse { - let li = connect_and_make_lapin_info(data.get_ref(), get_cookie_id(&req)).await; + let li = connect_and_make_lapin_info(data.get_ref(), &get_cookie_id(&req)).await; match zk_interface_check_upstreaming(&session, &data.config, &li, body).await { Ok(hr) => hr, Err(e) => { @@ -568,7 +693,7 @@ async fn zk_interface_check_upstreaming( match orgauth::dbfun::read_user_by_token_api( &conn, - token, + &token, config.orgauth_config.login_token_expiration_ms, config.orgauth_config.regen_login_tokens, ) { @@ -959,6 +1084,7 @@ pub async fn init_server(mut config: Config) -> Result> { .service(web::resource("/upload").route(web::post().to(receive_files))) .service(web::resource("/public").route(web::post().to(public))) .service(web::resource("/private").route(web::post().to(private))) + .service(web::resource("/privatews").route(web::get().to(private_ws))) .service(web::resource("/stream").route(web::post().to(private_streaming))) .service(web::resource("/upstream").route(web::post().to(private_upstreaming))) .service(web::resource("/user").route(web::post().to(user))) diff --git a/server-lib/src/sqldata.rs b/server-lib/src/sqldata.rs index eb23e8d7..064939ed 100644 --- a/server-lib/src/sqldata.rs +++ b/server-lib/src/sqldata.rs @@ -928,11 +928,14 @@ pub struct LapinInfo { pub async fn make_lapin_info( conn: Option<&lapin::Connection>, - token: Option, + token: &Option, ) -> Option { match (conn, token) { (Some(conn), Some(token)) => match make_lapin_channels(conn).await { - Ok(lc) => Some(LapinInfo { channel: lc, token }), + Ok(lc) => Some(LapinInfo { + channel: lc, + token: token.clone(), + }), Err(e) => { error!("{e}"); None diff --git a/server/static/index.html b/server/static/index.html index 124cf129..697d7e72 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -1,96 +1,156 @@ - - - - - - - - - -
- - + + + + + + + + + + +
+ +