diff --git a/package.json b/package.json index 921db9e..559c14b 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,10 @@ "packageManager": "pnpm@10.19.0", "pnpm": { "onlyBuiltDependencies": [ + "es5-ext", + "esbuild", "svelte-preprocess", - "vue-demi" + "wasm-pack" ], "ignoredBuiltDependencies": [ "@tailwindcss/oxide", diff --git a/packages/buttplug/Cargo.lock b/packages/buttplug/Cargo.lock index 159f414..57bf566 100644 --- a/packages/buttplug/Cargo.lock +++ b/packages/buttplug/Cargo.lock @@ -35,7 +35,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "serde", "version_check", @@ -52,13 +52,10 @@ dependencies = [ ] [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" @@ -85,7 +82,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -96,7 +93,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -120,12 +117,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bit-set" version = "0.8.0" @@ -173,9 +164,9 @@ dependencies = [ [[package]] name = "borrow-or-share" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "bumpalo" @@ -184,41 +175,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] -name = "buttplug" -version = "9.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6436601fd48d3d8f816602ab3ebb5ced4f8c5037b86c6622e09c0d21b40acfe0" +name = "buttplug_core" +version = "10.0.0" +source = "git+https://github.com/valknarthing/buttplug.git#c569409c51ad15f343c3f97a57711cdaa358f2ea" dependencies = [ - "aes", - "ahash", "async-stream", - "async-trait", - "buttplug_derive", - "byteorder", "cfg-if", - "dashmap", - "derivative", + "derive_builder", "displaydoc", - "ecb", + "enum_dispatch", "futures", "futures-util", - "getrandom 0.2.16", + "getset", + "jsonschema", + "log", + "serde", + "serde_json", + "serde_repr", + "strum", + "strum_macros", + "thiserror", + "tokio", + "wasm-bindgen-futures", + "wasmtimer", +] + +[[package]] +name = "buttplug_server" +version = "10.0.0" +source = "git+https://github.com/valknarthing/buttplug.git#c569409c51ad15f343c3f97a57711cdaa358f2ea" +dependencies = [ + "aes", + "async-trait", + "buttplug_core", + "buttplug_server_device_config", + "byteorder", + "dashmap", + "derive_more", + "ecb", + "evalexpr", + "futures", + "futures-util", + "getrandom 0.2.17", + "getrandom 0.3.4", "getset", "instant", "jsonschema", - "lazy_static", + "log", "once_cell", - "os_info", "paste", "prost", - "prost-build", "rand", "regex", - "rusty-xinput", "serde", - "serde-aux", "serde_json", - "serde_repr", "sha2", "strum", "strum_macros", @@ -228,35 +238,44 @@ dependencies = [ "tokio-util", "tracing", "tracing-futures", - "tracing-subscriber", - "url", "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasmtimer", - "windows", ] [[package]] -name = "buttplug_derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5ee14ebc45f9ef4fb919e11ff6a51decfd6007c7c623107c652c2ab552a211" +name = "buttplug_server_device_config" +version = "10.0.0" +source = "git+https://github.com/valknarthing/buttplug.git#c569409c51ad15f343c3f97a57711cdaa358f2ea" dependencies = [ - "quote", - "syn 2.0.106", + "buttplug_core", + "dashmap", + "displaydoc", + "futures", + "futures-util", + "getset", + "jsonschema", + "log", + "serde", + "serde_json", + "serde_repr", + "serde_yaml", + "strum", + "strum_macros", + "thiserror", + "uuid", ] [[package]] name = "buttplug_wasm" -version = "9.0.9" +version = "10.0.0" dependencies = [ "async-trait", - "buttplug", + "buttplug_core", + "buttplug_server", + "buttplug_server_device_config", "console_error_panic_hook", "futures", "futures-util", - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "log-panics", "parking_lot 0.11.2", @@ -290,32 +309,11 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -[[package]] -name = "cc" -version = "1.2.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" -dependencies = [ - "find-msvc-tools", - "shlex", -] - [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "num-traits", - "windows-link 0.2.0", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cipher" @@ -337,12 +335,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -368,6 +360,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -384,23 +411,61 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.5.3" +name = "data-encoding" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ - "powerfmt", + "derive_builder_macro", ] [[package]] -name = "derivative" -version = "2.2.0" +name = "derive_builder_core" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "rustc_version", + "syn", ] [[package]] @@ -421,7 +486,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -448,6 +513,18 @@ dependencies = [ "serde", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -455,49 +532,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno" -version = "0.3.14" +name = "evalexpr" +version = "13.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "25929004897f2bbab309121a60400d36992f6d911d09baa6c172f6cc55706601" dependencies = [ - "libc", - "windows-sys 0.61.0", + "rand", ] [[package]] name = "fancy-regex" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8" dependencies = [ "bit-set", "regex-automata", "regex-syntax", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "find-msvc-tools" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "fluent-uri" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" dependencies = [ "borrow-or-share", "ref-cast", @@ -505,13 +563,16 @@ dependencies = [ ] [[package]] -name = "form_urlencoded" -version = "1.2.2" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "fraction" @@ -579,7 +640,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -624,28 +685,28 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -658,7 +719,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -678,6 +739,11 @@ name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -685,30 +751,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icu_collections" version = "2.0.0" @@ -795,6 +837,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -848,17 +896,6 @@ dependencies = [ "web-sys", ] -[[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 = "itertools" version = "0.14.0" @@ -886,27 +923,28 @@ dependencies = [ [[package]] name = "jsonschema" -version = "0.30.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d" +checksum = "89f50532ce4a0ba3ae930212908d8ec50e7806065c059fe9c75da2ece6132294" dependencies = [ "ahash", - "base64", "bytecount", + "data-encoding", "email_address", "fancy-regex", "fraction", + "getrandom 0.3.4", "idna", "itoa", "num-cmp", "num-traits", - "once_cell", "percent-encoding", "referencing", "regex", "regex-syntax", "serde", "serde_json", + "unicode-general-category", "uuid-simd", ] @@ -922,12 +960,6 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - [[package]] name = "litemap" version = "0.8.0" @@ -946,9 +978,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "log-panics" @@ -975,30 +1007,13 @@ dependencies = [ "adler2", ] -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - [[package]] name = "nu-ansi-term" version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1040,12 +1055,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-integer" version = "0.1.46" @@ -1101,27 +1110,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "os_info" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" -dependencies = [ - "log", - "plist", - "serde", - "windows-sys 0.52.0", -] - [[package]] name = "outref" version = "0.5.2" @@ -1188,16 +1176,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap", -] - [[package]] name = "pin-project" version = "1.1.10" @@ -1215,7 +1193,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1230,19 +1208,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "plist" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" -dependencies = [ - "base64", - "indexmap", - "quick-xml", - "serde", - "time", -] - [[package]] name = "potential_utf" version = "0.1.3" @@ -1252,12 +1217,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1267,16 +1226,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.106", -] - [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1296,7 +1245,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1310,63 +1259,25 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.5" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", "prost-derive", ] -[[package]] -name = "prost-build" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn 2.0.106", - "tempfile", -] - [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 2.0.106", -] - -[[package]] -name = "prost-types" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" -dependencies = [ - "prost", -] - -[[package]] -name = "quick-xml" -version = "0.38.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" -dependencies = [ - "memchr", + "syn", ] [[package]] @@ -1411,7 +1322,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -1449,18 +1360,19 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "referencing" -version = "0.30.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e" +checksum = "15a8af0c6bb8eaf8b07cb06fc31ff30ca6fe19fb99afa476c276d8b24f365b0b" dependencies = [ "ahash", "fluent-uri", - "once_cell", + "getrandom 0.3.4", + "hashbrown 0.16.0", "parking_lot 0.12.4", "percent-encoding", "serde_json", @@ -1468,9 +1380,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1480,9 +1392,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1502,16 +1414,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] -name = "rustix" -version = "1.1.2" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.0", + "semver", ] [[package]] @@ -1520,17 +1428,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "rusty-xinput" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3335c2b62e1e48dd927f6c8941705386e3697fa944aabcb10431bea7ee47ef3" -dependencies = [ - "lazy_static", - "log", - "winapi", -] - [[package]] name = "ryu" version = "1.0.20" @@ -1544,68 +1441,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "serde" -version = "1.0.226" +name = "semver" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] -[[package]] -name = "serde-aux" -version = "4.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "207f67b28fe90fb596503a9bf0bf1ea5e831e21307658e177c5dfcdfc3ab8a0a" -dependencies = [ - "chrono", - "serde", - "serde-value", - "serde_json", -] - -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1616,7 +1497,20 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", ] [[package]] @@ -1639,12 +1533,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "slab" version = "0.4.11" @@ -1663,6 +1551,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.27.2" @@ -1678,18 +1572,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "syn", ] [[package]] @@ -1711,40 +1594,27 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", -] - -[[package]] -name = "tempfile" -version = "3.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.61.0", + "syn", ] [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1756,37 +1626,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "time" -version = "0.3.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" - -[[package]] -name = "time-macros" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinystr" version = "0.8.1" @@ -1799,36 +1638,31 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", - "io-uring", - "libc", - "mio", "pin-project-lite", - "slab", "tokio-macros", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -1837,9 +1671,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -1850,9 +1684,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1861,20 +1695,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -1945,6 +1779,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.19" @@ -1952,16 +1792,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] -name = "url" -version = "2.5.7" +name = "unsafe-libyaml" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "utf8_iter" @@ -1971,12 +1805,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.18.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ + "getrandom 0.3.4", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -1987,7 +1822,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" dependencies = [ "outref", - "uuid", "vsimd", ] @@ -2015,15 +1849,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -2058,7 +1883,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn", "wasm-bindgen-shared", ] @@ -2093,7 +1918,7 @@ checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2153,145 +1978,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.0", - "windows-result 0.4.0", - "windows-strings 0.5.0", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" -dependencies = [ - "windows-link 0.2.0", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -2301,24 +1987,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" -dependencies = [ - "windows-link 0.2.0", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -2335,15 +2003,6 @@ dependencies = [ "windows_x86_64_msvc", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -2424,7 +2083,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", "synstructure", ] @@ -2445,7 +2104,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -2465,7 +2124,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", "synstructure", ] @@ -2499,5 +2158,11 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/packages/buttplug/Cargo.toml b/packages/buttplug/Cargo.toml index fab9de4..81e3d6d 100644 --- a/packages/buttplug/Cargo.toml +++ b/packages/buttplug/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "buttplug_wasm" -version = "9.0.9" +version = "10.0.0" authors = ["Nonpolynomial Labs, LLC "] description = "WASM Interop for the Buttplug Intimate Hardware Control Library" license = "BSD-3-Clause" @@ -16,9 +16,9 @@ name = "buttplug_wasm" path = "src/lib.rs" [dependencies] -buttplug = { version = "9.0.9", default-features = false, features = ["wasm"] } -# buttplug = { path = "../../../buttplug/buttplug", default-features = false, features = ["wasm"] } -# buttplug_derive = { path = "../buttplug_derive" } +buttplug_core = { git = "https://github.com/valknarthing/buttplug.git", default-features = false, features = ["wasm"] } +buttplug_server = { git = "https://github.com/valknarthing/buttplug.git", default-features = false, features = ["wasm"] } +buttplug_server_device_config = { git = "https://github.com/valknarthing/buttplug.git" } js-sys = "0.3.80" tracing-wasm = "0.2.1" log-panics = { version = "2.1.0", features = ["with-backtrace"] } diff --git a/packages/buttplug/package.json b/packages/buttplug/package.json index 7512202..d3b26fc 100644 --- a/packages/buttplug/package.json +++ b/packages/buttplug/package.json @@ -13,9 +13,7 @@ "build:wasm": "wasm-pack build --out-dir wasm --out-name index --target bundler --release" }, "dependencies": { - "class-transformer": "^0.5.1", "eventemitter3": "^5.0.1", - "reflect-metadata": "^0.2.2", "typescript": "^5.9.2", "vite": "^7.1.4", "vite-plugin-wasm": "3.5.0", diff --git a/packages/buttplug/src/client/ButtplugBrowserWebsocketClientConnector.ts b/packages/buttplug/src/client/ButtplugBrowserWebsocketClientConnector.ts index 00ce763..e44f3e6 100644 --- a/packages/buttplug/src/client/ButtplugBrowserWebsocketClientConnector.ts +++ b/packages/buttplug/src/client/ButtplugBrowserWebsocketClientConnector.ts @@ -6,20 +6,20 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -"use strict"; +'use strict'; -import { IButtplugClientConnector } from "./IButtplugClientConnector"; -import { ButtplugMessage } from "../core/Messages"; -import { ButtplugBrowserWebsocketConnector } from "../utils/ButtplugBrowserWebsocketConnector"; +import { IButtplugClientConnector } from './IButtplugClientConnector'; +import { ButtplugMessage } from '../core/Messages'; +import { ButtplugBrowserWebsocketConnector } from '../utils/ButtplugBrowserWebsocketConnector'; export class ButtplugBrowserWebsocketClientConnector - extends ButtplugBrowserWebsocketConnector - implements IButtplugClientConnector + extends ButtplugBrowserWebsocketConnector + implements IButtplugClientConnector { - public send = (msg: ButtplugMessage): void => { - if (!this.Connected) { - throw new Error("ButtplugClient not connected"); - } - this.sendMessage(msg); - }; + public send = (msg: ButtplugMessage): void => { + if (!this.Connected) { + throw new Error('ButtplugClient not connected'); + } + this.sendMessage(msg); + }; } diff --git a/packages/buttplug/src/client/ButtplugClient.ts b/packages/buttplug/src/client/ButtplugClient.ts new file mode 100644 index 0000000..9e11ed4 --- /dev/null +++ b/packages/buttplug/src/client/ButtplugClient.ts @@ -0,0 +1,242 @@ +/*! + * Buttplug JS Source Code File - Visit https://buttplug.io for more info about + * the project. Licensed under the BSD 3-Clause license. See LICENSE file in the + * project root for full license information. + * + * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. + */ + +'use strict'; + +import { ButtplugLogger } from '../core/Logging'; +import { EventEmitter } from 'eventemitter3'; +import { ButtplugClientDevice } from './ButtplugClientDevice'; +import { IButtplugClientConnector } from './IButtplugClientConnector'; +import { ButtplugMessageSorter } from '../utils/ButtplugMessageSorter'; +import * as Messages from '../core/Messages'; +import { + ButtplugError, + ButtplugInitError, + ButtplugMessageError, +} from '../core/Exceptions'; +import { ButtplugClientConnectorException } from './ButtplugClientConnectorException'; + +export class ButtplugClient extends EventEmitter { + protected _pingTimer: NodeJS.Timeout | null = null; + protected _connector: IButtplugClientConnector | null = null; + protected _devices: Map = new Map(); + protected _clientName: string; + protected _logger = ButtplugLogger.Logger; + protected _isScanning = false; + private _sorter: ButtplugMessageSorter = new ButtplugMessageSorter(true); + + constructor(clientName = 'Generic Buttplug Client') { + super(); + this._clientName = clientName; + this._logger.Debug(`ButtplugClient: Client ${clientName} created.`); + } + + public get connected(): boolean { + return this._connector !== null && this._connector.Connected; + } + + public get devices(): Map { + // While this function doesn't actually send a message, if we don't have a + // connector, we shouldn't have devices. + this.checkConnector(); + return this._devices; + } + + public get isScanning(): boolean { + return this._isScanning; + } + + public connect = async (connector: IButtplugClientConnector) => { + this._logger.Info( + `ButtplugClient: Connecting using ${connector.constructor.name}` + ); + await connector.connect(); + this._connector = connector; + this._connector.addListener('message', this.parseMessages); + this._connector.addListener('disconnect', this.disconnectHandler); + await this.initializeConnection(); + }; + + public disconnect = async () => { + this._logger.Debug('ButtplugClient: Disconnect called'); + this._devices.clear(); + this.checkConnector(); + await this.shutdownConnection(); + await this._connector!.disconnect(); + }; + + public startScanning = async () => { + this._logger.Debug('ButtplugClient: StartScanning called'); + this._isScanning = true; + await this.sendMsgExpectOk({ StartScanning: { Id: 1 } }); + }; + + public stopScanning = async () => { + this._logger.Debug('ButtplugClient: StopScanning called'); + this._isScanning = false; + await this.sendMsgExpectOk({ StopScanning: { Id: 1 } }); + }; + + public stopAllDevices = async () => { + this._logger.Debug('ButtplugClient: StopAllDevices'); + await this.sendMsgExpectOk({ StopCmd: { Id: 1, DeviceIndex: undefined, FeatureIndex: undefined, Inputs: true, Outputs: true } }); + }; + + protected disconnectHandler = () => { + this._logger.Info('ButtplugClient: Disconnect event receieved.'); + this.emit('disconnect'); + }; + + protected parseMessages = (msgs: Messages.ButtplugMessage[]) => { + const leftoverMsgs = this._sorter.ParseIncomingMessages(msgs); + for (const x of leftoverMsgs) { + if (x.DeviceList !== undefined) { + this.parseDeviceList(x as Messages.DeviceList); + break; + } else if (x.ScanningFinished !== undefined) { + this._isScanning = false; + this.emit('scanningfinished', x); + } else if (x.InputReading !== undefined) { + // TODO this should be emitted from the device or feature, not the client + this.emit('inputreading', x); + } else { + console.log(`Unhandled message: ${x}`); + } + } + }; + + protected initializeConnection = async (): Promise => { + this.checkConnector(); + const msg = await this.sendMessage( + { + RequestServerInfo: { + ClientName: this._clientName, + Id: 1, + ProtocolVersionMajor: Messages.MESSAGE_SPEC_VERSION_MAJOR, + ProtocolVersionMinor: Messages.MESSAGE_SPEC_VERSION_MINOR + } + } + ); + if (msg.ServerInfo !== undefined) { + const serverinfo = msg as Messages.ServerInfo; + this._logger.Info( + `ButtplugClient: Connected to Server ${serverinfo.ServerName}` + ); + // TODO: maybe store server name, do something with message template version? + const ping = serverinfo.MaxPingTime; + // If the server version is lower than the client version, the server will disconnect here. + if (ping > 0) { + /* + this._pingTimer = setInterval(async () => { + // If we've disconnected, stop trying to ping the server. + if (!this.Connected) { + await this.ShutdownConnection(); + return; + } + await this.SendMessage(new Messages.Ping()); + } , Math.round(ping / 2)); + */ + } + await this.requestDeviceList(); + return true; + } else if (msg.Error !== undefined) { + // Disconnect and throw an exception with the error message we got back. + // This will usually only error out if we have a version mismatch that the + // server has detected. + await this._connector!.disconnect(); + const err = msg.Error as Messages.Error; + throw ButtplugError.LogAndError( + ButtplugInitError, + this._logger, + `Cannot connect to server. ${err.ErrorMessage}` + ); + } + return false; + } + + private parseDeviceList = (list: Messages.DeviceList) => { + for (let [_, d] of Object.entries(list.Devices)) { + if (!this._devices.has(d.DeviceIndex)) { + const device = ButtplugClientDevice.fromMsg( + d, + this.sendMessageClosure + ); + this._logger.Debug(`ButtplugClient: Adding Device: ${device}`); + this._devices.set(d.DeviceIndex, device); + this.emit('deviceadded', device); + } else { + this._logger.Debug(`ButtplugClient: Device already added: ${d}`); + } + } + for (let [index, device] of this._devices.entries()) { + if (!list.Devices.hasOwnProperty(index.toString())) { + this._devices.delete(index); + this.emit('deviceremoved', device); + } + } + } + + protected requestDeviceList = async () => { + this.checkConnector(); + this._logger.Debug('ButtplugClient: ReceiveDeviceList called'); + const response = (await this.sendMessage( + { + RequestDeviceList: { Id: 1 } + } + )); + this.parseDeviceList(response.DeviceList!); + }; + + protected shutdownConnection = async () => { + await this.stopAllDevices(); + if (this._pingTimer !== null) { + clearInterval(this._pingTimer); + this._pingTimer = null; + } + }; + + protected async sendMessage( + msg: Messages.ButtplugMessage + ): Promise { + this.checkConnector(); + const p = this._sorter.PrepareOutgoingMessage(msg); + await this._connector!.send(msg); + return await p; + } + + protected checkConnector() { + if (!this.connected) { + throw new ButtplugClientConnectorException( + 'ButtplugClient not connected' + ); + } + } + + protected sendMsgExpectOk = async ( + msg: Messages.ButtplugMessage + ): Promise => { + const response = await this.sendMessage(msg); + if (response.Ok !== undefined) { + return; + } else if (response.Error !== undefined) { + throw ButtplugError.FromError(response as Messages.Error); + } else { + throw ButtplugError.LogAndError( + ButtplugMessageError, + this._logger, + `Message ${response} not handled by SendMsgExpectOk` + ); + } + }; + + protected sendMessageClosure = async ( + msg: Messages.ButtplugMessage + ): Promise => { + return await this.sendMessage(msg); + }; +} diff --git a/packages/buttplug/src/client/ButtplugClientConnectorException.ts b/packages/buttplug/src/client/ButtplugClientConnectorException.ts index 406f132..b037b92 100644 --- a/packages/buttplug/src/client/ButtplugClientConnectorException.ts +++ b/packages/buttplug/src/client/ButtplugClientConnectorException.ts @@ -6,11 +6,11 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -import { ButtplugError } from "../core/Exceptions"; -import * as Messages from "../core/Messages"; +import { ButtplugError } from '../core/Exceptions'; +import * as Messages from '../core/Messages'; export class ButtplugClientConnectorException extends ButtplugError { - public constructor(message: string) { - super(message, Messages.ErrorClass.ERROR_UNKNOWN); - } + public constructor(message: string) { + super(message, Messages.ErrorClass.ERROR_UNKNOWN); + } } diff --git a/packages/buttplug/src/client/ButtplugClientDevice.ts b/packages/buttplug/src/client/ButtplugClientDevice.ts index 8a1a7ba..a84fd6c 100644 --- a/packages/buttplug/src/client/ButtplugClientDevice.ts +++ b/packages/buttplug/src/client/ButtplugClientDevice.ts @@ -6,396 +6,160 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -"use strict"; -import * as Messages from "../core/Messages"; +'use strict'; +import * as Messages from '../core/Messages'; import { - ButtplugDeviceError, - ButtplugError, - ButtplugMessageError, -} from "../core/Exceptions"; -import { EventEmitter } from "eventemitter3"; -import { getMessageClassFromMessage } from "../core/MessageUtils"; + ButtplugDeviceError, + ButtplugError, + ButtplugMessageError, +} from '../core/Exceptions'; +import { EventEmitter } from 'eventemitter3'; +import { ButtplugClientDeviceFeature } from './ButtplugClientDeviceFeature'; +import { DeviceOutputCommand } from './ButtplugClientDeviceCommand'; /** * Represents an abstract device, capable of taking certain kinds of messages. */ export class ButtplugClientDevice extends EventEmitter { - /** - * Return the name of the device. - */ - public get name(): string { - return this._deviceInfo.DeviceName; - } - /** - * Return the user set name of the device. - */ - public get displayName(): string | undefined { - return this._deviceInfo.DeviceDisplayName; - } + private _features: Map; - /** - * Return the index of the device. - */ - public get index(): number { - return this._deviceInfo.DeviceIndex; - } + /** + * Return the name of the device. + */ + public get name(): string { + return this._deviceInfo.DeviceName; + } - /** - * Return the index of the device. - */ - public get messageTimingGap(): number | undefined { - return this._deviceInfo.DeviceMessageTimingGap; - } + /** + * Return the user set name of the device. + */ + public get displayName(): string | undefined { + return this._deviceInfo.DeviceDisplayName; + } - /** - * Return a list of message types the device accepts. - */ - public get messageAttributes(): Messages.MessageAttributes { - return this._deviceInfo.DeviceMessages; - } + /** + * Return the index of the device. + */ + public get index(): number { + return this._deviceInfo.DeviceIndex; + } - public static fromMsg( - msg: Messages.DeviceInfo, - sendClosure: ( - device: ButtplugClientDevice, - msg: Messages.ButtplugDeviceMessage, - ) => Promise, - ): ButtplugClientDevice { - return new ButtplugClientDevice(msg, sendClosure); - } + /** + * Return the index of the device. + */ + public get messageTimingGap(): number | undefined { + return this._deviceInfo.DeviceMessageTimingGap; + } - // Map of messages and their attributes (feature count, etc...) - private allowedMsgs: Map = new Map< - string, - Messages.MessageAttributes - >(); + public get features(): Map { + return this._features; + } - /** - * @param _index Index of the device, as created by the device manager. - * @param _name Name of the device. - * @param allowedMsgs Buttplug messages the device can receive. - */ - constructor( - private _deviceInfo: Messages.DeviceInfo, - private _sendClosure: ( - device: ButtplugClientDevice, - msg: Messages.ButtplugDeviceMessage, - ) => Promise, - ) { - super(); - _deviceInfo.DeviceMessages.update(); - } + public static fromMsg( + msg: Messages.DeviceInfo, + sendClosure: ( + msg: Messages.ButtplugMessage + ) => Promise + ): ButtplugClientDevice { + return new ButtplugClientDevice(msg, sendClosure); + } - public async send( - msg: Messages.ButtplugDeviceMessage, - ): Promise { - // Assume we're getting the closure from ButtplugClient, which does all of - // the index/existence/connection/message checks for us. - return await this._sendClosure(this, msg); - } + /** + * @param _index Index of the device, as created by the device manager. + * @param _name Name of the device. + * @param allowedMsgs Buttplug messages the device can receive. + */ + private constructor( + private _deviceInfo: Messages.DeviceInfo, + private _sendClosure: ( + msg: Messages.ButtplugMessage + ) => Promise + ) { + super(); + this._features = new Map(Object.entries(_deviceInfo.DeviceFeatures).map(([index, v]) => [parseInt(index), new ButtplugClientDeviceFeature(_deviceInfo.DeviceIndex, _deviceInfo.DeviceName, v, _sendClosure)])); + } - public async sendExpectOk( - msg: Messages.ButtplugDeviceMessage, - ): Promise { - const response = await this.send(msg); - switch (getMessageClassFromMessage(response)) { - case Messages.Ok: - return; - case Messages.Error: - throw ButtplugError.FromError(response as Messages.Error); - default: - throw new ButtplugMessageError( - `Message type ${response.constructor} not handled by SendMsgExpectOk`, - ); - } - } + public async send( + msg: Messages.ButtplugMessage + ): Promise { + // Assume we're getting the closure from ButtplugClient, which does all of + // the index/existence/connection/message checks for us. + return await this._sendClosure(msg); + } - public async scalar( - scalar: Messages.ScalarSubcommand | Messages.ScalarSubcommand[], - ): Promise { - if (Array.isArray(scalar)) { - await this.sendExpectOk(new Messages.ScalarCmd(scalar, this.index)); - } else { - await this.sendExpectOk(new Messages.ScalarCmd([scalar], this.index)); - } - } + protected sendMsgExpectOk = async ( + msg: Messages.ButtplugMessage + ): Promise => { + const response = await this.send(msg); + if (response.Ok !== undefined) { + return; + } else if (response.Error !== undefined) { + throw ButtplugError.FromError(response as Messages.Error); + } else { + /* + throw ButtplugError.LogAndError( + ButtplugMessageError, + this._logger, + `Message ${response} not handled by SendMsgExpectOk` + ); + */ + } + }; - private async scalarCommandBuilder( - speed: number | number[], - actuator: Messages.ActuatorType, - ) { - const scalarAttrs = this.messageAttributes.ScalarCmd?.filter( - (x) => x.ActuatorType === actuator, - ); - if (!scalarAttrs || scalarAttrs.length === 0) { - throw new ButtplugDeviceError( - `Device ${this.name} has no ${actuator} capabilities`, - ); - } - const cmds: Messages.ScalarSubcommand[] = []; - if (typeof speed === "number") { - scalarAttrs.forEach((x) => - cmds.push(new Messages.ScalarSubcommand(x.Index, speed, actuator)), - ); - } else if (Array.isArray(speed)) { - if (speed.length > scalarAttrs.length) { - throw new ButtplugDeviceError( - `${speed.length} commands send to a device with ${scalarAttrs.length} vibrators`, - ); - } - scalarAttrs.forEach((x, i) => { - cmds.push(new Messages.ScalarSubcommand(x.Index, speed[i], actuator)); - }); - } else { - throw new ButtplugDeviceError( - `${actuator} can only take numbers or arrays of numbers.`, - ); - } - await this.scalar(cmds); - } + protected isOutputValid(featureIndex: number, type: Messages.OutputType) { + if (!this._deviceInfo.DeviceFeatures.hasOwnProperty(featureIndex.toString())) { + throw new ButtplugDeviceError(`Feature index ${featureIndex} does not exist for device ${this.name}`); + } + if (this._deviceInfo.DeviceFeatures[featureIndex.toString()].Outputs !== undefined && !this._deviceInfo.DeviceFeatures[featureIndex.toString()].Outputs.hasOwnProperty(type)) { + throw new ButtplugDeviceError(`Feature index ${featureIndex} does not support type ${type} for device ${this.name}`); + } + } - public get vibrateAttributes(): Messages.GenericDeviceMessageAttributes[] { - return ( - this.messageAttributes.ScalarCmd?.filter( - (x) => x.ActuatorType === Messages.ActuatorType.Vibrate, - ) ?? [] - ); - } + public hasOutput(type: Messages.OutputType): boolean { + return this._features.values().filter((f) => f.hasOutput(type)).toArray().length > 0; + } - public async vibrate(speed: number | number[]): Promise { - await this.scalarCommandBuilder(speed, Messages.ActuatorType.Vibrate); - } + public hasInput(type: Messages.InputType): boolean { + return this._features.values().filter((f) => f.hasInput(type)).toArray().length > 0; + } - public get oscillateAttributes(): Messages.GenericDeviceMessageAttributes[] { - return ( - this.messageAttributes.ScalarCmd?.filter( - (x) => x.ActuatorType === Messages.ActuatorType.Oscillate, - ) ?? [] - ); - } + public async runOutput(cmd: DeviceOutputCommand): Promise { + let p: Promise[] = []; + for (let f of this._features.values()) { + if (f.hasOutput(cmd.outputType)) { + p.push(f.runOutput(cmd)); + } + } + if (p.length == 0) { + return Promise.reject(`No features with output type ${cmd.outputType}`); + } + await Promise.all(p); + } - public async oscillate(speed: number | number[]): Promise { - await this.scalarCommandBuilder(speed, Messages.ActuatorType.Oscillate); - } + public async stop(): Promise { + await this.sendMsgExpectOk({StopCmd: { Id: 1, DeviceIndex: this.index, FeatureIndex: undefined, Inputs: true, Outputs: true}}); + } - public get rotateAttributes(): Messages.GenericDeviceMessageAttributes[] { - return this.messageAttributes.RotateCmd ?? []; - } + public async battery(): Promise { + let p: Promise[] = []; + for (let f of this._features.values()) { + if (f.hasInput(Messages.InputType.Battery)) { + // Right now, we only have one battery per device, so assume the first one we find is it. + let response = await f.runInput(Messages.InputType.Battery, Messages.InputCommandType.Read); + if (response === undefined) { + throw new ButtplugMessageError("Got incorrect message back."); + } + if (response.Reading[Messages.InputType.Battery] === undefined) { + throw new ButtplugMessageError("Got reading with no Battery info."); + } + return response.Reading[Messages.InputType.Battery].Value; + } + } + throw new ButtplugDeviceError(`No battery present on this device.`); + } - public async rotate( - values: number | [number, boolean][], - clockwise?: boolean, - ): Promise { - const rotateAttrs = this.messageAttributes.RotateCmd; - if (!rotateAttrs || rotateAttrs.length === 0) { - throw new ButtplugDeviceError( - `Device ${this.name} has no Rotate capabilities`, - ); - } - let msg: Messages.RotateCmd; - if (typeof values === "number") { - msg = Messages.RotateCmd.Create( - this.index, - new Array(rotateAttrs.length).fill([values, clockwise]), - ); - } else if (Array.isArray(values)) { - msg = Messages.RotateCmd.Create(this.index, values); - } else { - throw new ButtplugDeviceError( - "SendRotateCmd can only take a number and boolean, or an array of number/boolean tuples", - ); - } - await this.sendExpectOk(msg); - } - - public get linearAttributes(): Messages.GenericDeviceMessageAttributes[] { - return this.messageAttributes.LinearCmd ?? []; - } - - public async linear( - values: number | [number, number][], - duration?: number, - ): Promise { - const linearAttrs = this.messageAttributes.LinearCmd; - if (!linearAttrs || linearAttrs.length === 0) { - throw new ButtplugDeviceError( - `Device ${this.name} has no Linear capabilities`, - ); - } - let msg: Messages.LinearCmd; - if (typeof values === "number") { - msg = Messages.LinearCmd.Create( - this.index, - new Array(linearAttrs.length).fill([values, duration]), - ); - } else if (Array.isArray(values)) { - msg = Messages.LinearCmd.Create(this.index, values); - } else { - throw new ButtplugDeviceError( - "SendLinearCmd can only take a number and number, or an array of number/number tuples", - ); - } - await this.sendExpectOk(msg); - } - - public async sensorRead( - sensorIndex: number, - sensorType: Messages.SensorType, - ): Promise { - const response = await this.send( - new Messages.SensorReadCmd(this.index, sensorIndex, sensorType), - ); - switch (getMessageClassFromMessage(response)) { - case Messages.SensorReading: - return (response as Messages.SensorReading).Data; - case Messages.Error: - throw ButtplugError.FromError(response as Messages.Error); - default: - throw new ButtplugMessageError( - `Message type ${response.constructor} not handled by sensorRead`, - ); - } - } - - public get hasBattery(): boolean { - const batteryAttrs = this.messageAttributes.SensorReadCmd?.filter( - (x) => x.SensorType === Messages.SensorType.Battery, - ); - return batteryAttrs !== undefined && batteryAttrs.length > 0; - } - - public async battery(): Promise { - if (!this.hasBattery) { - throw new ButtplugDeviceError( - `Device ${this.name} has no Battery capabilities`, - ); - } - const batteryAttrs = this.messageAttributes.SensorReadCmd?.filter( - (x) => x.SensorType === Messages.SensorType.Battery, - ); - // Find the battery sensor, we'll need its index. - const result = await this.sensorRead( - batteryAttrs![0].Index, - Messages.SensorType.Battery, - ); - return result[0] / 100.0; - } - - public get hasRssi(): boolean { - const rssiAttrs = this.messageAttributes.SensorReadCmd?.filter( - (x) => x.SensorType === Messages.SensorType.RSSI, - ); - return rssiAttrs !== undefined && rssiAttrs.length === 0; - } - - public async rssi(): Promise { - if (!this.hasRssi) { - throw new ButtplugDeviceError( - `Device ${this.name} has no RSSI capabilities`, - ); - } - const rssiAttrs = this.messageAttributes.SensorReadCmd?.filter( - (x) => x.SensorType === Messages.SensorType.RSSI, - ); - // Find the battery sensor, we'll need its index. - const result = await this.sensorRead( - rssiAttrs![0].Index, - Messages.SensorType.RSSI, - ); - return result[0]; - } - - public async rawRead( - endpoint: string, - expectedLength: number, - timeout: number, - ): Promise { - if (!this.messageAttributes.RawReadCmd) { - throw new ButtplugDeviceError( - `Device ${this.name} has no raw read capabilities`, - ); - } - if (this.messageAttributes.RawReadCmd.Endpoints.indexOf(endpoint) === -1) { - throw new ButtplugDeviceError( - `Device ${this.name} has no raw readable endpoint ${endpoint}`, - ); - } - const response = await this.send( - new Messages.RawReadCmd(this.index, endpoint, expectedLength, timeout), - ); - switch (getMessageClassFromMessage(response)) { - case Messages.RawReading: - return new Uint8Array((response as Messages.RawReading).Data); - case Messages.Error: - throw ButtplugError.FromError(response as Messages.Error); - default: - throw new ButtplugMessageError( - `Message type ${response.constructor} not handled by rawRead`, - ); - } - } - - public async rawWrite( - endpoint: string, - data: Uint8Array, - writeWithResponse: boolean, - ): Promise { - if (!this.messageAttributes.RawWriteCmd) { - throw new ButtplugDeviceError( - `Device ${this.name} has no raw write capabilities`, - ); - } - if (this.messageAttributes.RawWriteCmd.Endpoints.indexOf(endpoint) === -1) { - throw new ButtplugDeviceError( - `Device ${this.name} has no raw writable endpoint ${endpoint}`, - ); - } - await this.sendExpectOk( - new Messages.RawWriteCmd(this.index, endpoint, data, writeWithResponse), - ); - } - - public async rawSubscribe(endpoint: string): Promise { - if (!this.messageAttributes.RawSubscribeCmd) { - throw new ButtplugDeviceError( - `Device ${this.name} has no raw subscribe capabilities`, - ); - } - if ( - this.messageAttributes.RawSubscribeCmd.Endpoints.indexOf(endpoint) === -1 - ) { - throw new ButtplugDeviceError( - `Device ${this.name} has no raw subscribable endpoint ${endpoint}`, - ); - } - await this.sendExpectOk(new Messages.RawSubscribeCmd(this.index, endpoint)); - } - - public async rawUnsubscribe(endpoint: string): Promise { - // This reuses raw subscribe's info. - if (!this.messageAttributes.RawSubscribeCmd) { - throw new ButtplugDeviceError( - `Device ${this.name} has no raw unsubscribe capabilities`, - ); - } - if ( - this.messageAttributes.RawSubscribeCmd.Endpoints.indexOf(endpoint) === -1 - ) { - throw new ButtplugDeviceError( - `Device ${this.name} has no raw unsubscribable endpoint ${endpoint}`, - ); - } - await this.sendExpectOk( - new Messages.RawUnsubscribeCmd(this.index, endpoint), - ); - } - - public async stop(): Promise { - await this.sendExpectOk(new Messages.StopDeviceCmd(this.index)); - } - - public emitDisconnected() { - this.emit("deviceremoved"); - } + public emitDisconnected() { + this.emit('deviceremoved'); + } } diff --git a/packages/buttplug/src/client/ButtplugClientDeviceCommand.ts b/packages/buttplug/src/client/ButtplugClientDeviceCommand.ts new file mode 100644 index 0000000..d2858ad --- /dev/null +++ b/packages/buttplug/src/client/ButtplugClientDeviceCommand.ts @@ -0,0 +1,111 @@ +import { ButtplugDeviceError } from "../core/Exceptions"; +import { OutputType } from "../core/Messages"; + +class PercentOrSteps { + private _percent: number | undefined; + private _steps: number | undefined; + + public get percent() { + return this._percent; + } + + public get steps() { + return this._steps; + } + + public static createSteps(s: number): PercentOrSteps { + let v = new PercentOrSteps; + v._steps = s; + return v; + } + + public static createPercent(p: number): PercentOrSteps { + if (p < 0 || p > 1.0) { + throw new ButtplugDeviceError(`Percent value ${p} is not in the range 0.0 <= x <= 1.0`); + } + + let v = new PercentOrSteps; + v._percent = p; + return v; + } +} + +export class DeviceOutputCommand { + public constructor( + private _outputType: OutputType, + private _value: PercentOrSteps, + private _duration?: number, + ) + {} + + public get outputType() { + return this._outputType; + } + + public get value() { + return this._value; + } + + public get duration() { + return this._duration; + } +} + +export class DeviceOutputValueConstructor { + public constructor( + private _outputType: OutputType) + {} + + public steps(steps: number): DeviceOutputCommand { + return new DeviceOutputCommand(this._outputType, PercentOrSteps.createSteps(steps), undefined); + } + + public percent(percent: number): DeviceOutputCommand { + return new DeviceOutputCommand(this._outputType, PercentOrSteps.createPercent(percent), undefined); + } +} + +export class DeviceOutputPositionWithDurationConstructor { + public steps(steps: number, duration: number): DeviceOutputCommand { + return new DeviceOutputCommand(OutputType.Position, PercentOrSteps.createSteps(steps), duration); + } + + public percent(percent: number, duration: number): DeviceOutputCommand { + return new DeviceOutputCommand(OutputType.HwPositionWithDuration, PercentOrSteps.createPercent(percent), duration); + } +} + +export class DeviceOutput { + private constructor() {} + + public static get Vibrate() { + return new DeviceOutputValueConstructor(OutputType.Vibrate); + } + public static get Rotate() { + return new DeviceOutputValueConstructor(OutputType.Rotate); + } + public static get Oscillate() { + return new DeviceOutputValueConstructor(OutputType.Oscillate); + } + public static get Constrict() { + return new DeviceOutputValueConstructor(OutputType.Constrict); + } + public static get Inflate() { + return new DeviceOutputValueConstructor(OutputType.Inflate); + } + public static get Temperature() { + return new DeviceOutputValueConstructor(OutputType.Temperature); + } + public static get Led() { + return new DeviceOutputValueConstructor(OutputType.Led); + } + public static get Spray() { + return new DeviceOutputValueConstructor(OutputType.Spray); + } + public static get Position() { + return new DeviceOutputValueConstructor(OutputType.Position); + } + public static get PositionWithDuration() { + return new DeviceOutputPositionWithDurationConstructor(); + } +} diff --git a/packages/buttplug/src/client/ButtplugClientDeviceFeature.ts b/packages/buttplug/src/client/ButtplugClientDeviceFeature.ts new file mode 100644 index 0000000..dae0783 --- /dev/null +++ b/packages/buttplug/src/client/ButtplugClientDeviceFeature.ts @@ -0,0 +1,168 @@ +import { ButtplugDeviceError, ButtplugError, ButtplugMessageError } from "../core/Exceptions"; +import * as Messages from "../core/Messages"; +import { DeviceOutputCommand } from "./ButtplugClientDeviceCommand"; + +export class ButtplugClientDeviceFeature { + + constructor( + private _deviceIndex: number, + private _deviceName: string, + private _feature: Messages.DeviceFeature, + private _sendClosure: ( + msg: Messages.ButtplugMessage + ) => Promise) { + } + + protected send = async (msg: Messages.ButtplugMessage): Promise => { + return await this._sendClosure(msg); + } + + protected sendMsgExpectOk = async ( + msg: Messages.ButtplugMessage + ): Promise => { + const response = await this.send(msg); + if (response.Ok !== undefined) { + return; + } else if (response.Error !== undefined) { + throw ButtplugError.FromError(response as Messages.Error); + } else { + throw new ButtplugMessageError("Expected Ok or Error, and didn't get either!"); + } + }; + + protected isOutputValid(type: Messages.OutputType) { + if (this._feature.Output !== undefined && !this._feature.Output.hasOwnProperty(type)) { + throw new ButtplugDeviceError(`Feature index ${this._feature.FeatureIndex} does not support type ${type} for device ${this._deviceName}`); + } + } + + protected isInputValid(type: Messages.InputType) { + if (this._feature.Input !== undefined && !this._feature.Input.hasOwnProperty(type)) { + throw new ButtplugDeviceError(`Feature index ${this._feature.FeatureIndex} does not support type ${type} for device ${this._deviceName}`); + } + } + + protected async sendOutputCmd(command: DeviceOutputCommand): Promise { + // Make sure the requested feature is valid + this.isOutputValid(command.outputType); + if (command.value === undefined) { + throw new ButtplugDeviceError(`${command.outputType} requires value defined`); + } + + let type = command.outputType; + let duration: undefined | number = undefined; + if (type == Messages.OutputType.HwPositionWithDuration) { + if (command.duration === undefined) { + throw new ButtplugDeviceError("PositionWithDuration requires duration defined"); + } + duration = command.duration; + } + let value: number; + let p = command.value; + if (p.percent === undefined) { + // TODO Check step limits here + value = command.value.steps!; + } else { + value = Math.ceil(this._feature.Output[type]!.Value![1] * p.percent); + } + let newCommand: Messages.DeviceFeatureOutput = { Value: value, Duration: duration }; + let outCommand = {}; + outCommand[type.toString()] = newCommand; + + let cmd: Messages.ButtplugMessage = { + OutputCmd: { + Id: 1, + DeviceIndex: this._deviceIndex, + FeatureIndex: this._feature.FeatureIndex, + Command: outCommand + } + }; + await this.sendMsgExpectOk(cmd); + } + + public get featureDescriptor(): string { + return this._feature.FeatureDescriptor; + } + + public get featureIndex(): number { + return this._feature.FeatureIndex; + } + + public get outputTypes(): Messages.OutputType[] { + if (this._feature.Output === undefined) return []; + return Object.keys(this._feature.Output) as Messages.OutputType[]; + } + + public get inputTypes(): Messages.InputType[] { + if (this._feature.Input === undefined) return []; + return Object.keys(this._feature.Input) as Messages.InputType[]; + } + + public outputMaxValue(type: Messages.OutputType): number { + if (this._feature.Output === undefined || this._feature.Output[type] === undefined) { + return 0; + } + const val = this._feature.Output[type]!.Value; + // Value can arrive as number[] [min, max] from server or as number + if (Array.isArray(val)) { + return val[val.length - 1]; + } + return val as number; + } + + public hasOutput(type: Messages.OutputType): boolean { + if (this._feature.Output !== undefined) { + return this._feature.Output.hasOwnProperty(type.toString()); + } + return false; + } + + public hasInput(type: Messages.InputType): boolean { + if (this._feature.Input !== undefined) { + return this._feature.Input.hasOwnProperty(type.toString()); + } + return false; + } + + + public async runOutput(cmd: DeviceOutputCommand): Promise { + if (this._feature.Output !== undefined && this._feature.Output.hasOwnProperty(cmd.outputType.toString())) { + return this.sendOutputCmd(cmd); + } + throw new ButtplugDeviceError(`Output type ${cmd.outputType} not supported by feature.`); + } + + public async runInput(inputType: Messages.InputType, inputCommand: Messages.InputCommandType): Promise { + // Make sure the requested feature is valid + this.isInputValid(inputType); + let inputAttributes = this._feature.Input[inputType]; + console.log(this._feature.Input); + if ((inputCommand === Messages.InputCommandType.Unsubscribe && !inputAttributes.Command.includes(Messages.InputCommandType.Subscribe)) && !inputAttributes.Command.includes(inputCommand)) { + throw new ButtplugDeviceError(`${inputType} does not support command ${inputCommand}`); + } + + let cmd: Messages.ButtplugMessage = { + InputCmd: { + Id: 1, + DeviceIndex: this._deviceIndex, + FeatureIndex: this._feature.FeatureIndex, + Type: inputType, + Command: inputCommand, + } + }; + if (inputCommand == Messages.InputCommandType.Read) { + const response = await this.send(cmd); + if (response.InputReading !== undefined) { + return response.InputReading; + } else if (response.Error !== undefined) { + throw ButtplugError.FromError(response as Messages.Error); + } else { + throw new ButtplugMessageError("Expected InputReading or Error, and didn't get either!"); + } + } else { + console.log(`Sending subscribe message: ${JSON.stringify(cmd)}`); + await this.sendMsgExpectOk(cmd); + console.log("Got back ok?"); + } + } +} diff --git a/packages/buttplug/src/client/ButtplugNodeWebsocketClientConnector.ts b/packages/buttplug/src/client/ButtplugNodeWebsocketClientConnector.ts index f255b04..5eeb535 100644 --- a/packages/buttplug/src/client/ButtplugNodeWebsocketClientConnector.ts +++ b/packages/buttplug/src/client/ButtplugNodeWebsocketClientConnector.ts @@ -6,12 +6,12 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -"use strict"; +'use strict'; -import { ButtplugBrowserWebsocketClientConnector } from "./ButtplugBrowserWebsocketClientConnector"; -import { WebSocket as NodeWebSocket } from "ws"; +import { ButtplugBrowserWebsocketClientConnector } from './ButtplugBrowserWebsocketClientConnector'; +import { WebSocket as NodeWebSocket } from 'ws'; export class ButtplugNodeWebsocketClientConnector extends ButtplugBrowserWebsocketClientConnector { - protected _websocketConstructor = - NodeWebSocket as unknown as typeof WebSocket; + protected _websocketConstructor = + NodeWebSocket as unknown as typeof WebSocket; } diff --git a/packages/buttplug/src/client/Client.ts b/packages/buttplug/src/client/Client.ts deleted file mode 100644 index 754e413..0000000 --- a/packages/buttplug/src/client/Client.ts +++ /dev/null @@ -1,276 +0,0 @@ -/*! - * Buttplug JS Source Code File - Visit https://buttplug.io for more info about - * the project. Licensed under the BSD 3-Clause license. See LICENSE file in the - * project root for full license information. - * - * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. - */ - -"use strict"; - -import { ButtplugLogger } from "../core/Logging"; -import { EventEmitter } from "eventemitter3"; -import { ButtplugClientDevice } from "./ButtplugClientDevice"; -import { IButtplugClientConnector } from "./IButtplugClientConnector"; -import { ButtplugMessageSorter } from "../utils/ButtplugMessageSorter"; - -import * as Messages from "../core/Messages"; -import { - ButtplugDeviceError, - ButtplugError, - ButtplugInitError, - ButtplugMessageError, -} from "../core/Exceptions"; -import { ButtplugClientConnectorException } from "./ButtplugClientConnectorException"; -import { getMessageClassFromMessage } from "../core/MessageUtils"; - -export class ButtplugClient extends EventEmitter { - protected _pingTimer: NodeJS.Timeout | null = null; - protected _connector: IButtplugClientConnector | null = null; - protected _devices: Map = new Map(); - protected _clientName: string; - protected _logger = ButtplugLogger.Logger; - protected _isScanning = false; - private _sorter: ButtplugMessageSorter = new ButtplugMessageSorter(true); - - constructor(clientName = "Generic Buttplug Client") { - super(); - this._clientName = clientName; - this._logger.Debug(`ButtplugClient: Client ${clientName} created.`); - } - - public get connected(): boolean { - return this._connector !== null && this._connector.Connected; - } - - public get devices(): ButtplugClientDevice[] { - // While this function doesn't actually send a message, if we don't have a - // connector, we shouldn't have devices. - this.checkConnector(); - const devices: ButtplugClientDevice[] = []; - this._devices.forEach((d) => { - devices.push(d); - }); - return devices; - } - - public get isScanning(): boolean { - return this._isScanning; - } - - public connect = async (connector: IButtplugClientConnector) => { - this._logger.Info( - `ButtplugClient: Connecting using ${connector.constructor.name}`, - ); - await connector.connect(); - this._connector = connector; - this._connector.addListener("message", this.parseMessages); - this._connector.addListener("disconnect", this.disconnectHandler); - await this.initializeConnection(); - }; - - public disconnect = async () => { - this._logger.Debug("ButtplugClient: Disconnect called"); - this.checkConnector(); - await this.shutdownConnection(); - await this._connector!.disconnect(); - }; - - public startScanning = async () => { - this._logger.Debug("ButtplugClient: StartScanning called"); - this._isScanning = true; - await this.sendMsgExpectOk(new Messages.StartScanning()); - }; - - public stopScanning = async () => { - this._logger.Debug("ButtplugClient: StopScanning called"); - this._isScanning = false; - await this.sendMsgExpectOk(new Messages.StopScanning()); - }; - - public stopAllDevices = async () => { - this._logger.Debug("ButtplugClient: StopAllDevices"); - await this.sendMsgExpectOk(new Messages.StopAllDevices()); - }; - - private async sendDeviceMessage( - device: ButtplugClientDevice, - deviceMsg: Messages.ButtplugDeviceMessage, - ): Promise { - this.checkConnector(); - const dev = this._devices.get(device.index); - if (dev === undefined) { - throw ButtplugError.LogAndError( - ButtplugDeviceError, - this._logger, - `Device ${device.index} not available.`, - ); - } - deviceMsg.DeviceIndex = device.index; - return await this.sendMessage(deviceMsg); - } - - protected disconnectHandler = () => { - this._logger.Info("ButtplugClient: Disconnect event receieved."); - this.emit("disconnect"); - }; - - protected parseMessages = (msgs: Messages.ButtplugMessage[]) => { - const leftoverMsgs = this._sorter.ParseIncomingMessages(msgs); - for (const x of leftoverMsgs) { - switch (getMessageClassFromMessage(x)) { - case Messages.DeviceAdded: { - const addedMsg = x as Messages.DeviceAdded; - const addedDevice = ButtplugClientDevice.fromMsg( - addedMsg, - this.sendDeviceMessageClosure, - ); - this._devices.set(addedMsg.DeviceIndex, addedDevice); - this.emit("deviceadded", addedMsg, addedDevice); - break; - } - case Messages.DeviceRemoved: { - const removedMsg = x as Messages.DeviceRemoved; - if (this._devices.has(removedMsg.DeviceIndex)) { - const removedDevice = this._devices.get(removedMsg.DeviceIndex); - removedDevice?.emitDisconnected(); - this._devices.delete(removedMsg.DeviceIndex); - this.emit("deviceremoved", removedMsg, removedDevice); - } - break; - } - case Messages.ScanningFinished: - this._isScanning = false; - this.emit("scanningfinished", x); - break; - } - } - }; - - protected initializeConnection = async (): Promise => { - this.checkConnector(); - const msg = await this.sendMessage( - new Messages.RequestServerInfo( - this._clientName, - Messages.MESSAGE_SPEC_VERSION, - ), - ); - switch (getMessageClassFromMessage(msg)) { - case Messages.ServerInfo: { - const serverinfo = msg as Messages.ServerInfo; - this._logger.Info( - `ButtplugClient: Connected to Server ${serverinfo.ServerName}`, - ); - // TODO: maybe store server name, do something with message template version? - const ping = serverinfo.MaxPingTime; - if (serverinfo.MessageVersion < Messages.MESSAGE_SPEC_VERSION) { - // Disconnect and throw an exception explaining the version mismatch problem. - await this._connector!.disconnect(); - throw ButtplugError.LogAndError( - ButtplugInitError, - this._logger, - `Server protocol version ${serverinfo.MessageVersion} is older than client protocol version ${Messages.MESSAGE_SPEC_VERSION}. Please update server.`, - ); - } - if (ping > 0) { - /* - this._pingTimer = setInterval(async () => { - // If we've disconnected, stop trying to ping the server. - if (!this.Connected) { - await this.ShutdownConnection(); - return; - } - await this.SendMessage(new Messages.Ping()); - } , Math.round(ping / 2)); - */ - } - await this.requestDeviceList(); - return true; - } - case Messages.Error: { - // Disconnect and throw an exception with the error message we got back. - // This will usually only error out if we have a version mismatch that the - // server has detected. - await this._connector!.disconnect(); - const err = msg as Messages.Error; - throw ButtplugError.LogAndError( - ButtplugInitError, - this._logger, - `Cannot connect to server. ${err.ErrorMessage}`, - ); - } - } - return false; - }; - - protected requestDeviceList = async () => { - this.checkConnector(); - this._logger.Debug("ButtplugClient: ReceiveDeviceList called"); - const deviceList = (await this.sendMessage( - new Messages.RequestDeviceList(), - )) as Messages.DeviceList; - deviceList.Devices.forEach((d) => { - if (!this._devices.has(d.DeviceIndex)) { - const device = ButtplugClientDevice.fromMsg( - d, - this.sendDeviceMessageClosure, - ); - this._logger.Debug(`ButtplugClient: Adding Device: ${device}`); - this._devices.set(d.DeviceIndex, device); - this.emit("deviceadded", device); - } else { - this._logger.Debug(`ButtplugClient: Device already added: ${d}`); - } - }); - }; - - protected shutdownConnection = async () => { - await this.stopAllDevices(); - if (this._pingTimer !== null) { - clearInterval(this._pingTimer); - this._pingTimer = null; - } - }; - - protected async sendMessage( - msg: Messages.ButtplugMessage, - ): Promise { - this.checkConnector(); - const p = this._sorter.PrepareOutgoingMessage(msg); - await this._connector!.send(msg); - return await p; - } - - protected checkConnector() { - if (!this.connected) { - throw new ButtplugClientConnectorException( - "ButtplugClient not connected", - ); - } - } - - protected sendMsgExpectOk = async ( - msg: Messages.ButtplugMessage, - ): Promise => { - const response = await this.sendMessage(msg); - switch (getMessageClassFromMessage(response)) { - case Messages.Ok: - return; - case Messages.Error: - throw ButtplugError.FromError(response as Messages.Error); - default: - throw ButtplugError.LogAndError( - ButtplugMessageError, - this._logger, - `Message type ${getMessageClassFromMessage(response)!.constructor} not handled by SendMsgExpectOk`, - ); - } - }; - - protected sendDeviceMessageClosure = async ( - device: ButtplugClientDevice, - msg: Messages.ButtplugDeviceMessage, - ): Promise => { - return await this.sendDeviceMessage(device, msg); - }; -} diff --git a/packages/buttplug/src/client/IButtplugClientConnector.ts b/packages/buttplug/src/client/IButtplugClientConnector.ts index 731df34..110b191 100644 --- a/packages/buttplug/src/client/IButtplugClientConnector.ts +++ b/packages/buttplug/src/client/IButtplugClientConnector.ts @@ -6,13 +6,13 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -import { ButtplugMessage } from "../core/Messages"; -import { EventEmitter } from "eventemitter3"; +import { ButtplugMessage } from '../core/Messages'; +import { EventEmitter } from 'eventemitter3'; export interface IButtplugClientConnector extends EventEmitter { - connect: () => Promise; - disconnect: () => Promise; - initialize: () => Promise; - send: (msg: ButtplugMessage) => void; - readonly Connected: boolean; + connect: () => Promise; + disconnect: () => Promise; + initialize: () => Promise; + send: (msg: ButtplugMessage) => void; + readonly Connected: boolean; } diff --git a/packages/buttplug/src/core/Exceptions.ts b/packages/buttplug/src/core/Exceptions.ts index 2f9dfa5..04d047c 100644 --- a/packages/buttplug/src/core/Exceptions.ts +++ b/packages/buttplug/src/core/Exceptions.ts @@ -6,96 +6,102 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -import * as Messages from "./Messages"; -import { ButtplugLogger } from "./Logging"; +import * as Messages from './Messages'; +import { ButtplugLogger } from './Logging'; export class ButtplugError extends Error { - public get ErrorClass(): Messages.ErrorClass { - return this.errorClass; - } + public get ErrorClass(): Messages.ErrorClass { + return this.errorClass; + } - public get InnerError(): Error | undefined { - return this.innerError; - } + public get InnerError(): Error | undefined { + return this.innerError; + } - public get Id(): number | undefined { - return this.messageId; - } + public get Id(): number | undefined { + return this.messageId; + } - public get ErrorMessage(): Messages.ButtplugMessage { - return new Messages.Error(this.message, this.ErrorClass, this.Id); - } + public get ErrorMessage(): Messages.ButtplugMessage { + return { + Error: { + Id: this.Id, + ErrorCode: this.ErrorClass, + ErrorMessage: this.message + } + } + } - public static LogAndError( - constructor: new (str: string, num: number) => T, - logger: ButtplugLogger, - message: string, - id: number = Messages.SYSTEM_MESSAGE_ID, - ): T { - logger.Error(message); - return new constructor(message, id); - } + public static LogAndError( + constructor: new (str: string, num: number) => T, + logger: ButtplugLogger, + message: string, + id: number = Messages.SYSTEM_MESSAGE_ID + ): T { + logger.Error(message); + return new constructor(message, id); + } - public static FromError(error: Messages.Error) { - switch (error.ErrorCode) { - case Messages.ErrorClass.ERROR_DEVICE: - return new ButtplugDeviceError(error.ErrorMessage, error.Id); - case Messages.ErrorClass.ERROR_INIT: - return new ButtplugInitError(error.ErrorMessage, error.Id); - case Messages.ErrorClass.ERROR_UNKNOWN: - return new ButtplugUnknownError(error.ErrorMessage, error.Id); - case Messages.ErrorClass.ERROR_PING: - return new ButtplugPingError(error.ErrorMessage, error.Id); - case Messages.ErrorClass.ERROR_MSG: - return new ButtplugMessageError(error.ErrorMessage, error.Id); - default: - throw new Error(`Message type ${error.ErrorCode} not handled`); - } - } + public static FromError(error: Messages.Error) { + switch (error.ErrorCode) { + case Messages.ErrorClass.ERROR_DEVICE: + return new ButtplugDeviceError(error.ErrorMessage, error.Id); + case Messages.ErrorClass.ERROR_INIT: + return new ButtplugInitError(error.ErrorMessage, error.Id); + case Messages.ErrorClass.ERROR_UNKNOWN: + return new ButtplugUnknownError(error.ErrorMessage, error.Id); + case Messages.ErrorClass.ERROR_PING: + return new ButtplugPingError(error.ErrorMessage, error.Id); + case Messages.ErrorClass.ERROR_MSG: + return new ButtplugMessageError(error.ErrorMessage, error.Id); + default: + throw new Error(`Message type ${error.ErrorCode} not handled`); + } + } - public errorClass: Messages.ErrorClass = Messages.ErrorClass.ERROR_UNKNOWN; - public innerError: Error | undefined; - public messageId: number | undefined; + public errorClass: Messages.ErrorClass = Messages.ErrorClass.ERROR_UNKNOWN; + public innerError: Error | undefined; + public messageId: number | undefined; - protected constructor( - message: string, - errorClass: Messages.ErrorClass, - id: number = Messages.SYSTEM_MESSAGE_ID, - inner?: Error, - ) { - super(message); - this.errorClass = errorClass; - this.innerError = inner; - this.messageId = id; - } + protected constructor( + message: string, + errorClass: Messages.ErrorClass, + id: number = Messages.SYSTEM_MESSAGE_ID, + inner?: Error + ) { + super(message); + this.errorClass = errorClass; + this.innerError = inner; + this.messageId = id; + } } export class ButtplugInitError extends ButtplugError { - public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { - super(message, Messages.ErrorClass.ERROR_INIT, id); - } + public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { + super(message, Messages.ErrorClass.ERROR_INIT, id); + } } export class ButtplugDeviceError extends ButtplugError { - public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { - super(message, Messages.ErrorClass.ERROR_DEVICE, id); - } + public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { + super(message, Messages.ErrorClass.ERROR_DEVICE, id); + } } export class ButtplugMessageError extends ButtplugError { - public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { - super(message, Messages.ErrorClass.ERROR_MSG, id); - } + public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { + super(message, Messages.ErrorClass.ERROR_MSG, id); + } } export class ButtplugPingError extends ButtplugError { - public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { - super(message, Messages.ErrorClass.ERROR_PING, id); - } + public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { + super(message, Messages.ErrorClass.ERROR_PING, id); + } } export class ButtplugUnknownError extends ButtplugError { - public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { - super(message, Messages.ErrorClass.ERROR_UNKNOWN, id); - } + public constructor(message: string, id: number = Messages.SYSTEM_MESSAGE_ID) { + super(message, Messages.ErrorClass.ERROR_UNKNOWN, id); + } } diff --git a/packages/buttplug/src/core/Logging.ts b/packages/buttplug/src/core/Logging.ts index abcbb50..8fab968 100644 --- a/packages/buttplug/src/core/Logging.ts +++ b/packages/buttplug/src/core/Logging.ts @@ -6,71 +6,73 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -import { EventEmitter } from "eventemitter3"; +import { EventEmitter } from 'eventemitter3'; export enum ButtplugLogLevel { - Off, - Error, - Warn, - Info, - Debug, - Trace, + Off, + Error, + Warn, + Info, + Debug, + Trace, } /** * Representation of log messages for the internal logging utility. */ export class LogMessage { - /** Timestamp for the log message */ - private timestamp: string; + /** Timestamp for the log message */ + private timestamp: string; - /** Log Message */ - private logMessage: string; + /** Log Message */ + private logMessage: string; - /** Log Level */ - private logLevel: ButtplugLogLevel; + /** Log Level */ + private logLevel: ButtplugLogLevel; - /** - * @param logMessage Log message. - * @param logLevel: Log severity level. - */ - public constructor(logMessage: string, logLevel: ButtplugLogLevel) { - const a = new Date(); - const hour = a.getHours(); - const min = a.getMinutes(); - const sec = a.getSeconds(); - this.timestamp = `${hour}:${min}:${sec}`; - this.logMessage = logMessage; - this.logLevel = logLevel; - } + /** + * @param logMessage Log message. + * @param logLevel: Log severity level. + */ + public constructor(logMessage: string, logLevel: ButtplugLogLevel) { + const a = new Date(); + const hour = a.getHours(); + const min = a.getMinutes(); + const sec = a.getSeconds(); + this.timestamp = `${hour}:${min}:${sec}`; + this.logMessage = logMessage; + this.logLevel = logLevel; + } - /** - * Returns the log message. - */ - public get Message() { - return this.logMessage; - } + /** + * Returns the log message. + */ + public get Message() { + return this.logMessage; + } - /** - * Returns the log message level. - */ - public get LogLevel() { - return this.logLevel; - } + /** + * Returns the log message level. + */ + public get LogLevel() { + return this.logLevel; + } - /** - * Returns the log message timestamp. - */ - public get Timestamp() { - return this.timestamp; - } + /** + * Returns the log message timestamp. + */ + public get Timestamp() { + return this.timestamp; + } - /** - * Returns a formatted string with timestamp, level, and message. - */ - public get FormattedMessage() { - return `${ButtplugLogLevel[this.logLevel]} : ${this.timestamp} : ${this.logMessage}`; - } + /** + * Returns a formatted string with timestamp, level, and message. + */ + public get FormattedMessage() { + return `${ButtplugLogLevel[this.logLevel]} : ${this.timestamp} : ${ + this.logMessage + }`; + } } /** @@ -79,117 +81,117 @@ export class LogMessage { * basically), and allows message logging throughout the module. */ export class ButtplugLogger extends EventEmitter { - /** Singleton instance for the logger */ - protected static sLogger: ButtplugLogger | undefined = undefined; - /** Sets maximum log level to log to console */ - protected maximumConsoleLogLevel: ButtplugLogLevel = ButtplugLogLevel.Off; - /** Sets maximum log level for all log messages */ - protected maximumEventLogLevel: ButtplugLogLevel = ButtplugLogLevel.Off; + /** Singleton instance for the logger */ + protected static sLogger: ButtplugLogger | undefined = undefined; + /** Sets maximum log level to log to console */ + protected maximumConsoleLogLevel: ButtplugLogLevel = ButtplugLogLevel.Off; + /** Sets maximum log level for all log messages */ + protected maximumEventLogLevel: ButtplugLogLevel = ButtplugLogLevel.Off; - /** - * Returns the stored static instance of the logger, creating one if it - * doesn't currently exist. - */ - public static get Logger(): ButtplugLogger { - if (ButtplugLogger.sLogger === undefined) { - ButtplugLogger.sLogger = new ButtplugLogger(); - } - return this.sLogger!; - } + /** + * Returns the stored static instance of the logger, creating one if it + * doesn't currently exist. + */ + public static get Logger(): ButtplugLogger { + if (ButtplugLogger.sLogger === undefined) { + ButtplugLogger.sLogger = new ButtplugLogger(); + } + return this.sLogger!; + } - /** - * Constructor. Can only be called internally since we regulate ButtplugLogger - * ownership. - */ - protected constructor() { - super(); - } + /** + * Constructor. Can only be called internally since we regulate ButtplugLogger + * ownership. + */ + protected constructor() { + super(); + } - /** - * Set the maximum log level to output to console. - */ - public get MaximumConsoleLogLevel() { - return this.maximumConsoleLogLevel; - } + /** + * Set the maximum log level to output to console. + */ + public get MaximumConsoleLogLevel() { + return this.maximumConsoleLogLevel; + } - /** - * Get the maximum log level to output to console. - */ - public set MaximumConsoleLogLevel(buttplugLogLevel: ButtplugLogLevel) { - this.maximumConsoleLogLevel = buttplugLogLevel; - } + /** + * Get the maximum log level to output to console. + */ + public set MaximumConsoleLogLevel(buttplugLogLevel: ButtplugLogLevel) { + this.maximumConsoleLogLevel = buttplugLogLevel; + } - /** - * Set the global maximum log level - */ - public get MaximumEventLogLevel() { - return this.maximumEventLogLevel; - } + /** + * Set the global maximum log level + */ + public get MaximumEventLogLevel() { + return this.maximumEventLogLevel; + } - /** - * Get the global maximum log level - */ - public set MaximumEventLogLevel(logLevel: ButtplugLogLevel) { - this.maximumEventLogLevel = logLevel; - } + /** + * Get the global maximum log level + */ + public set MaximumEventLogLevel(logLevel: ButtplugLogLevel) { + this.maximumEventLogLevel = logLevel; + } - /** - * Log new message at Error level. - */ - public Error(msg: string) { - this.AddLogMessage(msg, ButtplugLogLevel.Error); - } + /** + * Log new message at Error level. + */ + public Error(msg: string) { + this.AddLogMessage(msg, ButtplugLogLevel.Error); + } - /** - * Log new message at Warn level. - */ - public Warn(msg: string) { - this.AddLogMessage(msg, ButtplugLogLevel.Warn); - } + /** + * Log new message at Warn level. + */ + public Warn(msg: string) { + this.AddLogMessage(msg, ButtplugLogLevel.Warn); + } - /** - * Log new message at Info level. - */ - public Info(msg: string) { - this.AddLogMessage(msg, ButtplugLogLevel.Info); - } + /** + * Log new message at Info level. + */ + public Info(msg: string) { + this.AddLogMessage(msg, ButtplugLogLevel.Info); + } - /** - * Log new message at Debug level. - */ - public Debug(msg: string) { - this.AddLogMessage(msg, ButtplugLogLevel.Debug); - } + /** + * Log new message at Debug level. + */ + public Debug(msg: string) { + this.AddLogMessage(msg, ButtplugLogLevel.Debug); + } - /** - * Log new message at Trace level. - */ - public Trace(msg: string) { - this.AddLogMessage(msg, ButtplugLogLevel.Trace); - } + /** + * Log new message at Trace level. + */ + public Trace(msg: string) { + this.AddLogMessage(msg, ButtplugLogLevel.Trace); + } - /** - * Checks to see if message should be logged, and if so, adds message to the - * log buffer. May also print message and emit event. - */ - protected AddLogMessage(msg: string, level: ButtplugLogLevel) { - // If nothing wants the log message we have, ignore it. - if ( - level > this.maximumEventLogLevel && - level > this.maximumConsoleLogLevel - ) { - return; - } - const logMsg = new LogMessage(msg, level); - // Clients and console logging may have different needs. For instance, it - // could be that the client requests trace level, while all we want in the - // console is info level. This makes sure the client can't also spam the - // console. - if (level <= this.maximumConsoleLogLevel) { - console.log(logMsg.FormattedMessage); - } - if (level <= this.maximumEventLogLevel) { - this.emit("log", logMsg); - } - } + /** + * Checks to see if message should be logged, and if so, adds message to the + * log buffer. May also print message and emit event. + */ + protected AddLogMessage(msg: string, level: ButtplugLogLevel) { + // If nothing wants the log message we have, ignore it. + if ( + level > this.maximumEventLogLevel && + level > this.maximumConsoleLogLevel + ) { + return; + } + const logMsg = new LogMessage(msg, level); + // Clients and console logging may have different needs. For instance, it + // could be that the client requests trace level, while all we want in the + // console is info level. This makes sure the client can't also spam the + // console. + if (level <= this.maximumConsoleLogLevel) { + console.log(logMsg.FormattedMessage); + } + if (level <= this.maximumEventLogLevel) { + this.emit('log', logMsg); + } + } } diff --git a/packages/buttplug/src/core/MessageUtils.ts b/packages/buttplug/src/core/MessageUtils.ts deleted file mode 100644 index 7981b00..0000000 --- a/packages/buttplug/src/core/MessageUtils.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Buttplug JS Source Code File - Visit https://buttplug.io for more info about - * the project. Licensed under the BSD 3-Clause license. See LICENSE file in the - * project root for full license information. - * - * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. - */ - -"use strict"; -import { plainToInstance } from "class-transformer"; -import * as Messages from "./Messages"; - -function getMessageClass( - type: string, -): (new (...args: unknown[]) => Messages.ButtplugMessage) | null { - for (const value of Object.values(Messages)) { - if (typeof value === "function" && "Name" in value && value.Name === type) { - return value; - } - } - return null; -} - -export function getMessageClassFromMessage( - msg: Messages.ButtplugMessage, -): (new (...args: unknown[]) => Messages.ButtplugMessage) | null { - // Making the bold assumption all message classes have the Name static. Should define a - // requirement for this in the abstract class. - return getMessageClass(Object.getPrototypeOf(msg).constructor.Name); -} - -export function fromJSON(str): Messages.ButtplugMessage[] { - const msgarray: object[] = JSON.parse(str); - const msgs: Messages.ButtplugMessage[] = []; - for (const x of Array.from(msgarray)) { - const type = Object.getOwnPropertyNames(x)[0]; - const cls = getMessageClass(type); - if (cls) { - const msg = plainToInstance( - cls, - x[type], - ); - msg.update(); - msgs.push(msg); - } - } - return msgs; -} diff --git a/packages/buttplug/src/core/Messages.ts b/packages/buttplug/src/core/Messages.ts index 9e84fda..fdfc688 100644 --- a/packages/buttplug/src/core/Messages.ts +++ b/packages/buttplug/src/core/Messages.ts @@ -7,485 +7,203 @@ */ // tslint:disable:max-classes-per-file -"use strict"; +'use strict'; -import { instanceToPlain, Type } from "class-transformer"; -import "reflect-metadata"; +import { ButtplugMessageError } from './Exceptions'; export const SYSTEM_MESSAGE_ID = 0; export const DEFAULT_MESSAGE_ID = 1; export const MAX_ID = 4294967295; -export const MESSAGE_SPEC_VERSION = 3; +export const MESSAGE_SPEC_VERSION_MAJOR = 4; +export const MESSAGE_SPEC_VERSION_MINOR = 0; -export class MessageAttributes { - public ScalarCmd?: Array; - public RotateCmd?: Array; - public LinearCmd?: Array; - public RawReadCmd?: RawDeviceMessageAttributes; - public RawWriteCmd?: RawDeviceMessageAttributes; - public RawSubscribeCmd?: RawDeviceMessageAttributes; - public SensorReadCmd?: Array; - public SensorSubscribeCmd?: Array; - public StopDeviceCmd: {}; - - constructor(data: Partial) { - Object.assign(this, data); - } - - public update() { - this.ScalarCmd?.forEach((x, i) => (x.Index = i)); - this.RotateCmd?.forEach((x, i) => (x.Index = i)); - this.LinearCmd?.forEach((x, i) => (x.Index = i)); - this.SensorReadCmd?.forEach((x, i) => (x.Index = i)); - this.SensorSubscribeCmd?.forEach((x, i) => (x.Index = i)); - } +// Base message interfaces +export interface ButtplugMessage { + Ok?: Ok; + Ping?: Ping; + Error?: Error; + RequestServerInfo?: RequestServerInfo; + ServerInfo?: ServerInfo; + RequestDeviceList?: RequestDeviceList; + StartScanning?: StartScanning; + StopScanning?: StopScanning; + ScanningFinished?: ScanningFinished; + StopCmd?: StopCmd; + InputCmd?: InputCmd; + InputReading?: InputReading; + OutputCmd?: OutputCmd; + DeviceList?: DeviceList; } -export enum ActuatorType { - Unknown = "Unknown", - Vibrate = "Vibrate", - Rotate = "Rotate", - Oscillate = "Oscillate", - Constrict = "Constrict", - Inflate = "Inflate", - Position = "Position", +export function msgId(msg: ButtplugMessage): number { + for (let [_, entry] of Object.entries(msg)) { + if (entry != undefined) { + return entry.Id; + } + } + throw new ButtplugMessageError(`Message ${msg} does not have an ID.`); } -export enum SensorType { - Unknown = "Unknown", - Battery = "Battery", - RSSI = "RSSI", - Button = "Button", - Pressure = "Pressure", - // Temperature, - // Accelerometer, - // Gyro, +export function setMsgId(msg: ButtplugMessage, id: number) { + for (let [_, entry] of Object.entries(msg)) { + if (entry != undefined) { + entry.Id = id; + return; + } + } + throw new ButtplugMessageError(`Message ${msg} does not have an ID.`); } -export class GenericDeviceMessageAttributes { - public FeatureDescriptor: string; - public ActuatorType: ActuatorType; - public StepCount: number; - public Index = 0; - constructor(data: Partial) { - Object.assign(this, data); - } +export interface Ok { + Id: number | undefined; } -export class RawDeviceMessageAttributes { - constructor(public Endpoints: Array) {} -} - -export class SensorDeviceMessageAttributes { - public FeatureDescriptor: string; - public SensorType: SensorType; - public StepRange: Array; - public Index = 0; - constructor(data: Partial) { - Object.assign(this, data); - } -} - -export abstract class ButtplugMessage { - constructor(public Id: number) {} - - // tslint:disable-next-line:ban-types - public get Type(): Function { - return this.constructor; - } - - public toJSON(): string { - return JSON.stringify(this.toProtocolFormat()); - } - - public toProtocolFormat(): object { - const jsonObj = {}; - jsonObj[(this.constructor as unknown as { Name: string }).Name] = - instanceToPlain(this); - return jsonObj; - } - - public update() {} -} - -export abstract class ButtplugDeviceMessage extends ButtplugMessage { - constructor( - public DeviceIndex: number, - public Id: number, - ) { - super(Id); - } -} - -export abstract class ButtplugSystemMessage extends ButtplugMessage { - constructor(public Id: number = SYSTEM_MESSAGE_ID) { - super(Id); - } -} - -export class Ok extends ButtplugSystemMessage { - static Name = "Ok"; - - constructor(public Id: number = DEFAULT_MESSAGE_ID) { - super(Id); - } -} - -export class Ping extends ButtplugMessage { - static Name = "Ping"; - - constructor(public Id: number = DEFAULT_MESSAGE_ID) { - super(Id); - } +export interface Ping { + Id: number | undefined; } export enum ErrorClass { - ERROR_UNKNOWN, - ERROR_INIT, - ERROR_PING, - ERROR_MSG, - ERROR_DEVICE, + ERROR_UNKNOWN, + ERROR_INIT, + ERROR_PING, + ERROR_MSG, + ERROR_DEVICE, } -export class Error extends ButtplugMessage { - static Name = "Error"; - - constructor( - public ErrorMessage: string, - public ErrorCode: ErrorClass = ErrorClass.ERROR_UNKNOWN, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(Id); - } - - get Schemversion() { - return 0; - } +export interface Error { + ErrorMessage: string; + ErrorCode: ErrorClass; + Id: number | undefined; } -export class DeviceInfo { - public DeviceIndex: number; - public DeviceName: string; - @Type(() => MessageAttributes) - public DeviceMessages: MessageAttributes; - public DeviceDisplayName?: string; - public DeviceMessageTimingGap?: number; - - constructor(data: Partial) { - Object.assign(this, data); - } +export interface RequestDeviceList { + Id: number | undefined; } -export class DeviceList extends ButtplugMessage { - static Name = "DeviceList"; - - @Type(() => DeviceInfo) - public Devices: DeviceInfo[]; - public Id: number; - - constructor(devices: DeviceInfo[], id: number = DEFAULT_MESSAGE_ID) { - super(id); - this.Devices = devices; - this.Id = id; - } - - public update() { - for (const device of this.Devices) { - device.DeviceMessages.update(); - } - } +export interface StartScanning { + Id: number | undefined; } -export class DeviceAdded extends ButtplugSystemMessage { - static Name = "DeviceAdded"; - - public DeviceIndex: number; - public DeviceName: string; - @Type(() => MessageAttributes) - public DeviceMessages: MessageAttributes; - public DeviceDisplayName?: string; - public DeviceMessageTimingGap?: number; - - constructor(data: Partial) { - super(); - Object.assign(this, data); - } - - public update() { - this.DeviceMessages.update(); - } +export interface StopScanning { + Id: number | undefined; } -export class DeviceRemoved extends ButtplugSystemMessage { - static Name = "DeviceRemoved"; - - constructor(public DeviceIndex: number) { - super(); - } +export interface StopAllDevices { + Id: number | undefined; } -export class RequestDeviceList extends ButtplugMessage { - static Name = "RequestDeviceList"; - - constructor(public Id: number = DEFAULT_MESSAGE_ID) { - super(Id); - } +export interface ScanningFinished { + Id: number | undefined; } -export class StartScanning extends ButtplugMessage { - static Name = "StartScanning"; - - constructor(public Id: number = DEFAULT_MESSAGE_ID) { - super(Id); - } +export interface RequestServerInfo { + ClientName: string; + ProtocolVersionMajor: number; + ProtocolVersionMinor: number; + Id: number | undefined; } -export class StopScanning extends ButtplugMessage { - static Name = "StopScanning"; - - constructor(public Id: number = DEFAULT_MESSAGE_ID) { - super(Id); - } +export interface ServerInfo { + MaxPingTime: number; + ServerName: string; + ProtocolVersionMajor: number; + ProtocolVersionMinor: number; + Id: number | undefined; } -export class ScanningFinished extends ButtplugSystemMessage { - static Name = "ScanningFinished"; - - constructor() { - super(); - } +export interface DeviceFeature { + FeatureDescriptor: string; + Output: { [key: string]: DeviceFeatureOutput }; + Input: { [key: string]: DeviceFeatureInput }; + FeatureIndex: number; } -export class RequestServerInfo extends ButtplugMessage { - static Name = "RequestServerInfo"; - - constructor( - public ClientName: string, - public MessageVersion: number = 0, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(Id); - } +export interface DeviceInfo { + DeviceIndex: number; + DeviceName: string; + DeviceFeatures: { [key: number]: DeviceFeature }; + DeviceDisplayName?: string; + DeviceMessageTimingGap?: number; } -export class ServerInfo extends ButtplugSystemMessage { - static Name = "ServerInfo"; - - constructor( - public MessageVersion: number, - public MaxPingTime: number, - public ServerName: string, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(); - } +export interface DeviceList { + Devices: { [key: number]: DeviceInfo }; + Id: number | undefined; } -export class StopDeviceCmd extends ButtplugDeviceMessage { - static Name = "StopDeviceCmd"; - - constructor( - public DeviceIndex: number = -1, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } +export enum OutputType { + Unknown = 'Unknown', + Vibrate = 'Vibrate', + Rotate = 'Rotate', + Oscillate = 'Oscillate', + Constrict = 'Constrict', + Inflate = 'Inflate', + Position = 'Position', + HwPositionWithDuration = 'HwPositionWithDuration', + Temperature = 'Temperature', + Spray = 'Spray', + Led = 'Led', } -export class StopAllDevices extends ButtplugMessage { - static Name = "StopAllDevices"; - - constructor(public Id: number = DEFAULT_MESSAGE_ID) { - super(Id); - } +export enum InputType { + Unknown = 'Unknown', + Battery = 'Battery', + RSSI = 'RSSI', + Button = 'Button', + Pressure = 'Pressure', + // Temperature, + // Accelerometer, + // Gyro, } -export class GenericMessageSubcommand { - protected constructor(public Index: number) {} +export enum InputCommandType { + Read = 'Read', + Subscribe = 'Subscribe', + Unsubscribe = 'Unsubscribe', } -export class ScalarSubcommand extends GenericMessageSubcommand { - constructor( - Index: number, - public Scalar: number, - public ActuatorType: ActuatorType, - ) { - super(Index); - } +export interface DeviceFeatureInput { + Value: number[]; + Command: InputCommandType[]; } -export class ScalarCmd extends ButtplugDeviceMessage { - static Name = "ScalarCmd"; - - constructor( - public Scalars: ScalarSubcommand[], - public DeviceIndex: number = -1, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } +export interface DeviceFeatureOutput { + Value: number; + Duration?: number; } -export class RotateSubcommand extends GenericMessageSubcommand { - constructor( - Index: number, - public Speed: number, - public Clockwise: boolean, - ) { - super(Index); - } +export interface OutputCmd { + DeviceIndex: number; + FeatureIndex: number; + Command: { [key: string]: DeviceFeatureOutput }; + Id: number | undefined; } -export class RotateCmd extends ButtplugDeviceMessage { - static Name = "RotateCmd"; +// Device Input Commands - public static Create( - deviceIndex: number, - commands: [number, boolean][], - ): RotateCmd { - const cmdList: RotateSubcommand[] = new Array(); - - let i = 0; - for (const [speed, clockwise] of commands) { - cmdList.push(new RotateSubcommand(i, speed, clockwise)); - ++i; - } - - return new RotateCmd(cmdList, deviceIndex); - } - constructor( - public Rotations: RotateSubcommand[], - public DeviceIndex: number = -1, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } +export interface InputCmd { + DeviceIndex: number; + FeatureIndex: number; + Type: InputType; + Command: InputCommandType; + Id: number | undefined; } -export class VectorSubcommand extends GenericMessageSubcommand { - constructor( - Index: number, - public Position: number, - public Duration: number, - ) { - super(Index); - } +export interface InputValue { + Value: number; } -export class LinearCmd extends ButtplugDeviceMessage { - static Name = "LinearCmd"; - - public static Create( - deviceIndex: number, - commands: [number, number][], - ): LinearCmd { - const cmdList: VectorSubcommand[] = new Array(); - - let i = 0; - for (const cmd of commands) { - cmdList.push(new VectorSubcommand(i, cmd[0], cmd[1])); - ++i; - } - - return new LinearCmd(cmdList, deviceIndex); - } - constructor( - public Vectors: VectorSubcommand[], - public DeviceIndex: number = -1, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } +export interface InputReading { + DeviceIndex: number; + FeatureIndex: number; + Reading: { [key: string]: InputValue }; + Id: number | undefined; } -export class SensorReadCmd extends ButtplugDeviceMessage { - static Name = "SensorReadCmd"; - - constructor( - public DeviceIndex: number, - public SensorIndex: number, - public SensorType: SensorType, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } -} - -export class SensorReading extends ButtplugDeviceMessage { - static Name = "SensorReading"; - - constructor( - public DeviceIndex: number, - public SensorIndex: number, - public SensorType: SensorType, - public Data: number[], - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } -} - -export class RawReadCmd extends ButtplugDeviceMessage { - static Name = "RawReadCmd"; - - constructor( - public DeviceIndex: number, - public Endpoint: string, - public ExpectedLength: number, - public Timeout: number, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } -} - -export class RawWriteCmd extends ButtplugDeviceMessage { - static Name = "RawWriteCmd"; - - constructor( - public DeviceIndex: number, - public Endpoint: string, - public Data: Uint8Array, - public WriteWithResponse: boolean, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } -} - -export class RawSubscribeCmd extends ButtplugDeviceMessage { - static Name = "RawSubscribeCmd"; - - constructor( - public DeviceIndex: number, - public Endpoint: string, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } -} - -export class RawUnsubscribeCmd extends ButtplugDeviceMessage { - static Name = "RawUnsubscribeCmd"; - - constructor( - public DeviceIndex: number, - public Endpoint: string, - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } -} - -export class RawReading extends ButtplugDeviceMessage { - static Name = "RawReading"; - - constructor( - public DeviceIndex: number, - public Endpoint: string, - public Data: number[], - public Id: number = DEFAULT_MESSAGE_ID, - ) { - super(DeviceIndex, Id); - } +export interface StopCmd { + Id: number | undefined; + DeviceIndex: number | undefined; + FeatureIndex: number | undefined; + Inputs: boolean | undefined; + Outputs: boolean | undefined; } diff --git a/packages/buttplug/src/index.ts b/packages/buttplug/src/index.ts index 4fb4835..619369a 100644 --- a/packages/buttplug/src/index.ts +++ b/packages/buttplug/src/index.ts @@ -1,88 +1,95 @@ -import { ButtplugMessage } from "./core/Messages"; -import { IButtplugClientConnector } from "./client/IButtplugClientConnector"; -import { fromJSON } from "./core/MessageUtils"; -import { EventEmitter } from "eventemitter3"; +/*! + * Buttplug JS Source Code File - Visit https://buttplug.io for more info about + * the project. Licensed under the BSD 3-Clause license. See LICENSE file in the + * project root for full license information. + * + * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. + */ -export * from "./client/Client"; -export * from "./client/ButtplugClientDevice"; -export * from "./client/ButtplugBrowserWebsocketClientConnector"; -export * from "./client/ButtplugNodeWebsocketClientConnector"; -export * from "./client/ButtplugClientConnectorException"; -export * from "./utils/ButtplugMessageSorter"; -export * from "./client/IButtplugClientConnector"; -export * from "./core/Messages"; -export * from "./core/MessageUtils"; -export * from "./core/Logging"; -export * from "./core/Exceptions"; +import { ButtplugMessage } from './core/Messages'; +import { IButtplugClientConnector } from './client/IButtplugClientConnector'; +import { EventEmitter } from 'eventemitter3'; + +export * from './client/ButtplugClient'; +export * from './client/ButtplugClientDevice'; +export * from './client/ButtplugBrowserWebsocketClientConnector'; +export * from './client/ButtplugNodeWebsocketClientConnector'; +export * from './client/ButtplugClientConnectorException'; +export * from './utils/ButtplugMessageSorter'; +export * from './client/ButtplugClientDeviceCommand'; +export * from './client/ButtplugClientDeviceFeature'; +export * from './client/IButtplugClientConnector'; +export * from './core/Messages'; +export * from './core/Logging'; +export * from './core/Exceptions'; export class ButtplugWasmClientConnector - extends EventEmitter - implements IButtplugClientConnector + extends EventEmitter + implements IButtplugClientConnector { - private static _loggingActivated = false; - private static wasmInstance; - private _connected: boolean = false; - private client; - private serverPtr; + private static _loggingActivated = false; + private static wasmInstance; + private _connected: boolean = false; + private client; + private serverPtr; - constructor() { - super(); - } + constructor() { + super(); + } - public get Connected(): boolean { - return this._connected; - } + public get Connected(): boolean { + return this._connected; + } - private static maybeLoadWasm = async () => { - if (ButtplugWasmClientConnector.wasmInstance == undefined) { - ButtplugWasmClientConnector.wasmInstance = await import( - "../wasm/index.js" - ); - } - }; + private static maybeLoadWasm = async () => { + if (ButtplugWasmClientConnector.wasmInstance == undefined) { + ButtplugWasmClientConnector.wasmInstance = await import( + '../wasm/index.js' + ); + } + }; - public static activateLogging = async (logLevel: string = "debug") => { - await ButtplugWasmClientConnector.maybeLoadWasm(); - if (this._loggingActivated) { - console.log("Logging already activated, ignoring."); - return; - } - console.log("Turning on logging."); - ButtplugWasmClientConnector.wasmInstance.buttplug_activate_env_logger( - logLevel, - ); - }; + public static activateLogging = async (logLevel: string = 'debug') => { + await ButtplugWasmClientConnector.maybeLoadWasm(); + if (this._loggingActivated) { + console.log('Logging already activated, ignoring.'); + return; + } + console.log('Turning on logging.'); + ButtplugWasmClientConnector.wasmInstance.buttplug_activate_env_logger( + logLevel, + ); + }; - public initialize = async (): Promise => {}; + public initialize = async (): Promise => {}; - public connect = async (): Promise => { - await ButtplugWasmClientConnector.maybeLoadWasm(); - //ButtplugWasmClientConnector.wasmInstance.buttplug_activate_env_logger('debug'); - this.client = - ButtplugWasmClientConnector.wasmInstance.buttplug_create_embedded_wasm_server( - (msgs) => { - this.emitMessage(msgs); - }, - this.serverPtr, - ); - this._connected = true; - }; + public connect = async (): Promise => { + await ButtplugWasmClientConnector.maybeLoadWasm(); + this.client = + ButtplugWasmClientConnector.wasmInstance.buttplug_create_embedded_wasm_server( + (msgs) => { + this.emitMessage(msgs); + }, + this.serverPtr, + ); + this._connected = true; + }; - public disconnect = async (): Promise => {}; + public disconnect = async (): Promise => {}; - public send = (msg: ButtplugMessage): void => { - ButtplugWasmClientConnector.wasmInstance.buttplug_client_send_json_message( - this.client, - new TextEncoder().encode("[" + msg.toJSON() + "]"), - (output) => { - this.emitMessage(output); - }, - ); - }; + public send = (msg: ButtplugMessage): void => { + ButtplugWasmClientConnector.wasmInstance.buttplug_client_send_json_message( + this.client, + new TextEncoder().encode('[' + JSON.stringify(msg) + ']'), + (output) => { + this.emitMessage(output); + }, + ); + }; - private emitMessage = (msg: Uint8Array) => { - const str = new TextDecoder().decode(msg); - // This needs to use buttplug-js's fromJSON, otherwise we won't resolve the message name correctly. - this.emit("message", fromJSON(str)); - }; + private emitMessage = (msg: Uint8Array) => { + const str = new TextDecoder().decode(msg); + const msgs: ButtplugMessage[] = JSON.parse(str); + this.emit('message', msgs); + }; } diff --git a/packages/buttplug/src/lib.rs b/packages/buttplug/src/lib.rs index e677bdb..0a905e4 100644 --- a/packages/buttplug/src/lib.rs +++ b/packages/buttplug/src/lib.rs @@ -8,12 +8,16 @@ mod webbluetooth; use js_sys; use tokio_stream::StreamExt; use crate::webbluetooth::{WebBluetoothCommunicationManagerBuilder}; -use buttplug::{ - core::message::{ButtplugServerMessageCurrent,serializer::vec_to_protocol_json}, - server::{ButtplugServerBuilder,ButtplugServerDowngradeWrapper,device::{ServerDeviceManagerBuilder,configuration::{DeviceConfigurationManager}}}, - util::async_manager, core::message::{BUTTPLUG_CURRENT_MESSAGE_SPEC_VERSION, ButtplugServerMessageVariant, serializer::{ButtplugSerializedMessage, ButtplugMessageSerializer, ButtplugServerJSONSerializer}}, - util::device_configuration::load_protocol_configs +use buttplug_core::{ + message::{ButtplugServerMessageCurrent, BUTTPLUG_CURRENT_API_MAJOR_VERSION, serializer::{ButtplugSerializedMessage, ButtplugMessageSerializer}}, + util::async_manager, }; +use buttplug_server::{ + ButtplugServerBuilder, ButtplugServer, + device::ServerDeviceManagerBuilder, + message::{ButtplugServerMessageVariant, serializer::ButtplugServerJSONSerializer}, +}; +use buttplug_server_device_config::{DeviceConfigurationManager, load_protocol_configs}; type FFICallback = js_sys::Function; type FFICallbackContext = u32; @@ -33,16 +37,17 @@ use wasm_bindgen::prelude::*; use std::sync::Arc; use js_sys::Uint8Array; -pub type ButtplugWASMServer = Arc; +pub type ButtplugWASMServer = Arc; pub fn send_server_message( message: &ButtplugServerMessageCurrent, callback: &FFICallback, ) { - let msg_array = [message.clone()]; - let json_msg = vec_to_protocol_json(&msg_array); - let buf = json_msg.as_bytes(); - { + let serializer = ButtplugServerJSONSerializer::default(); + serializer.force_message_version(&BUTTPLUG_CURRENT_API_MAJOR_VERSION); + let json_msg = serializer.serialize(&[ButtplugServerMessageVariant::V4(message.clone())]); + if let ButtplugSerializedMessage::Text(json) = json_msg { + let buf = json.as_bytes(); let this = JsValue::null(); let uint8buf = unsafe { Uint8Array::new(&Uint8Array::view(buf)) }; callback.call1(&this, &JsValue::from(uint8buf)); @@ -50,10 +55,9 @@ pub fn send_server_message( } #[no_mangle] -pub fn create_test_dcm(allow_raw_messages: bool) -> DeviceConfigurationManager { +pub fn create_test_dcm(_allow_raw_messages: bool) -> DeviceConfigurationManager { load_protocol_configs(&None, &None, false) .expect("If this fails, the whole library goes with it.") - .allow_raw_messages(allow_raw_messages) .finish() .expect("If this fails, the whole library goes with it.") } @@ -68,18 +72,17 @@ pub fn buttplug_create_embedded_wasm_server( let mut sdm = ServerDeviceManagerBuilder::new(dcm); sdm.comm_manager(WebBluetoothCommunicationManagerBuilder::default()); let builder = ButtplugServerBuilder::new(sdm.finish().unwrap()); - let server = builder.finish().unwrap(); - let wrapper = Arc::new(ButtplugServerDowngradeWrapper::new(server)); - let event_stream = wrapper.server_version_event_stream(); + let server = Arc::new(builder.finish().unwrap()); + let event_stream = server.server_version_event_stream(); let callback = callback.clone(); async_manager::spawn(async move { pin_mut!(event_stream); while let Some(message) = event_stream.next().await { - send_server_message(&ButtplugServerMessageCurrent::try_from(message).unwrap(), &callback); + send_server_message(&message, &callback); } }); - - Box::into_raw(Box::new(wrapper)) + + Box::into_raw(Box::new(server)) } #[no_mangle] @@ -106,15 +109,18 @@ pub fn buttplug_client_send_json_message( }; let callback = callback.clone(); let serializer = ButtplugServerJSONSerializer::default(); - serializer.force_message_version(&BUTTPLUG_CURRENT_MESSAGE_SPEC_VERSION); + serializer.force_message_version(&BUTTPLUG_CURRENT_API_MAJOR_VERSION); let input_msg = serializer.deserialize(&ButtplugSerializedMessage::Text(std::str::from_utf8(buf).unwrap().to_owned())).unwrap(); async_manager::spawn(async move { let msg = input_msg[0].clone(); let response = server.parse_message(msg).await.unwrap(); - if let ButtplugServerMessageVariant::V3(response) = response { - send_server_message(&response, &callback); + let json_msg = serializer.serialize(&[response]); + if let ButtplugSerializedMessage::Text(json) = json_msg { + let buf = json.as_bytes(); + let this = JsValue::null(); + let uint8buf = unsafe { Uint8Array::new(&Uint8Array::view(buf)) }; + callback.call1(&this, &JsValue::from(uint8buf)); } - }); } diff --git a/packages/buttplug/src/utils/ButtplugBrowserWebsocketConnector.ts b/packages/buttplug/src/utils/ButtplugBrowserWebsocketConnector.ts index ea818df..76a87ef 100644 --- a/packages/buttplug/src/utils/ButtplugBrowserWebsocketConnector.ts +++ b/packages/buttplug/src/utils/ButtplugBrowserWebsocketConnector.ts @@ -6,86 +6,83 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -"use strict"; +'use strict'; -import { EventEmitter } from "eventemitter3"; -import { ButtplugMessage } from "../core/Messages"; -import { fromJSON } from "../core/MessageUtils"; +import { EventEmitter } from 'eventemitter3'; +import { ButtplugMessage } from '../core/Messages'; export class ButtplugBrowserWebsocketConnector extends EventEmitter { - protected _ws: WebSocket | undefined; - protected _websocketConstructor: typeof WebSocket | null = null; + protected _ws: WebSocket | undefined; + protected _websocketConstructor: typeof WebSocket | null = null; - public constructor(private _url: string) { - super(); - } + public constructor(private _url: string) { + super(); + } - public get Connected(): boolean { - return this._ws !== undefined; - } + public get Connected(): boolean { + return this._ws !== undefined; + } - public connect = async (): Promise => { - return new Promise((resolve, reject) => { - const ws = new (this._websocketConstructor ?? WebSocket)(this._url); - const onErrorCallback = (event: Event) => { - reject(event); - }; - const onCloseCallback = (event: CloseEvent) => reject(event.reason); - ws.addEventListener("open", async () => { - this._ws = ws; - try { - await this.initialize(); - this._ws.addEventListener("message", (msg) => { - this.parseIncomingMessage(msg); - }); - this._ws.removeEventListener("close", onCloseCallback); - this._ws.removeEventListener("error", onErrorCallback); - this._ws.addEventListener("close", this.disconnect); - resolve(); - } catch (e) { - reject(e); - } - }); - // In websockets, our error rarely tells us much, as for security reasons - // browsers usually only throw Error Code 1006. It's up to those using this - // library to state what the problem might be. + public connect = async (): Promise => { + return new Promise((resolve, reject) => { + const ws = new (this._websocketConstructor ?? WebSocket)(this._url); + const onErrorCallback = (event: Event) => {reject(event)} + const onCloseCallback = (event: CloseEvent) => reject(event.reason) + ws.addEventListener('open', async () => { + this._ws = ws; + try { + await this.initialize(); + this._ws.addEventListener('message', (msg) => { + this.parseIncomingMessage(msg); + }); + this._ws.removeEventListener('close', onCloseCallback); + this._ws.removeEventListener('error', onErrorCallback); + this._ws.addEventListener('close', this.disconnect); + resolve(); + } catch (e) { + reject(e); + } + }); + // In websockets, our error rarely tells us much, as for security reasons + // browsers usually only throw Error Code 1006. It's up to those using this + // library to state what the problem might be. - ws.addEventListener("error", onErrorCallback); - ws.addEventListener("close", onCloseCallback); - }); - }; + ws.addEventListener('error', onErrorCallback) + ws.addEventListener('close', onCloseCallback); + }); + }; - public disconnect = async (): Promise => { - if (!this.Connected) { - return; - } - this._ws!.close(); - this._ws = undefined; - this.emit("disconnect"); - }; + public disconnect = async (): Promise => { + if (!this.Connected) { + return; + } + this._ws!.close(); + this._ws = undefined; + this.emit('disconnect'); + }; - public sendMessage(msg: ButtplugMessage) { - if (!this.Connected) { - throw new Error("ButtplugBrowserWebsocketConnector not connected"); - } - this._ws!.send("[" + msg.toJSON() + "]"); - } + public sendMessage(msg: ButtplugMessage) { + if (!this.Connected) { + throw new Error('ButtplugBrowserWebsocketConnector not connected'); + } + this._ws!.send('[' + JSON.stringify(msg) + ']'); + } - public initialize = async (): Promise => { - return Promise.resolve(); - }; + public initialize = async (): Promise => { + return Promise.resolve(); + }; - protected parseIncomingMessage(event: MessageEvent) { - if (typeof event.data === "string") { - const msgs = fromJSON(event.data); - this.emit("message", msgs); - } else if (event.data instanceof Blob) { - // No-op, we only use text message types. - } - } + protected parseIncomingMessage(event: MessageEvent) { + if (typeof event.data === 'string') { + const msgs: ButtplugMessage[] = JSON.parse(event.data); + this.emit('message', msgs); + } else if (event.data instanceof Blob) { + // No-op, we only use text message types. + } + } - protected onReaderLoad(event: Event) { - const msgs = fromJSON((event.target as FileReader).result); - this.emit("message", msgs); - } + protected onReaderLoad(event: Event) { + const msgs: ButtplugMessage[] = JSON.parse((event.target as FileReader).result as string); + this.emit('message', msgs); + } } diff --git a/packages/buttplug/src/utils/ButtplugMessageSorter.ts b/packages/buttplug/src/utils/ButtplugMessageSorter.ts index adc49e3..ba4cc10 100644 --- a/packages/buttplug/src/utils/ButtplugMessageSorter.ts +++ b/packages/buttplug/src/utils/ButtplugMessageSorter.ts @@ -6,60 +6,61 @@ * @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved. */ -import * as Messages from "../core/Messages"; -import { ButtplugError } from "../core/Exceptions"; +import * as Messages from '../core/Messages'; +import { ButtplugError } from '../core/Exceptions'; export class ButtplugMessageSorter { - protected _counter = 1; - protected _waitingMsgs: Map< - number, - [(val: Messages.ButtplugMessage) => void, (err: Error) => void] - > = new Map(); + protected _counter = 1; + protected _waitingMsgs: Map< + number, + [(val: Messages.ButtplugMessage) => void, (err: Error) => void] + > = new Map(); - public constructor(private _useCounter: boolean) {} + public constructor(private _useCounter: boolean) {} - // One of the places we should actually return a promise, as we need to store - // them while waiting for them to return across the line. - // tslint:disable:promise-function-async - public PrepareOutgoingMessage( - msg: Messages.ButtplugMessage, - ): Promise { - if (this._useCounter) { - msg.Id = this._counter; - // Always increment last, otherwise we might lose sync - this._counter += 1; - } - let res; - let rej; - const msgPromise = new Promise( - (resolve, reject) => { - res = resolve; - rej = reject; - }, - ); - this._waitingMsgs.set(msg.Id, [res, rej]); - return msgPromise; - } + // One of the places we should actually return a promise, as we need to store + // them while waiting for them to return across the line. + // tslint:disable:promise-function-async + public PrepareOutgoingMessage( + msg: Messages.ButtplugMessage + ): Promise { + if (this._useCounter) { + Messages.setMsgId(msg, this._counter); + // Always increment last, otherwise we might lose sync + this._counter += 1; + } + let res; + let rej; + const msgPromise = new Promise( + (resolve, reject) => { + res = resolve; + rej = reject; + } + ); + this._waitingMsgs.set(Messages.msgId(msg), [res, rej]); + return msgPromise; + } - public ParseIncomingMessages( - msgs: Messages.ButtplugMessage[], - ): Messages.ButtplugMessage[] { - const noMatch: Messages.ButtplugMessage[] = []; - for (const x of msgs) { - if (x.Id !== Messages.SYSTEM_MESSAGE_ID && this._waitingMsgs.has(x.Id)) { - const [res, rej] = this._waitingMsgs.get(x.Id)!; - // If we've gotten back an error, reject the related promise using a - // ButtplugException derived type. - if (x.Type === Messages.Error) { - rej(ButtplugError.FromError(x as Messages.Error)); - continue; - } - res(x); - continue; - } else { - noMatch.push(x); - } - } - return noMatch; - } + public ParseIncomingMessages( + msgs: Messages.ButtplugMessage[] + ): Messages.ButtplugMessage[] { + const noMatch: Messages.ButtplugMessage[] = []; + for (const x of msgs) { + let id = Messages.msgId(x); + if (id !== Messages.SYSTEM_MESSAGE_ID && this._waitingMsgs.has(id)) { + const [res, rej] = this._waitingMsgs.get(id)!; + // If we've gotten back an error, reject the related promise using a + // ButtplugException derived type. + if (x.Error !== undefined) { + rej(ButtplugError.FromError(x.Error!)); + continue; + } + res(x); + continue; + } else { + noMatch.push(x); + } + } + return noMatch; + } } diff --git a/packages/buttplug/src/webbluetooth/webbluetooth_hardware.rs b/packages/buttplug/src/webbluetooth/webbluetooth_hardware.rs index cbd18a3..94953f3 100644 --- a/packages/buttplug/src/webbluetooth/webbluetooth_hardware.rs +++ b/packages/buttplug/src/webbluetooth/webbluetooth_hardware.rs @@ -1,25 +1,17 @@ use async_trait::async_trait; -use buttplug::{ - core::{ - errors::ButtplugDeviceError, - message::Endpoint, - }, - server::device::{ - configuration::{BluetoothLESpecifier, ProtocolCommunicationSpecifier}, - hardware::{ - Hardware, - HardwareConnector, - HardwareEvent, - HardwareInternal, - HardwareReadCmd, - HardwareReading, - HardwareSpecializer, - HardwareSubscribeCmd, - HardwareUnsubscribeCmd, - HardwareWriteCmd, - }, -}, - util::future::{ButtplugFuture, ButtplugFutureStateShared}, +use buttplug_core::errors::ButtplugDeviceError; +use buttplug_server_device_config::{BluetoothLESpecifier, Endpoint, ProtocolCommunicationSpecifier}; +use buttplug_server::device::hardware::{ + Hardware, + HardwareConnector, + HardwareEvent, + HardwareInternal, + HardwareReadCmd, + HardwareReading, + HardwareSpecializer, + HardwareSubscribeCmd, + HardwareUnsubscribeCmd, + HardwareWriteCmd, }; use futures::future::{self, BoxFuture}; use js_sys::{DataView, Uint8Array}; @@ -28,7 +20,7 @@ use std::{ convert::TryFrom, fmt::{self, Debug}, }; -use tokio::sync::{broadcast, mpsc}; +use tokio::sync::{broadcast, mpsc, oneshot}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::{spawn_local, JsFuture}; @@ -41,9 +33,6 @@ use web_sys::{ MessageEvent, }; -type WebBluetoothResultFuture = ButtplugFuture>; -type WebBluetoothReadResultFuture = ButtplugFuture>; - struct BluetoothDeviceWrapper { pub device: BluetoothDevice } @@ -179,7 +168,7 @@ impl HardwareSpecializer for WebBluetoothHardwareSpecializer { receiver, command_sender, )); - Ok(Hardware::new(&name, &address, &[], device_impl)) + Ok(Hardware::new(&name, &address, &[], &None, false, device_impl)) } WebBluetoothEvent::Disconnected => Err( ButtplugDeviceError::DeviceCommunicationError( @@ -202,19 +191,19 @@ pub enum WebBluetoothEvent { pub enum WebBluetoothDeviceCommand { Write( HardwareWriteCmd, - ButtplugFutureStateShared>, + oneshot::Sender>, ), Read( HardwareReadCmd, - ButtplugFutureStateShared>, + oneshot::Sender>, ), Subscribe( HardwareSubscribeCmd, - ButtplugFutureStateShared>, + oneshot::Sender>, ), Unsubscribe( HardwareUnsubscribeCmd, - ButtplugFutureStateShared>, + oneshot::Sender>, ), } @@ -287,7 +276,7 @@ async fn run_webbluetooth_loop( .await; while let Some(msg) = device_command_receiver.recv().await { match msg { - WebBluetoothDeviceCommand::Write(write_cmd, waker) => { + WebBluetoothDeviceCommand::Write(write_cmd, sender) => { debug!("Writing to endpoint {:?}", write_cmd.endpoint()); let chr = char_map.get(&write_cmd.endpoint()).unwrap().clone(); spawn_local(async move { @@ -295,10 +284,10 @@ async fn run_webbluetooth_loop( JsFuture::from(chr.write_value_with_u8_array(&uint8buf).unwrap()) .await .unwrap(); - waker.set_reply(Ok(())); + let _ = sender.send(Ok(())); }); } - WebBluetoothDeviceCommand::Read(read_cmd, waker) => { + WebBluetoothDeviceCommand::Read(read_cmd, sender) => { debug!("Writing to endpoint {:?}", read_cmd.endpoint()); let chr = char_map.get(&read_cmd.endpoint()).unwrap().clone(); spawn_local(async move { @@ -307,10 +296,10 @@ async fn run_webbluetooth_loop( let mut body = vec![0; data_view.byte_length() as usize]; Uint8Array::new(&data_view).copy_to(&mut body[..]); let reading = HardwareReading::new(read_cmd.endpoint(), &body); - waker.set_reply(Ok(reading)); + let _ = sender.send(Ok(reading)); }); } - WebBluetoothDeviceCommand::Subscribe(subscribe_cmd, waker) => { + WebBluetoothDeviceCommand::Subscribe(subscribe_cmd, sender) => { debug!("Subscribing to endpoint {:?}", subscribe_cmd.endpoint()); let chr = char_map.get(&subscribe_cmd.endpoint()).unwrap().clone(); let ep = subscribe_cmd.endpoint(); @@ -335,10 +324,10 @@ async fn run_webbluetooth_loop( spawn_local(async move { JsFuture::from(chr.start_notifications()).await.unwrap(); debug!("Endpoint subscribed"); - waker.set_reply(Ok(())); + let _ = sender.send(Ok(())); }); } - WebBluetoothDeviceCommand::Unsubscribe(_unsubscribe_cmd, _waker) => {} + WebBluetoothDeviceCommand::Unsubscribe(_unsubscribe_cmd, _sender) => {} } } debug!("run_webbluetooth_loop exited!"); @@ -388,12 +377,13 @@ impl HardwareInternal for WebBluetoothHardware { let sender = self.device_command_sender.clone(); let msg = msg.clone(); Box::pin(async move { - let fut = WebBluetoothReadResultFuture::default(); - let waker = fut.get_state_clone(); - sender - .send(WebBluetoothDeviceCommand::Read(msg, waker)) + let (tx, rx) = oneshot::channel(); + let _ = sender + .send(WebBluetoothDeviceCommand::Read(msg, tx)) .await; - fut.await + rx.await.unwrap_or(Err(ButtplugDeviceError::DeviceCommunicationError( + "Device command channel closed".to_string(), + ))) }) } @@ -401,12 +391,13 @@ impl HardwareInternal for WebBluetoothHardware { let sender = self.device_command_sender.clone(); let msg = msg.clone(); Box::pin(async move { - let fut = WebBluetoothResultFuture::default(); - let waker = fut.get_state_clone(); - sender - .send(WebBluetoothDeviceCommand::Write(msg.clone(), waker)) + let (tx, rx) = oneshot::channel(); + let _ = sender + .send(WebBluetoothDeviceCommand::Write(msg, tx)) .await; - fut.await + rx.await.unwrap_or(Err(ButtplugDeviceError::DeviceCommunicationError( + "Device command channel closed".to_string(), + ))) }) } @@ -414,12 +405,13 @@ impl HardwareInternal for WebBluetoothHardware { let sender = self.device_command_sender.clone(); let msg = msg.clone(); Box::pin(async move { - let fut = WebBluetoothResultFuture::default(); - let waker = fut.get_state_clone(); - sender - .send(WebBluetoothDeviceCommand::Subscribe(msg.clone(), waker)) + let (tx, rx) = oneshot::channel(); + let _ = sender + .send(WebBluetoothDeviceCommand::Subscribe(msg, tx)) .await; - fut.await + rx.await.unwrap_or(Err(ButtplugDeviceError::DeviceCommunicationError( + "Device command channel closed".to_string(), + ))) }) } diff --git a/packages/buttplug/src/webbluetooth/webbluetooth_manager.rs b/packages/buttplug/src/webbluetooth/webbluetooth_manager.rs index dfb6414..f21a4c7 100644 --- a/packages/buttplug/src/webbluetooth/webbluetooth_manager.rs +++ b/packages/buttplug/src/webbluetooth/webbluetooth_manager.rs @@ -1,14 +1,10 @@ use super::webbluetooth_hardware::WebBluetoothHardwareConnector; -use buttplug::{ - core::ButtplugResultFuture, - server::device::{ - configuration::{ProtocolCommunicationSpecifier}, - hardware::communication::{ - HardwareCommunicationManager, HardwareCommunicationManagerBuilder, - HardwareCommunicationManagerEvent, - }, - } +use buttplug_core::ButtplugResultFuture; +use buttplug_server_device_config::ProtocolCommunicationSpecifier; +use buttplug_server::device::hardware::communication::{ + HardwareCommunicationManager, HardwareCommunicationManagerBuilder, + HardwareCommunicationManagerEvent, }; use futures::future; use js_sys::Array; @@ -69,8 +65,8 @@ impl HardwareCommunicationManager for WebBluetoothCommunicationManager { let options = web_sys::RequestDeviceOptions::new(); let filters = Array::new(); let optional_services = Array::new(); - for vals in config_manager.protocol_device_configurations().iter() { - for config in vals.1 { + for vals in config_manager.base_communication_specifiers().iter() { + for config in vals.1.iter() { if let ProtocolCommunicationSpecifier::BluetoothLE(btle) = &config { for name in btle.names() { let filter = web_sys::BluetoothLeScanFilterInit::new(); diff --git a/packages/buttplug/tsconfig.json b/packages/buttplug/tsconfig.json index e1bc0b0..c6ec8e4 100644 --- a/packages/buttplug/tsconfig.json +++ b/packages/buttplug/tsconfig.json @@ -5,9 +5,7 @@ "outDir": "dist", "moduleResolution": "bundler", "esModuleInterop": true, - "skipLibCheck": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true + "skipLibCheck": true }, "include": ["src"] } diff --git a/packages/buttplug/vite.config.ts b/packages/buttplug/vite.config.ts index c62e188..839447e 100644 --- a/packages/buttplug/vite.config.ts +++ b/packages/buttplug/vite.config.ts @@ -14,5 +14,8 @@ export default defineConfig({ minify: false, // for demo purposes target: "esnext", // this is important as well outDir: "dist", + rollupOptions: { + external: [/\.\/wasm\//, /\.\.\/wasm\//], + }, }, }); diff --git a/packages/frontend/src/lib/components/device-card/device-card.svelte b/packages/frontend/src/lib/components/device-card/device-card.svelte index 738afb8..7c448dd 100644 --- a/packages/frontend/src/lib/components/device-card/device-card.svelte +++ b/packages/frontend/src/lib/components/device-card/device-card.svelte @@ -5,7 +5,6 @@ import { Label } from "$lib/components/ui/label"; import { Card, CardContent, CardHeader } from "$lib/components/ui/card"; import type { BluetoothDevice } from "$lib/types"; import { _ } from "svelte-i18n"; -import { ActuatorType } from "@sexy.pivoine.art/buttplug"; interface Props { device: BluetoothDevice; @@ -16,7 +15,7 @@ interface Props { let { device, onChange, onStop }: Props = $props(); function getBatteryColor(level: number) { - if (!device.info.hasBattery) { + if (!device.hasBattery) { return "text-gray-400"; } if (level > 60) return "text-green-400"; @@ -25,7 +24,7 @@ function getBatteryColor(level: number) { } function getBatteryBgColor(level: number) { - if (!device.info.hasBattery) { + if (!device.hasBattery) { return "bg-gray-400/20"; } if (level > 60) return "bg-green-400/20"; @@ -34,17 +33,13 @@ function getBatteryBgColor(level: number) { } function getScalarAnimations() { - const cmds: [{ ActuatorType: typeof ActuatorType }] = - device.info.messageAttributes.ScalarCmd; - return cmds - .filter((_, i: number) => !!device.actuatorValues[i]) - .map(({ ActuatorType }) => `animate-${ActuatorType.toLowerCase()}`); + return device.actuators + .filter((a) => a.value > 0) + .map((a) => `animate-${a.outputType.toLowerCase()}`); } function isActive() { - const cmds: [{ ActuatorType: typeof ActuatorType }] = - device.info.messageAttributes.ScalarCmd; - return cmds.some((_, i: number) => !!device.actuatorValues[i]); + return device.actuators.some((a) => a.value > 0); } @@ -119,7 +114,7 @@ function isActive() { > {$_("device_card.battery")} - {#if device.info.hasBattery} + {#if device.hasBattery} {device.batteryLevel}% @@ -144,19 +139,19 @@ function isActive() { --> - {#each device.info.messageAttributes.ScalarCmd as scalarCmd} + {#each device.actuators as actuator, idx}
- onChange(scalarCmd.Index, val)} - max={scalarCmd.StepCount} + value={actuator.value} + onValueChange={(val) => onChange(idx, val)} + max={actuator.maxSteps} step={1} />
diff --git a/packages/frontend/src/lib/types.ts b/packages/frontend/src/lib/types.ts index 98daf93..1245e4b 100644 --- a/packages/frontend/src/lib/types.ts +++ b/packages/frontend/src/lib/types.ts @@ -108,12 +108,20 @@ export interface Stats { viewers_count: number; } +export interface DeviceActuator { + featureIndex: number; + outputType: string; + maxSteps: number; + descriptor: string; + value: number; +} + export interface BluetoothDevice { id: string; name: string; - actuatorValues: number[]; - sensorValues: number[]; + actuators: DeviceActuator[]; batteryLevel: number; + hasBattery: boolean; isConnected: boolean; lastSeen: Date; info: ButtplugClientDevice; diff --git a/packages/frontend/src/routes/play/+page.svelte b/packages/frontend/src/routes/play/+page.svelte index f9107cf..2cf8fe8 100644 --- a/packages/frontend/src/routes/play/+page.svelte +++ b/packages/frontend/src/routes/play/+page.svelte @@ -3,18 +3,13 @@ import { _ } from "svelte-i18n"; import Meta from "$lib/components/meta/meta.svelte"; import { ButtplugClient, - ButtplugMessage, ButtplugWasmClientConnector, - DeviceList, - SensorReadCmd, - StopDeviceCmd, - SensorReading, - ScalarCmd, - ScalarSubcommand, - ButtplugDeviceMessage, ButtplugClientDevice, - SensorType, + OutputType, + InputType, + DeviceOutputValueConstructor, } from "@sexy.pivoine.art/buttplug"; +import type { ButtplugMessage } from "@sexy.pivoine.art/buttplug"; import Button from "$lib/components/ui/button/button.svelte"; import { onMount } from "svelte"; import { goto } from "$app/navigation"; @@ -50,11 +45,12 @@ async function init() { // await ButtplugWasmClientConnector.activateLogging("info"); await client.connect(connector); client.on("deviceadded", onDeviceAdded); - client.on("deviceremoved", (msg: ButtplugDeviceMessage) => - devices.splice(msg.DeviceIndex, 1), - ); + client.on("deviceremoved", (dev: ButtplugClientDevice) => { + const idx = devices.findIndex((d) => d.info.index === dev.index); + if (idx !== -1) devices.splice(idx, 1); + }); client.on("scanningfinished", () => (scanning = false)); - connector.on("message", handleMessages); + client.on("inputreading", handleInputReading); connected = client.connected; } @@ -63,65 +59,48 @@ async function startScanning() { scanning = true; } -async function onDeviceAdded( - msg: ButtplugDeviceMessage, - dev: ButtplugClientDevice, -) { +async function onDeviceAdded(dev: ButtplugClientDevice) { const device = convertDevice(dev); devices.push(device); - const cmds = device.info.messageAttributes.SensorReadCmd; - - cmds?.forEach(async (cmd) => { - await client.sendDeviceMessage( - { index: device.info.index }, - new SensorReadCmd(device.info.index, cmd.Index, cmd.SensorType), - ); - }); -} - -async function handleMessages(messages: ButtplugMessage[]) { - messages.forEach(async (msg) => { - await handleMessage(msg); - }); -} - -async function handleMessage(msg: ButtplugMessage) { - if (msg instanceof SensorReading) { - const device = devices[msg.DeviceIndex]; - if (msg.SensorType === SensorType.Battery) { - device.batteryLevel = msg.Data[0]; + // Try to read battery level + if (device.hasBattery) { + try { + device.batteryLevel = await dev.battery(); + } catch (e) { + console.warn(`Failed to read battery for ${dev.name}:`, e); } - device.sensorValues[msg.Index] = msg.Data[0]; - device.lastSeen = new Date(); - } else if (msg instanceof DeviceList) { - devices = client.devices.map(convertDevice); } } +function handleInputReading(msg: ButtplugMessage) { + if (msg.InputReading === undefined) return; + const reading = msg.InputReading; + const device = devices.find((d) => d.info.index === reading.DeviceIndex); + if (!device) return; + + if (reading.Reading[InputType.Battery] !== undefined) { + device.batteryLevel = reading.Reading[InputType.Battery].Value; + } + device.lastSeen = new Date(); +} + async function handleChange( device: BluetoothDevice, - scalarIndex: number, + actuatorIdx: number, value: number, ) { - const vibrateCmd = device.info.messageAttributes.ScalarCmd[scalarIndex]; - await client.sendDeviceMessage( - { index: device.info.index }, - new ScalarCmd( - [ - new ScalarSubcommand( - vibrateCmd.Index, - (device.actuatorValues[scalarIndex] = value), - vibrateCmd.ActuatorType, - ), - ], - device.info.index, - ), - ); + const actuator = device.actuators[actuatorIdx]; + const feature = device.info.features.get(actuator.featureIndex); + if (!feature) return; + + actuator.value = value; + const outputType = actuator.outputType as OutputType; + await feature.runOutput(new DeviceOutputValueConstructor(outputType).steps(value)); // Capture event if recording if (isRecording && recordingStartTime) { - captureEvent(device, scalarIndex, value); + captureEvent(device, actuatorIdx, value); } } @@ -145,44 +124,51 @@ function stopRecording() { function captureEvent( device: BluetoothDevice, - scalarIndex: number, + actuatorIdx: number, value: number, ) { if (!recordingStartTime) return; const timestamp = performance.now() - recordingStartTime; - const scalarCmd = device.info.messageAttributes.ScalarCmd[scalarIndex]; + const actuator = device.actuators[actuatorIdx]; recordedEvents.push({ timestamp, deviceIndex: device.info.index, deviceName: device.name, - actuatorIndex: scalarIndex, - actuatorType: scalarCmd.ActuatorType, - value: (value / scalarCmd.StepCount) * 100, // Normalize to 0-100 + actuatorIndex: actuatorIdx, + actuatorType: actuator.outputType, + value: (value / actuator.maxSteps) * 100, // Normalize to 0-100 }); } async function handleStop(device: BluetoothDevice) { - await client.sendDeviceMessage( - { index: device.info.index }, - new StopDeviceCmd(device.info.index), - ); - device.actuatorValues = device.info.messageAttributes.ScalarCmd.map(() => 0); + await device.info.stop(); + device.actuators.forEach((a) => (a.value = 0)); } function convertDevice(device: ButtplugClientDevice): BluetoothDevice { - console.log(device); + const actuators: import("$lib/types").DeviceActuator[] = []; + for (const [, feature] of device.features) { + for (const outputType of feature.outputTypes) { + actuators.push({ + featureIndex: feature.featureIndex, + outputType, + maxSteps: feature.outputMaxValue(outputType), + descriptor: feature.featureDescriptor, + value: 0, + }); + } + } + return { - id: device.index as string, - name: device.name as string, + id: String(device.index), + name: device.name, + actuators, batteryLevel: 0, + hasBattery: device.hasInput(InputType.Battery), isConnected: true, lastSeen: new Date(), - sensorValues: device.messageAttributes.SensorReadCmd - ? device.messageAttributes.SensorReadCmd.map(() => 0) - : [], - actuatorValues: device.messageAttributes.ScalarCmd.map(() => 0), info: device, }; } @@ -195,7 +181,7 @@ async function handleSaveRecording(data: { const deviceInfo: DeviceInfo[] = devices.map((d) => ({ name: d.name, index: d.info.index, - capabilities: d.info.messageAttributes.ScalarCmd.map((cmd) => cmd.ActuatorType), + capabilities: d.actuators.map((a) => a.outputType), })); try { @@ -345,37 +331,26 @@ function executeEvent(event: RecordedEvent) { } // Find matching actuator by type - const scalarCmd = device.info.messageAttributes.ScalarCmd.find( - cmd => cmd.ActuatorType === event.actuatorType + const actuator = device.actuators.find( + (a) => a.outputType === event.actuatorType, ); - if (!scalarCmd) { + if (!actuator) { console.warn(`Actuator type ${event.actuatorType} not found on ${device.name}`); return; } // Convert normalized value (0-100) back to device scale - const deviceValue = (event.value / 100) * scalarCmd.StepCount; + const deviceValue = Math.round((event.value / 100) * actuator.maxSteps); - // Send command to device - client.sendDeviceMessage( - { index: device.info.index }, - new ScalarCmd( - [ - new ScalarSubcommand( - scalarCmd.Index, - deviceValue, - scalarCmd.ActuatorType, - ), - ], - device.info.index, - ), - ); + // Send command to device via feature + const feature = device.info.features.get(actuator.featureIndex); + if (feature) { + const outputType = actuator.outputType as OutputType; + feature.runOutput(new DeviceOutputValueConstructor(outputType).steps(deviceValue)); + } // Update UI - const scalarIndex = device.info.messageAttributes.ScalarCmd.indexOf(scalarCmd); - if (scalarIndex !== -1) { - device.actuatorValues[scalarIndex] = deviceValue; - } + actuator.value = deviceValue; } function seek(percentage: number) { @@ -618,7 +593,7 @@ onMount(() => { deviceInfo={devices.map((d) => ({ name: d.name, index: d.info.index, - capabilities: d.info.messageAttributes.ScalarCmd.map((cmd) => cmd.ActuatorType), + capabilities: d.actuators.map((a) => a.outputType), }))} duration={recordingDuration} onSave={handleSaveRecording} diff --git a/packages/frontend/src/routes/play/components/device-mapping-dialog.svelte b/packages/frontend/src/routes/play/components/device-mapping-dialog.svelte index f37a942..de8d489 100644 --- a/packages/frontend/src/routes/play/components/device-mapping-dialog.svelte +++ b/packages/frontend/src/routes/play/components/device-mapping-dialog.svelte @@ -19,8 +19,8 @@ let mappings = $state>(new Map()); // Check if a connected device is compatible with a recorded device function isCompatible(recordedDevice: DeviceInfo, connectedDevice: BluetoothDevice): boolean { - const connectedActuators = connectedDevice.info.messageAttributes.ScalarCmd.map( - cmd => cmd.ActuatorType + const connectedActuators = connectedDevice.actuators.map( + (a) => a.outputType, ); // Check if all required actuator types from recording exist on connected device diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 70d37e2..238817b 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -8,6 +8,14 @@ export default defineConfig({ resolve: { alias: { $lib: path.resolve("./src/lib"), "@": path.resolve("./src/lib") }, }, + ssr: { + noExternal: ["@sexy.pivoine.art/buttplug"], + }, + build: { + rollupOptions: { + external: [/\/wasm\/index\.js/], + }, + }, server: { port: 3000, proxy: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5115508..13a174f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,15 +23,9 @@ importers: packages/buttplug: dependencies: - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 eventemitter3: specifier: ^5.0.1 version: 5.0.1 - reflect-metadata: - specifier: ^0.2.2 - version: 0.2.2 typescript: specifier: ^5.9.2 version: 5.9.3 @@ -1650,9 +1644,6 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - cli-color@2.0.4: resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==} engines: {node: '>=0.10'} @@ -2857,9 +2848,6 @@ packages: resolution: {integrity: sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==} engines: {node: '>=6'} - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - relative-time-format@1.1.11: resolution: {integrity: sha512-TH+oV/w77hjaB9xCzoFYJ/Icmr/12+02IAoCI/YGS2UBTbjCbBjHGEBxGnVy4EJvOR1qadGzyFRI6hGaJJG93Q==} @@ -4710,8 +4698,6 @@ snapshots: chownr@2.0.0: {} - class-transformer@0.5.1: {} - cli-color@2.0.4: dependencies: d: 1.0.2 @@ -5897,8 +5883,6 @@ snapshots: reduce-flatten@2.0.0: {} - reflect-metadata@0.2.2: {} - relative-time-format@1.1.11: {} resolve-from@4.0.0: {}