mirror of
https://github.com/fluxerapp/fluxer.git
synced 2026-09-02 21:04:06 +03:00
chore(workspace): tighten quality gates (#2230)
This commit is contained in:
@@ -8,7 +8,7 @@ ARG USER_GID=1000
|
||||
ARG NODE_MAJOR=24
|
||||
ARG ELP_VERSION=2026-02-27
|
||||
ARG PNPM_VERSION=10.29.3
|
||||
ARG WASM_BINDGEN_VERSION=0.2.122
|
||||
ARG WASM_BINDGEN_VERSION=0.2.123
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
+10
-4
@@ -7,10 +7,16 @@ QUICK=0
|
||||
SKIP_INSTALL=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--quick) QUICK=1 ;;
|
||||
--skip-install) SKIP_INSTALL=1 ;;
|
||||
-h|--help) sed -n '2,25p' "$0"; exit 0 ;;
|
||||
*) echo "unknown argument: $arg" >&2; exit 2 ;;
|
||||
--quick) QUICK=1 ;;
|
||||
--skip-install) SKIP_INSTALL=1 ;;
|
||||
-h | --help)
|
||||
sed -n '2,25p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "unknown argument: $arg" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
|
||||
rust:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
@@ -178,20 +178,40 @@ jobs:
|
||||
with:
|
||||
workspaces: |
|
||||
. -> target
|
||||
fluxer_desktop/native/rust -> target
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install cargo-deny
|
||||
run: cargo install cargo-deny --version 0.19.6 --locked
|
||||
|
||||
- name: Check Rust dependencies
|
||||
run: cargo deny --locked check -D warnings
|
||||
|
||||
- name: Check desktop native dependencies
|
||||
run: tools/ci/check-desktop-native-workspaces.sh dependencies
|
||||
|
||||
- name: Install native dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
pkg-config \
|
||||
build-essential \
|
||||
libcurl4-openssl-dev \
|
||||
libvips-dev \
|
||||
binutils \
|
||||
clang \
|
||||
cmake \
|
||||
libavfilter-dev \
|
||||
libclang-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfido2-dev \
|
||||
libheif-dev \
|
||||
libwebp-dev
|
||||
libpipewire-0.3-dev \
|
||||
libspa-0.2-dev \
|
||||
libssl-dev \
|
||||
libudev-dev \
|
||||
libvips-dev \
|
||||
libwebp-dev \
|
||||
meson \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
zlib1g-dev
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: pnpm --filter fluxer_admin install
|
||||
@@ -199,17 +219,20 @@ jobs:
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Check formatting (desktop native)
|
||||
run: cargo fmt --manifest-path fluxer_desktop/native/rust/Cargo.toml --all -- --check
|
||||
- name: Check formatting (desktop native workspaces)
|
||||
run: tools/ci/check-desktop-native-workspaces.sh fmt
|
||||
|
||||
- name: Clippy (warnings as errors)
|
||||
run: cargo clippy --workspace -- -D warnings
|
||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
|
||||
- name: Clippy (desktop native workspaces on Linux, warnings as errors)
|
||||
run: tools/ci/check-desktop-native-workspaces.sh clippy
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --workspace
|
||||
run: cargo test --workspace --all-features --locked
|
||||
|
||||
- name: Run desktop native tests
|
||||
run: cargo test --manifest-path fluxer_desktop/native/rust/Cargo.toml
|
||||
- name: Run desktop native workspace tests on Linux
|
||||
run: tools/ci/check-desktop-native-workspaces.sh test
|
||||
|
||||
gateway:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
Generated
+78
-367
@@ -49,14 +49,13 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "ammonia"
|
||||
version = "4.1.2"
|
||||
version = "4.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6"
|
||||
checksum = "dc6d763210e2eb7670d1a5183a08bebefa3f97db2a738a684f2ce00bd49f681d"
|
||||
dependencies = [
|
||||
"cssparser 0.35.0",
|
||||
"html5ever 0.35.0",
|
||||
"cssparser",
|
||||
"html5ever",
|
||||
"maplit",
|
||||
"tendril 0.4.3",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -127,9 +126,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
@@ -182,7 +181,7 @@ dependencies = [
|
||||
"ring",
|
||||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.103.13",
|
||||
"rustls-webpki",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_nanos",
|
||||
@@ -190,7 +189,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.4",
|
||||
"tokio-rustls",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tokio-websockets",
|
||||
@@ -528,23 +527,17 @@ dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"h2 0.3.27",
|
||||
"h2 0.4.14",
|
||||
"http 0.2.12",
|
||||
"h2",
|
||||
"http 1.4.2",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.32",
|
||||
"hyper 1.10.1",
|
||||
"hyper-rustls 0.24.2",
|
||||
"hyper-rustls 0.27.9",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.12",
|
||||
"rustls 0.23.40",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.4",
|
||||
"tokio-rustls",
|
||||
"tower",
|
||||
"tracing",
|
||||
]
|
||||
@@ -709,7 +702,7 @@ dependencies = [
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.10.1",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
@@ -933,9 +926,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
@@ -1218,9 +1211,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
@@ -1268,42 +1261,19 @@ dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa"
|
||||
dependencies = [
|
||||
"cssparser-macros 0.6.1",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.11.3",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98"
|
||||
dependencies = [
|
||||
"cssparser-macros 0.7.0",
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.13.1",
|
||||
"phf",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser-macros"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser-macros"
|
||||
version = "0.7.0"
|
||||
@@ -1652,7 +1622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1779,7 +1749,7 @@ dependencies = [
|
||||
"clap",
|
||||
"fluxer_common",
|
||||
"hmac 0.13.0",
|
||||
"hyper 1.10.1",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"image",
|
||||
"libc",
|
||||
@@ -1923,8 +1893,7 @@ dependencies = [
|
||||
"libc",
|
||||
"moka",
|
||||
"rmp-serde",
|
||||
"rustls 0.23.40",
|
||||
"rustls-pemfile",
|
||||
"rustls",
|
||||
"scylla",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2101,16 +2070,6 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
|
||||
dependencies = [
|
||||
"mac",
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.32"
|
||||
@@ -2283,28 +2242,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.27"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
|
||||
checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -2400,17 +2340,6 @@ dependencies = [
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4"
|
||||
dependencies = [
|
||||
"log",
|
||||
"markup5ever 0.35.0",
|
||||
"match_token",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.39.0"
|
||||
@@ -2418,7 +2347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"markup5ever 0.39.0",
|
||||
"markup5ever",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2497,30 +2426,6 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.27",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.10.1"
|
||||
@@ -2531,7 +2436,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2 0.4.14",
|
||||
"h2",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
@@ -2543,21 +2448,6 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.32",
|
||||
"log",
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.9"
|
||||
@@ -2565,12 +2455,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||
dependencies = [
|
||||
"http 1.4.2",
|
||||
"hyper 1.10.1",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls 0.23.40",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.4",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
@@ -2586,12 +2476,12 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.10.1",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.3",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -2985,29 +2875,12 @@ dependencies = [
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"tendril 0.4.3",
|
||||
"web_atoms 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.39.0"
|
||||
@@ -3015,19 +2888,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
|
||||
dependencies = [
|
||||
"log",
|
||||
"tendril 0.5.0",
|
||||
"web_atoms 0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_token"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"tendril",
|
||||
"web_atoms",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3192,7 +3054,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3363,55 +3225,25 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_macros 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_macros 0.13.1",
|
||||
"phf_shared 0.13.1",
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
|
||||
dependencies = [
|
||||
"phf_generator 0.13.1",
|
||||
"phf_shared 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
"rand 0.8.6",
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3421,20 +3253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"phf_shared 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3443,22 +3262,13 @@ version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
|
||||
dependencies = [
|
||||
"phf_generator 0.13.1",
|
||||
"phf_shared 0.13.1",
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.13.1"
|
||||
@@ -3759,8 +3569,8 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.40",
|
||||
"socket2 0.6.3",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -3780,7 +3590,7 @@ dependencies = [
|
||||
"rand 0.9.4",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.40",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror",
|
||||
@@ -3798,7 +3608,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.6.3",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -4016,15 +3826,15 @@ dependencies = [
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.10.1",
|
||||
"hyper-rustls 0.27.9",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.40",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"serde",
|
||||
@@ -4032,7 +3842,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.4",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
@@ -4069,7 +3879,7 @@ dependencies = [
|
||||
"futures",
|
||||
"getrandom 0.2.17",
|
||||
"http 1.4.2",
|
||||
"hyper 1.10.1",
|
||||
"hyper",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"retry-policies",
|
||||
@@ -4156,19 +3966,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-webpki 0.101.7",
|
||||
"sct",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4182,7 +3980,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.103.13",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -4199,15 +3997,6 @@ dependencies = [
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.1"
|
||||
@@ -4229,14 +4018,14 @@ dependencies = [
|
||||
"jni",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.23.40",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki 0.103.13",
|
||||
"rustls-webpki",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4245,16 +4034,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.13"
|
||||
@@ -4347,23 +4126,13 @@ version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdd0be4d296f048bfb06dd01bbc80ef789ddd2e55583e8d2e6b804942abfabc2"
|
||||
dependencies = [
|
||||
"cssparser 0.37.0",
|
||||
"cssparser",
|
||||
"ego-tree",
|
||||
"getopts",
|
||||
"html5ever 0.39.0",
|
||||
"html5ever",
|
||||
"precomputed-hash",
|
||||
"selectors",
|
||||
"tendril 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4467,12 +4236,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cssparser 0.37.0",
|
||||
"cssparser",
|
||||
"derive_more",
|
||||
"log",
|
||||
"new_debug_unreachable",
|
||||
"phf 0.13.1",
|
||||
"phf_codegen 0.13.1",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"precomputed-hash",
|
||||
"rustc-hash",
|
||||
"servo_arc",
|
||||
@@ -4766,9 +4535,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
@@ -4786,19 +4555,6 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared 0.11.3",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.9.0"
|
||||
@@ -4807,30 +4563,18 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared 0.13.1",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
|
||||
dependencies = [
|
||||
"phf_generator 0.13.1",
|
||||
"phf_shared 0.13.1",
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
@@ -4905,18 +4649,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
|
||||
dependencies = [
|
||||
"futf",
|
||||
"mac",
|
||||
"utf-8",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5087,7 +4820,7 @@ dependencies = [
|
||||
"log",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"phf 0.13.1",
|
||||
"phf",
|
||||
"pin-project-lite",
|
||||
"postgres-protocol",
|
||||
"postgres-types",
|
||||
@@ -5104,32 +4837,22 @@ version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2ad44aa0ae96db89c4742212ed41645b2f597311ff6e1945542a4d9fadc2fb"
|
||||
dependencies = [
|
||||
"rustls 0.23.40",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"sha2 0.11.0",
|
||||
"tokio",
|
||||
"tokio-postgres",
|
||||
"tokio-rustls 0.26.4",
|
||||
"tokio-rustls",
|
||||
"x509-cert",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||
dependencies = [
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls 0.23.40",
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -5173,7 +4896,7 @@ dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.4",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
@@ -5714,28 +5437,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web_atoms"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414"
|
||||
dependencies = [
|
||||
"phf 0.11.3",
|
||||
"phf_codegen 0.11.3",
|
||||
"string_cache 0.8.9",
|
||||
"string_cache_codegen 0.5.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web_atoms"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538"
|
||||
dependencies = [
|
||||
"phf 0.13.1",
|
||||
"phf_codegen 0.13.1",
|
||||
"string_cache 0.9.0",
|
||||
"string_cache_codegen 0.6.1",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5806,7 +5517,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
# cargo-deny configuration for the Fluxer workspace.
|
||||
#
|
||||
# Applies to the root workspace (Cargo.toml at the repo root) AND to every
|
||||
# per-addon crate under fluxer_desktop/native/* (each addon has its own
|
||||
# [workspace], so we invoke cargo-deny with --config pointing here).
|
||||
#
|
||||
# Used by the native desktop security gate in CI.
|
||||
# Applies to the root workspace (Cargo.toml at the repo root).
|
||||
|
||||
[graph]
|
||||
all-features = false
|
||||
all-features = true
|
||||
no-default-features = false
|
||||
|
||||
[output]
|
||||
@@ -44,13 +40,11 @@ allow = [
|
||||
"BSD-3-Clause",
|
||||
"ISC",
|
||||
"MPL-2.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Unicode-3.0",
|
||||
"Zlib",
|
||||
"CC0-1.0",
|
||||
"AGPL-3.0-or-later",
|
||||
"BSL-1.0",
|
||||
"OpenSSL",
|
||||
"CDLA-Permissive-2.0",
|
||||
]
|
||||
# Explicitly deny GPL-only / strong-copyleft licenses that don't compose with
|
||||
@@ -72,21 +66,48 @@ license-files = [
|
||||
[bans]
|
||||
multiple-versions = "warn"
|
||||
wildcards = "deny"
|
||||
# Per-addon crates path-depend on ../rust (the shared `fluxer_desktop_native`
|
||||
# crate) without a version. cargo-deny flags that as a wildcard; we allow it
|
||||
# because path deps can't realistically pin a SemVer range, and this only
|
||||
# affects intra-repo workspace links (registry wildcards remain denied).
|
||||
# Internal workspace crates use path dependencies without registry versions.
|
||||
# Registry wildcards remain denied.
|
||||
allow-wildcard-paths = true
|
||||
highlight = "all"
|
||||
workspace-default-features = "allow"
|
||||
external-default-features = "allow"
|
||||
# Keep desktop packaging and native addons away from the obsolete libfuse2 stack.
|
||||
# Keep workspace artifacts away from the obsolete libfuse2 stack.
|
||||
# AppImage packaging must use the static electron-builder runtime instead.
|
||||
deny = [
|
||||
{ crate = "fuse", reason = "libfuse2-based Rust wrapper; use a maintained FUSE3-native crate only if Fluxer ever needs FUSE directly" },
|
||||
{ crate = "fuse-sys", reason = "libfuse2 FFI crate; Fluxer AppImages must not reintroduce libfuse2 through native Rust dependencies" },
|
||||
]
|
||||
skip = []
|
||||
skip = [
|
||||
{ crate = "block-buffer@0.10.4", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "const-oid@0.9.6", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "cpufeatures@0.2.17", reason = "transitive dependency requires the older crypto API" },
|
||||
{ crate = "crypto-common@0.1.7", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "digest@0.10.7", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "foldhash@0.1.5", reason = "transitive dependency requires the older hashbrown API" },
|
||||
{ crate = "getrandom@0.2.17", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "getrandom@0.3.4", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "hashbrown@0.14.5", reason = "transitive dependency requires the older hashbrown API" },
|
||||
{ crate = "hashbrown@0.15.5", reason = "transitive dependency requires the older hashbrown API" },
|
||||
{ crate = "hashbrown@0.16.1", reason = "transitive dependency requires the prior hashbrown API" },
|
||||
{ crate = "hmac@0.12.1", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "http@0.2.12", reason = "transitive dependency requires the older HTTP API" },
|
||||
{ crate = "http-body@0.4.6", reason = "transitive dependency requires the older HTTP body API" },
|
||||
{ crate = "r-efi@5.3.0", reason = "transitive dependency requires the older WASI API" },
|
||||
{ crate = "rand@0.8.6", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "rand@0.9.4", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "rand_chacha@0.3.1", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "rand_chacha@0.9.0", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "rand_core@0.6.4", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "rand_core@0.9.5", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "sha1@0.10.6", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "sha2@0.10.9", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "socket2@0.5.10", reason = "transitive dependency requires the older socket API" },
|
||||
{ crate = "wasi@0.11.1+wasi-snapshot-preview1", reason = "transitive dependency requires the legacy WASI API" },
|
||||
{ crate = "windows-sys@0.52.0", reason = "transitive dependency requires the older Windows API" },
|
||||
{ crate = "windows-sys@0.59.0", reason = "transitive dependency requires the prior Windows API" },
|
||||
{ crate = "wit-bindgen@0.51.0", reason = "transitive dependency requires the older WASI binding API" },
|
||||
]
|
||||
skip-tree = []
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -6,7 +6,7 @@ license.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.104"
|
||||
axum = { version = "0.8.9", features = ["macros"] }
|
||||
base64 = "0.22.1"
|
||||
chrono = { version = "0.4", default-features = false, features = ["serde"] }
|
||||
|
||||
@@ -40,8 +40,7 @@ export class DMPermissionValidator {
|
||||
if (isBugHunterBotUser(senderUser)) {
|
||||
return;
|
||||
}
|
||||
const isShadowbannedSpammer =
|
||||
!senderUser.isBot && (senderUser.flags & UserFlags.SPAMMER) === UserFlags.SPAMMER;
|
||||
const isShadowbannedSpammer = !senderUser.isBot && (senderUser.flags & UserFlags.SPAMMER) === UserFlags.SPAMMER;
|
||||
const [senderBlockedTarget, targetBlockedSender, areFriends, targetSettings, senderSettings] = await Promise.all([
|
||||
this.deps.userRepository.getRelationship(senderId, recipientId, RelationshipTypes.BLOCKED),
|
||||
this.deps.userRepository.getRelationship(recipientId, senderId, RelationshipTypes.BLOCKED),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
import {Logger} from '@fluxer/logger/src/Logger';
|
||||
import {beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
|
||||
import type {MediaProxyMetadataRequest} from '../../infrastructure/IMediaService';
|
||||
import {setInjectedMediaService} from '../../middleware/ServiceRegistry';
|
||||
@@ -71,6 +72,7 @@ describe('Attachment Upload Validation', () => {
|
||||
const guild = await createGuild(harness, account.token, 'Storage Unavailable Test Guild');
|
||||
const channel = await createChannel(harness, account.token, guild.id, 'test-channel');
|
||||
const channelId = guild.system_channel_id ?? channel.id;
|
||||
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
|
||||
harness.storageService.configure({shouldFailUpload: true});
|
||||
try {
|
||||
const payload = {
|
||||
@@ -83,8 +85,25 @@ describe('Attachment Upload Validation', () => {
|
||||
const body = JSON.parse(text) as {code?: string};
|
||||
expect(response.status).toBe(HTTP_STATUS.SERVICE_UNAVAILABLE);
|
||||
expect(body.code).toBe(APIErrorCodes.SERVICE_UNAVAILABLE);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
err: expect.objectContaining({
|
||||
cause: expect.objectContaining({message: 'Mock storage upload failure'}),
|
||||
code: APIErrorCodes.SERVICE_UNAVAILABLE,
|
||||
message: 'Attachment storage is temporarily unavailable',
|
||||
status: HTTP_STATUS.SERVICE_UNAVAILABLE,
|
||||
}),
|
||||
method: 'POST',
|
||||
path: `/channels/${channelId}/messages`,
|
||||
requestId: expect.any(String),
|
||||
status: HTTP_STATUS.SERVICE_UNAVAILABLE,
|
||||
},
|
||||
'Request failed',
|
||||
);
|
||||
} finally {
|
||||
harness.storageService.configure({shouldFailUpload: false});
|
||||
errorLoggerSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -160,12 +179,9 @@ describe('Attachment Upload Validation', () => {
|
||||
content: 'Filename mismatch test',
|
||||
attachments: [{id: 0, filename: 'expected.txt'}],
|
||||
};
|
||||
const {response, text, json} = await sendMessageWithAttachments(harness, account.token, channelId, payload, [
|
||||
const {response, json} = await sendMessageWithAttachments(harness, account.token, channelId, payload, [
|
||||
{index: 0, filename: 'different.txt', data: fileData},
|
||||
]);
|
||||
if (response.status !== 200) {
|
||||
console.log('Error response:', text);
|
||||
}
|
||||
expect(response.status).toBe(200);
|
||||
expect(json.attachments).toBeDefined();
|
||||
expect(json.attachments).not.toBeNull();
|
||||
|
||||
@@ -798,8 +798,7 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
const corpus = new Map<string, Array<Row>>();
|
||||
const shapeCache = new Map<string, Array<Shape>>();
|
||||
const unstableShapes: Array<string> = [];
|
||||
const reproducibleOrders: Array<string> = [];
|
||||
const traffic = {legacy: 0, next: 0, shapes: 0, improved: 0};
|
||||
const traffic = {legacy: 0, next: 0, shapes: 0};
|
||||
const keysetOrderDeltas: Array<string> = [];
|
||||
const legacyThrowDeltas: Array<string> = [];
|
||||
|
||||
@@ -906,7 +905,6 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
executor: LegacyPostgresKvQueryExecutor,
|
||||
tableName: string,
|
||||
shape: Shape,
|
||||
candidate: string,
|
||||
): Promise<boolean> {
|
||||
const observed = new Set<string>();
|
||||
for (const ordering of HEAP_ORDERINGS) {
|
||||
@@ -914,7 +912,6 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
}
|
||||
await restoreOne(kv, tableName, HEAP_ORDERINGS[0]!);
|
||||
if (observed.size === 1) return false;
|
||||
if (observed.has(candidate)) reproducibleOrders.push(`${tableName} ${shape.name}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -950,7 +947,6 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
traffic.legacy += left.rowsRead;
|
||||
traffic.next += right.rowsRead;
|
||||
traffic.shapes += 1;
|
||||
if (right.rowsRead < left.rowsRead) traffic.improved += 1;
|
||||
if (left.fingerprint === right.fingerprint) {
|
||||
if (right.rowsRead > left.rowsRead) {
|
||||
mismatches.push({
|
||||
@@ -976,7 +972,7 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!(await legacyIsOrderUnstable(kv, executor, tableName, shape, right.fingerprint))) {
|
||||
if (!(await legacyIsOrderUnstable(kv, executor, tableName, shape))) {
|
||||
mismatches.push({
|
||||
kind: sameMultiset ? 'order' : 'rowset',
|
||||
table: tableName,
|
||||
@@ -1159,7 +1155,6 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
}
|
||||
}
|
||||
expect(shapeCount).toBeGreaterThan(3000);
|
||||
console.log(`select sweep: tables=${specs.length} shapes=${shapeCount}`);
|
||||
expect(mismatches.length, report(mismatches)).toBe(0);
|
||||
}, 1_800_000);
|
||||
|
||||
@@ -1210,7 +1205,6 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
traffic.legacy += left.rowsRead;
|
||||
traffic.next += right.rowsRead;
|
||||
traffic.shapes += 1;
|
||||
if (right.rowsRead < left.rowsRead) traffic.improved += 1;
|
||||
if (right.rowsRead > left.rowsRead) {
|
||||
mismatches.push({
|
||||
kind: 'rowsread',
|
||||
@@ -1309,7 +1303,7 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
keysetOrderDeltas.push(`${spec.name} ${label}`);
|
||||
continue;
|
||||
}
|
||||
if (await legacyIsOrderUnstable(NEXT_TABLE, readLegacy, spec.name, shape, fingerprint(rightAll))) {
|
||||
if (await legacyIsOrderUnstable(NEXT_TABLE, readLegacy, spec.name, shape)) {
|
||||
unstableShapes.push(`${spec.name} ${label}`);
|
||||
continue;
|
||||
}
|
||||
@@ -1454,14 +1448,5 @@ suite('PostgresKvQueryExecutor differential', () => {
|
||||
}
|
||||
expect(traffic.shapes).toBeGreaterThan(3000);
|
||||
expect(traffic.next).toBeLessThan(traffic.legacy);
|
||||
console.log(
|
||||
`row payload traffic: shapes=${traffic.shapes} legacyRows=${traffic.legacy} nextRows=${traffic.next} reduction=${(100 - (traffic.next / traffic.legacy) * 100).toFixed(1)}% shapesImproved=${traffic.improved}`,
|
||||
);
|
||||
console.log(
|
||||
`accepted deltas: order-unstable=${unstableShapes.length} (legacy-reproducible=${reproducibleOrders.length}) keyset-order=${keysetOrderDeltas.length} legacy-throw=${legacyThrowDeltas.length}`,
|
||||
);
|
||||
console.log(`order-unstable sample: ${unstableShapes.slice(0, 5).join(' | ')}`);
|
||||
console.log(`keyset-order sample: ${keysetOrderDeltas.slice(0, 5).join(' | ')}`);
|
||||
console.log(`legacy-throw: ${[...new Set(legacyThrowDeltas.map((entry) => entry.split(': ')[1]))].join(' | ')}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -210,9 +210,9 @@ suite('postgres kv upgrade safety', () => {
|
||||
if (pages > 200) throw new Error('runaway paging loop');
|
||||
}
|
||||
} catch (error) {
|
||||
return {keys, error: (error as Error).message, pages};
|
||||
return {keys, error: (error as Error).message};
|
||||
}
|
||||
return {keys, error: null, pages};
|
||||
return {keys, error: null};
|
||||
}
|
||||
|
||||
it('pages consistently with only the old image running', async () => {
|
||||
@@ -232,27 +232,18 @@ suite('postgres kv upgrade safety', () => {
|
||||
it('ROLLING RESTART: an old replica consumes a token minted by the new one', async () => {
|
||||
const first = await next.executePagedQuery(usersScan(), {pageSize: 7, pageState: null});
|
||||
expect(first.pageState).toBeTruthy();
|
||||
const token = JSON.parse(Buffer.from(first.pageState!, 'base64url').toString('utf8'));
|
||||
let failure: string | null = null;
|
||||
let rows = 0;
|
||||
try {
|
||||
const second = await legacy.executePagedQuery(usersScan(), {pageSize: 7, pageState: first.pageState});
|
||||
rows = second.rows.length;
|
||||
} catch (error) {
|
||||
failure = (error as Error).message;
|
||||
}
|
||||
console.log(`NEW->OLD token=${JSON.stringify(token)} legacy=${failure ?? `${rows} rows`}`);
|
||||
expect(failure).toBeNull();
|
||||
const second = await legacy.executePagedQuery(usersScan(), {pageSize: 7, pageState: first.pageState});
|
||||
expect(second.rows).toHaveLength(7);
|
||||
});
|
||||
|
||||
it('ROLLING RESTART: alternating old and new replicas drain the scan', async () => {
|
||||
const a = await drain(['next', 'legacy'], 7);
|
||||
const b = await drain(['legacy', 'next'], 7);
|
||||
console.log(`new-first err=${a.error} pages=${a.pages} total=${a.keys.length} unique=${new Set(a.keys).size}`);
|
||||
console.log(`old-first err=${b.error} pages=${b.pages} total=${b.keys.length} unique=${new Set(b.keys).size}`);
|
||||
expect(a.error).toBeNull();
|
||||
expect(a.keys).toHaveLength(40);
|
||||
expect(new Set(a.keys).size).toBe(40);
|
||||
expect(b.error).toBeNull();
|
||||
expect(b.keys).toHaveLength(40);
|
||||
expect(new Set(b.keys).size).toBe(40);
|
||||
});
|
||||
|
||||
@@ -262,9 +253,9 @@ suite('postgres kv upgrade safety', () => {
|
||||
const dual = Buffer.from(JSON.stringify({offset: first.rows.length, after: cursor.after})).toString('base64url');
|
||||
const viaOld = await legacy.executePagedQuery(usersScan(), {pageSize: 7, pageState: dual});
|
||||
const viaNew = await next.executePagedQuery(usersScan(), {pageSize: 7, pageState: dual});
|
||||
console.log(`dual token -> old replica: ${viaOld.rows.length} rows, new replica: ${viaNew.rows.length} rows`);
|
||||
expect(viaOld.rows.length).toBe(7);
|
||||
expect(viaNew.rows.length).toBe(7);
|
||||
expect(viaOld.rows).toHaveLength(7);
|
||||
expect(viaNew.rows).toHaveLength(7);
|
||||
expect(fp(viaNew.rows)).toBe(fp(viaOld.rows));
|
||||
});
|
||||
|
||||
it('deletes exactly the same rows as the old image', async () => {
|
||||
@@ -282,8 +273,7 @@ suite('postgres kv upgrade safety', () => {
|
||||
const left = await raw.query<{row_key: string}>(
|
||||
`SELECT row_key FROM ${KV} WHERE table_name = 'read_states' AND row_key LIKE '%400%' ORDER BY row_key`,
|
||||
);
|
||||
console.log(`rows left after legacy delete + new delete: ${left.rows.length}`);
|
||||
expect(left.rows.length).toBe(0);
|
||||
expect(left.rows).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('writes byte-identical row_key and partition_key to the old image', async () => {
|
||||
@@ -333,8 +323,6 @@ suite('postgres kv upgrade safety', () => {
|
||||
const cIndexes = await raw.query<{tablename: string}>(
|
||||
`SELECT tablename FROM pg_indexes WHERE indexname IN ('${OLD}_row_key_c_idx', '${NEW}_row_key_c_idx')`,
|
||||
);
|
||||
console.log(`key diffs=${diff.rows[0]!.n} index-definition diffs=${schemaDiff.rows[0]!.n}`);
|
||||
console.log(collations.rows.map((r) => `${r.tablename}.${r.attname}=${r.collname}`).join(' '));
|
||||
expect(Number(diff.rows[0]!.n)).toBe(0);
|
||||
expect(Number(schemaDiff.rows[0]!.n)).toBe(0);
|
||||
expect(collations.rows.filter((r) => r.tablename === OLD).map((r) => r.collname)).toEqual(['default', 'default']);
|
||||
@@ -358,7 +346,6 @@ suite('postgres kv upgrade safety', () => {
|
||||
const indexes = await raw.query<{indexname: string}>(
|
||||
`SELECT indexname FROM pg_indexes WHERE tablename = '${OLD}' ORDER BY indexname`,
|
||||
);
|
||||
console.log(`after boot on a legacy table: ${indexes.rows.map((r) => r.indexname).join(', ')}`);
|
||||
expect(collations.rows.map((r) => r.collname)).toEqual(['default', 'default']);
|
||||
expect(indexes.rows.map((r) => r.indexname)).toContain(`${OLD}_row_key_c_idx`);
|
||||
});
|
||||
@@ -371,10 +358,11 @@ suite('postgres kv upgrade safety', () => {
|
||||
ensurePostgresKvSchema(new TableClient(raw, BOOT)),
|
||||
ensurePostgresKvSchema(new TableClient(raw, BOOT)),
|
||||
]);
|
||||
const failures = results.filter((r) => r.status === 'rejected');
|
||||
console.log(`concurrent boots rejected: ${failures.length}`);
|
||||
for (const f of failures) console.log(` ${(f as PromiseRejectedResult).reason}`);
|
||||
expect(failures.length).toBe(0);
|
||||
expect(results).toEqual([
|
||||
{status: 'fulfilled', value: undefined},
|
||||
{status: 'fulfilled', value: undefined},
|
||||
{status: 'fulfilled', value: undefined},
|
||||
]);
|
||||
}, 120_000);
|
||||
|
||||
it('backfills the messages partition key once and never scans for it again', async () => {
|
||||
@@ -416,7 +404,6 @@ suite('postgres kv upgrade safety', () => {
|
||||
await insertLegacy('m2');
|
||||
await ensurePostgresKvSchema(backfillClient);
|
||||
const skipped = await partitionOf('m2');
|
||||
console.log(`second boot left the new legacy row alone: partition_key=${skipped === legacyKey('m2')}`);
|
||||
expect(skipped).toBe(legacyKey('m2'));
|
||||
expect(await partitionOf('m1')).toBe(`"c"${SEP}"b"`);
|
||||
await raw.query(`DROP TABLE IF EXISTS ${BACKFILL}`);
|
||||
@@ -432,7 +419,6 @@ suite('postgres kv upgrade safety', () => {
|
||||
);
|
||||
const after = await next.executeQuery(q);
|
||||
await raw.query(`UPDATE pg_index SET indisvalid = true WHERE indexrelid = '${KV}_row_key_c_idx'::regclass`);
|
||||
console.log(`index valid after boot: ${check.rows[0]?.indisvalid}`);
|
||||
expect(String(after.map((r) => Object.values(r).map(String).join(',')))).toBe(
|
||||
String(before.map((r) => Object.values(r).map(String).join(','))),
|
||||
);
|
||||
@@ -441,7 +427,6 @@ suite('postgres kv upgrade safety', () => {
|
||||
|
||||
describe('multi-range plan shape on a populated upgraded database', () => {
|
||||
const PERF = 'kv_ranges_perf';
|
||||
const SEP = String.fromCharCode(31);
|
||||
const BASE = 1456074443984486400n;
|
||||
let perf: IPostgresClient;
|
||||
|
||||
@@ -481,6 +466,7 @@ FROM generate_series($1::bigint, $1::bigint + 19999) u, generate_series(1, 3) s`
|
||||
`SELECT kv.row_key, kv.row_data FROM ${PERF} kv WHERE kv.table_name = $1 AND (kv.expires_at IS NULL OR kv.expires_at > now())`,
|
||||
['push_subscriptions'],
|
||||
);
|
||||
expect(Number.isFinite(scan.ms)).toBe(true);
|
||||
const deltas: Array<string> = [];
|
||||
for (const size of [5, 10, 25, 50, 100]) {
|
||||
const params = {user_ids: Array.from({length: size}, (_, i) => BASE + BigInt(i * 3))};
|
||||
@@ -494,9 +480,10 @@ FROM generate_series($1::bigint, $1::bigint + 19999) u, generate_series(1, 3) s`
|
||||
`SELECT kv.row_key, kv.row_data FROM ${PERF} kv WHERE kv.table_name = $1${fragments.predicate} AND (kv.expires_at IS NULL OR kv.expires_at > now())`,
|
||||
['push_subscriptions', ...fragments.params],
|
||||
);
|
||||
const verdict = `n=${size} pushdown=${pushed.ms.toFixed(2)}ms scan=${scan.ms.toFixed(2)}ms`;
|
||||
console.log(verdict);
|
||||
if (pushed.ms > scan.ms) deltas.push(`${verdict}\n${pushed.plan}`);
|
||||
expect(Number.isFinite(pushed.ms)).toBe(true);
|
||||
if (pushed.ms > scan.ms) {
|
||||
deltas.push(`n=${size} pushdown=${pushed.ms.toFixed(2)}ms scan=${scan.ms.toFixed(2)}ms\n${pushed.plan}`);
|
||||
}
|
||||
}
|
||||
expect(deltas.join('\n')).toBe('');
|
||||
const overCap = buildCandidatePlan(meta, {
|
||||
@@ -506,17 +493,13 @@ FROM generate_series($1::bigint, $1::bigint + 19999) u, generate_series(1, 3) s`
|
||||
expect(overCap.exact).toBe(true);
|
||||
const overCapGroups = planFragmentGroups(overCap.candidates);
|
||||
expect(overCapGroups.map((group) => group.params.length)).toEqual([512, 512, 376]);
|
||||
let chunkedMs = 0;
|
||||
for (const fragments of overCapGroups) {
|
||||
const pushed = await explain(
|
||||
`SELECT kv.row_key, kv.row_data FROM ${PERF} kv WHERE kv.table_name = $1${fragments.predicate} AND (kv.expires_at IS NULL OR kv.expires_at > now())`,
|
||||
['push_subscriptions', ...fragments.params],
|
||||
);
|
||||
expect(pushed.plan).not.toContain('Seq Scan');
|
||||
chunkedMs += pushed.ms;
|
||||
}
|
||||
console.log(`n=700 chunked=${chunkedMs.toFixed(2)}ms scan=${scan.ms.toFixed(2)}ms`);
|
||||
expect(SEP).toBe('\u001f');
|
||||
}, 300_000);
|
||||
});
|
||||
});
|
||||
@@ -547,8 +530,11 @@ describe('candidate plan shapes reached by real queries', () => {
|
||||
: plan.candidates.kind === 'rowKeys'
|
||||
? plan.candidates.rowKeys.length
|
||||
: 0;
|
||||
console.log(`${c.name} -> kind=${plan.candidates.kind} n=${size} exact=${plan.exact}`);
|
||||
expect({kind: plan.candidates.kind, size, exact: plan.exact}, c.name).toEqual({
|
||||
kind: 'ranges',
|
||||
size: 100,
|
||||
exact: true,
|
||||
});
|
||||
}
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -859,9 +859,7 @@ export class GuildOperationsService {
|
||||
const channels = await this.channelRepository.listGuildChannels(guildId);
|
||||
await Promise.all(channels.map((channel) => this.channelRepository.deleteAllChannelMessages(channel.id)));
|
||||
await Promise.all(
|
||||
channels.map((channel) =>
|
||||
deleteChannelMessageSearchDocuments(channel.id, {context: {source: 'guild_delete'}}),
|
||||
),
|
||||
channels.map((channel) => deleteChannelMessageSearchDocuments(channel.id, {context: {source: 'guild_delete'}})),
|
||||
);
|
||||
await Promise.all(channels.map((channel) => this.channelService.attachments.purgeChannelAttachments(channel)));
|
||||
const discoveryRow = await this.discoveryRepository.findByGuildId(guildId);
|
||||
|
||||
@@ -171,7 +171,6 @@ export async function stripNonJpegImageMetadataForUpload(
|
||||
return {body: await image.gif().toBuffer(), contentType: 'image/gif'};
|
||||
case 'webp':
|
||||
return {body: await image.webp({lossless: true}).toBuffer(), contentType: 'image/webp'};
|
||||
case 'avif':
|
||||
case 'heif':
|
||||
return {body: await image.avif().toBuffer(), contentType: 'image/avif'};
|
||||
case 'tiff':
|
||||
@@ -460,7 +459,6 @@ async function stripNonJpegImageFileToFile(
|
||||
case 'webp':
|
||||
await image.webp({lossless: true}).toFile(outputPath);
|
||||
return {contentType: 'image/webp', ...dimensions};
|
||||
case 'avif':
|
||||
case 'heif':
|
||||
await image.avif().toFile(outputPath);
|
||||
return {contentType: 'image/avif', ...dimensions};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {AppErrorHandler} from '@fluxer/errors/src/domains/core/ErrorHandlers';
|
||||
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
|
||||
import {Logger} from '@fluxer/logger/src/Logger';
|
||||
import {Hono} from 'hono';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
import {describe, expect, test, vi} from 'vitest';
|
||||
import type {HonoEnv} from '../../types/HonoEnv';
|
||||
import {ConcurrencyLimitMiddleware} from '../ConcurrencyLimitMiddleware';
|
||||
|
||||
@@ -22,6 +24,7 @@ function createDeferred(): Deferred {
|
||||
function buildApp(maxInflightRequests: number) {
|
||||
const release = createDeferred();
|
||||
const entered = createDeferred();
|
||||
const boomError = new Error('boom');
|
||||
const routes = new Hono<HonoEnv>({strict: true});
|
||||
routes.use(ConcurrencyLimitMiddleware({maxInflightRequests}));
|
||||
routes.get('/_health', (ctx) => ctx.text('OK'));
|
||||
@@ -33,65 +36,132 @@ function buildApp(maxInflightRequests: number) {
|
||||
return ctx.text('slow');
|
||||
});
|
||||
routes.get('/boom', () => {
|
||||
throw new Error('boom');
|
||||
throw boomError;
|
||||
});
|
||||
routes.onError(AppErrorHandler);
|
||||
const app = new Hono<HonoEnv>({strict: true});
|
||||
app.route('/v1', routes);
|
||||
app.route('/', routes);
|
||||
app.onError(AppErrorHandler);
|
||||
return {app, release, entered};
|
||||
return {app, release, entered, boomError};
|
||||
}
|
||||
|
||||
describe('ConcurrencyLimitMiddleware', () => {
|
||||
test('sheds with 503 once the in-flight ceiling is reached', async () => {
|
||||
const {app, release, entered} = buildApp(1);
|
||||
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
|
||||
const inflight = app.request('/slow');
|
||||
await entered.promise;
|
||||
try {
|
||||
await entered.promise;
|
||||
|
||||
const shed = await app.request('/fast');
|
||||
const shed = await app.request('/fast');
|
||||
|
||||
expect(shed.status).toBe(503);
|
||||
expect(shed.headers.get('Retry-After')).toBe('1');
|
||||
expect(await shed.json()).toMatchObject({code: 'SERVICE_UNAVAILABLE'});
|
||||
expect(shed.status).toBe(503);
|
||||
expect(shed.headers.get('Retry-After')).toBe('1');
|
||||
expect(await shed.json()).toMatchObject({code: 'SERVICE_UNAVAILABLE'});
|
||||
expect(errorLoggerSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
err: expect.any(ServiceUnavailableError),
|
||||
method: 'GET',
|
||||
path: '/fast',
|
||||
requestId: undefined,
|
||||
status: 503,
|
||||
},
|
||||
'Request failed',
|
||||
);
|
||||
|
||||
release.resolve();
|
||||
expect((await inflight).status).toBe(200);
|
||||
release.resolve();
|
||||
expect((await inflight).status).toBe(200);
|
||||
} finally {
|
||||
release.resolve();
|
||||
errorLoggerSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('admits probe paths while shedding everything else', async () => {
|
||||
const {app, release, entered} = buildApp(1);
|
||||
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
|
||||
const inflight = app.request('/slow');
|
||||
await entered.promise;
|
||||
try {
|
||||
await entered.promise;
|
||||
|
||||
expect((await app.request('/_health')).status).toBe(200);
|
||||
expect((await app.request('/_metrics')).status).toBe(200);
|
||||
expect((await app.request('/v1/_health')).status).toBe(200);
|
||||
expect((await app.request('/fast')).status).toBe(503);
|
||||
expect((await app.request('/_health')).status).toBe(200);
|
||||
expect((await app.request('/_metrics')).status).toBe(200);
|
||||
expect((await app.request('/v1/_health')).status).toBe(200);
|
||||
expect((await app.request('/fast')).status).toBe(503);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
err: expect.any(ServiceUnavailableError),
|
||||
method: 'GET',
|
||||
path: '/fast',
|
||||
requestId: undefined,
|
||||
status: 503,
|
||||
},
|
||||
'Request failed',
|
||||
);
|
||||
|
||||
release.resolve();
|
||||
await inflight;
|
||||
release.resolve();
|
||||
await inflight;
|
||||
} finally {
|
||||
release.resolve();
|
||||
errorLoggerSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('releases the slot when the handler throws', async () => {
|
||||
const {app} = buildApp(1);
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
expect((await app.request('/boom')).status).toBe(500);
|
||||
const {app, boomError} = buildApp(1);
|
||||
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
|
||||
try {
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
expect((await app.request('/boom')).status).toBe(500);
|
||||
}
|
||||
expect((await app.request('/fast')).status).toBe(200);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledTimes(3);
|
||||
for (let callIndex = 1; callIndex <= 3; callIndex += 1) {
|
||||
expect(errorLoggerSpy).toHaveBeenNthCalledWith(
|
||||
callIndex,
|
||||
{err: boomError, method: 'GET', path: '/boom', requestId: undefined, status: 500},
|
||||
'Unhandled error occurred',
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
errorLoggerSpy.mockRestore();
|
||||
}
|
||||
expect((await app.request('/fast')).status).toBe(200);
|
||||
});
|
||||
|
||||
test('releases the slot taken by a shed request', async () => {
|
||||
const {app, release, entered} = buildApp(1);
|
||||
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
|
||||
const inflight = app.request('/slow');
|
||||
await entered.promise;
|
||||
try {
|
||||
await entered.promise;
|
||||
|
||||
expect((await app.request('/fast')).status).toBe(503);
|
||||
expect((await app.request('/fast')).status).toBe(503);
|
||||
expect((await app.request('/fast')).status).toBe(503);
|
||||
expect((await app.request('/fast')).status).toBe(503);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledTimes(2);
|
||||
for (let callIndex = 1; callIndex <= 2; callIndex += 1) {
|
||||
expect(errorLoggerSpy).toHaveBeenNthCalledWith(
|
||||
callIndex,
|
||||
{
|
||||
err: expect.any(ServiceUnavailableError),
|
||||
method: 'GET',
|
||||
path: '/fast',
|
||||
requestId: undefined,
|
||||
status: 503,
|
||||
},
|
||||
'Request failed',
|
||||
);
|
||||
}
|
||||
|
||||
release.resolve();
|
||||
await inflight;
|
||||
release.resolve();
|
||||
await inflight;
|
||||
|
||||
expect((await app.request('/fast')).status).toBe(200);
|
||||
expect((await app.request('/fast')).status).toBe(200);
|
||||
} finally {
|
||||
release.resolve();
|
||||
errorLoggerSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {Logger} from '@fluxer/logger/src/Logger';
|
||||
import {SnowflakeType} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {Hono} from 'hono';
|
||||
import {afterAll, beforeEach, describe, expect, test} from 'vitest';
|
||||
import {afterAll, beforeEach, describe, expect, test, vi} from 'vitest';
|
||||
import {z} from 'zod';
|
||||
import {Config} from '../../Config';
|
||||
import type {HonoEnv} from '../../types/HonoEnv';
|
||||
@@ -52,10 +53,26 @@ describe('ResponseTypeMiddleware', () => {
|
||||
test('rejects mismatching responses while validation is enabled', async () => {
|
||||
const app = new Hono<HonoEnv>();
|
||||
app.get('/snowflake', ResponseType(SnowflakeResponse), (ctx) => ctx.json({id: 'not-a-snowflake'}));
|
||||
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
|
||||
|
||||
const response = await app.request('/snowflake');
|
||||
try {
|
||||
const response = await app.request('/snowflake');
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.status).toBe(500);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorLoggerSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
body: {id: 'not-a-snowflake'},
|
||||
method: 'GET',
|
||||
path: '/snowflake',
|
||||
status: 200,
|
||||
validationErrors: [{message: 'INVALID_SNOWFLAKE_FORMAT', path: 'id'}],
|
||||
},
|
||||
'Response validation failed',
|
||||
);
|
||||
} finally {
|
||||
errorLoggerSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('passes the response through untouched while validation is disabled', async () => {
|
||||
|
||||
@@ -9,8 +9,7 @@ export function channelRequiresAgeVerification(
|
||||
channelsById: ReadonlyMap<string, Channel>,
|
||||
guildNsfw: boolean,
|
||||
): boolean {
|
||||
const parentCategory =
|
||||
channel.parentId != null ? (channelsById.get(channel.parentId.toString()) ?? null) : null;
|
||||
const parentCategory = channel.parentId != null ? (channelsById.get(channel.parentId.toString()) ?? null) : null;
|
||||
return computeEffectiveChannelNsfw(
|
||||
channelToContentWarningView(channel),
|
||||
parentCategory ? channelToContentWarningView(parentCategory) : null,
|
||||
|
||||
@@ -49,6 +49,20 @@ describe('Stripe Webhook Refund', () => {
|
||||
.body(payload)
|
||||
.execute();
|
||||
}
|
||||
function useRefundListHandler(chargeId: string): void {
|
||||
server.use(
|
||||
http.get(
|
||||
({request}) => request.url === `https://api.stripe.com/v1/refunds?charge=${chargeId}&limit=100`,
|
||||
() =>
|
||||
HttpResponse.json({
|
||||
object: 'list',
|
||||
data: [],
|
||||
has_more: false,
|
||||
url: '/v1/refunds',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
describe('charge.refunded', () => {
|
||||
test('revokes premium and records first refund', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
@@ -73,6 +87,7 @@ describe('Stripe Webhook Refund', () => {
|
||||
payment_intent_id: paymentIntentId,
|
||||
completed_at: new Date(),
|
||||
});
|
||||
useRefundListHandler('ch_test_refund_123');
|
||||
await sendWebhook({
|
||||
type: 'charge.refunded',
|
||||
data: {
|
||||
@@ -121,6 +136,7 @@ describe('Stripe Webhook Refund', () => {
|
||||
payment_intent_id: paymentIntentId,
|
||||
completed_at: new Date(),
|
||||
});
|
||||
useRefundListHandler('ch_test_refund_456');
|
||||
await sendWebhook({
|
||||
type: 'charge.refunded',
|
||||
data: {
|
||||
@@ -150,6 +166,7 @@ describe('Stripe Webhook Refund', () => {
|
||||
{stripe_customer_id: stripeCustomerId},
|
||||
(await userRepository.findUnique(userId))!.toRow(),
|
||||
);
|
||||
useRefundListHandler('ch_test_refund_sub_789');
|
||||
await sendWebhook({
|
||||
type: 'charge.refunded',
|
||||
data: {
|
||||
@@ -182,6 +199,7 @@ describe('Stripe Webhook Refund', () => {
|
||||
subscriptionCurrentPeriodEnd: null,
|
||||
subscriptionCancelAt: null,
|
||||
});
|
||||
useRefundListHandler('ch_test_refund_donation_101');
|
||||
const result = await sendWebhook({
|
||||
type: 'charge.refunded',
|
||||
data: {
|
||||
@@ -234,6 +252,7 @@ describe('Stripe Webhook Refund', () => {
|
||||
premium_until: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
.execute();
|
||||
useRefundListHandler('ch_test_refund_gift_multi_123');
|
||||
await sendWebhook({
|
||||
type: 'charge.refunded',
|
||||
data: {
|
||||
@@ -287,6 +306,7 @@ describe('Stripe Webhook Refund', () => {
|
||||
.get('/users/@me')
|
||||
.execute();
|
||||
expect(redeemerBefore.premium_type).toBe(UserPremiumTypes.SUBSCRIPTION);
|
||||
useRefundListHandler('ch_test_refund_stripe_cus_only');
|
||||
await sendWebhook({
|
||||
type: 'charge.refunded',
|
||||
data: {
|
||||
@@ -325,6 +345,7 @@ describe('Stripe Webhook Refund', () => {
|
||||
premium_type: UserPremiumTypes.LIFETIME,
|
||||
})
|
||||
.execute();
|
||||
useRefundListHandler('ch_test_refund_gift_override_123');
|
||||
await sendWebhook({
|
||||
type: 'charge.refunded',
|
||||
data: {
|
||||
|
||||
@@ -119,12 +119,8 @@ export class UserRelationshipService {
|
||||
requestCache: RequestCache;
|
||||
}): Promise<Relationship> {
|
||||
const requesterUser = await this.userRepository.findUnique(userId);
|
||||
const requesterIsStaff =
|
||||
requesterUser != null && (requesterUser.flags & UserFlags.STAFF) === UserFlags.STAFF;
|
||||
if (
|
||||
!requesterIsStaff &&
|
||||
(await getInstanceConfigRepository().getInstancePolicyConfig()).direct_messages_disabled
|
||||
) {
|
||||
const requesterIsStaff = requesterUser != null && (requesterUser.flags & UserFlags.STAFF) === UserFlags.STAFF;
|
||||
if (!requesterIsStaff && (await getInstanceConfigRepository().getInstancePolicyConfig()).direct_messages_disabled) {
|
||||
throw new DirectMessagesDisabledError();
|
||||
}
|
||||
if (staffForceAccept && requesterIsStaff) {
|
||||
|
||||
@@ -267,7 +267,7 @@
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"autoprefixer": "catalog:",
|
||||
"esbuild": "catalog:",
|
||||
"happy-dom": "20.7.0",
|
||||
"happy-dom": "20.12.0",
|
||||
"postcss": "catalog:",
|
||||
"postcss-discard-comments": "catalog:",
|
||||
"postcss-loader": "catalog:",
|
||||
|
||||
Generated
+2
-2
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import {mkdirSync, readFileSync, writeFileSync} from 'node:fs';
|
||||
import {join} from 'node:path';
|
||||
import sharp from 'sharp';
|
||||
import sharp, {type OverlayOptions} from 'sharp';
|
||||
import {convertToCodePoints} from '../src/features/expressions/utils/EmojiCodepointUtils';
|
||||
|
||||
const EMOJI_SPRITES = {
|
||||
@@ -131,7 +131,7 @@ async function renderSpriteSheet(
|
||||
const size = EMOJI_SIZE * scale;
|
||||
const dstW = perRow * size;
|
||||
const dstH = rows * size;
|
||||
const compositeOps: Array<sharp.OverlayOptions> = [];
|
||||
const compositeOps: Array<OverlayOptions> = [];
|
||||
for (let i = 0; i < emojiEntries.length; i++) {
|
||||
const item = emojiEntries[i];
|
||||
const emojiBuffer = await loadEmojiImage(item.surrogates, size);
|
||||
|
||||
@@ -9,6 +9,7 @@ const APP_DIR = fileURLToPath(new URL('..', import.meta.url));
|
||||
const SOURCE_DIR = join(APP_DIR, 'src');
|
||||
const JSX_EXTENSIONS = new Set(['.tsx', '.jsx']);
|
||||
const SKIP_DIRS = new Set(['node_modules', 'dist', 'coverage', '.cache', '.swc']);
|
||||
const GENERATED_FILES = new Set([join(SOURCE_DIR, 'features', 'ui', 'components', 'SVGMasks.tsx')]);
|
||||
const NON_THEMEABLE_IDENTIFIERS = new Set([
|
||||
'Fragment',
|
||||
'I18nProvider',
|
||||
@@ -153,7 +154,9 @@ function collectFiles(paths) {
|
||||
files.push(absolute);
|
||||
}
|
||||
}
|
||||
return Array.from(new Set(files)).sort();
|
||||
return Array.from(new Set(files))
|
||||
.filter((file) => !GENERATED_FILES.has(file))
|
||||
.sort();
|
||||
}
|
||||
|
||||
function parseSource(source, filePath) {
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
'Segoe UI Symbol', 'Segoe UI Historic', 'Apple Symbols',
|
||||
'Noto Sans Symbols', 'Noto Sans Symbols 2', 'Noto Sans Math', 'Cambria Math';
|
||||
--font-fallback-mono:
|
||||
'Noto Sans Mono',
|
||||
'Fluxer Sans SC', 'Fluxer Sans TC', 'Fluxer Sans JP', 'Fluxer Sans KR',
|
||||
var(--font-fallback-sans);
|
||||
'Noto Sans Mono', 'Fluxer Sans SC', 'Fluxer Sans TC', 'Fluxer Sans JP', 'Fluxer Sans KR', var(--font-fallback-sans);
|
||||
|
||||
--font-sans:
|
||||
'Fluxer Sans', 'Fluxer Sans Arabic', 'Fluxer Sans Hebrew', 'Fluxer Sans Devanagari', 'Fluxer Sans Thai Looped',
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0980-0983, U+0985-098C, U+098F-0990, U+0993-09A8, U+09AA-09B0, U+09B2, U+09B6-09B9, U+09BC-09C4, U+09C7-09C8, U+09CB-09CE, U+09D7, U+09DC-09DD, U+09DF-09E3, U+09E6-09FE;
|
||||
unicode-range:
|
||||
U+0980-0983, U+0985-098C, U+098F-0990, U+0993-09A8, U+09AA-09B0, U+09B2, U+09B6-09B9, U+09BC-09C4, U+09C7-09C8, U+09CB-09CE, U+09D7, U+09DC-09DD, U+09DF-09E3, U+09E6-09FE;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -14,7 +15,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0B82-0B83, U+0B85-0B8A, U+0B8E-0B90, U+0B92-0B95, U+0B99-0B9A, U+0B9C, U+0B9E-0B9F, U+0BA3-0BA4, U+0BA8-0BAA, U+0BAE-0BB9, U+0BBE-0BC2, U+0BC6-0BC8, U+0BCA-0BCD, U+0BD0, U+0BD7, U+0BE6-0BFA;
|
||||
unicode-range:
|
||||
U+0B82-0B83, U+0B85-0B8A, U+0B8E-0B90, U+0B92-0B95, U+0B99-0B9A, U+0B9C, U+0B9E-0B9F, U+0BA3-0BA4, U+0BA8-0BAA, U+0BAE-0BB9, U+0BBE-0BC2, U+0BC6-0BC8, U+0BCA-0BCD, U+0BD0, U+0BD7, U+0BE6-0BFA;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -22,7 +24,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0C00-0C0C, U+0C0E-0C10, U+0C12-0C28, U+0C2A-0C39, U+0C3C-0C44, U+0C46-0C48, U+0C4A-0C4D, U+0C55-0C56, U+0C58-0C5A, U+0C5D, U+0C60-0C63, U+0C66-0C6F, U+0C77-0C7F;
|
||||
unicode-range:
|
||||
U+0C00-0C0C, U+0C0E-0C10, U+0C12-0C28, U+0C2A-0C39, U+0C3C-0C44, U+0C46-0C48, U+0C4A-0C4D, U+0C55-0C56, U+0C58-0C5A, U+0C5D, U+0C60-0C63, U+0C66-0C6F, U+0C77-0C7F;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -30,7 +33,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0C80-0C8C, U+0C8E-0C90, U+0C92-0CA8, U+0CAA-0CB3, U+0CB5-0CB9, U+0CBC-0CC4, U+0CC6-0CC8, U+0CCA-0CCD, U+0CD5-0CD6, U+0CDD-0CDE, U+0CE0-0CE3, U+0CE6-0CEF, U+0CF1-0CF3;
|
||||
unicode-range:
|
||||
U+0C80-0C8C, U+0C8E-0C90, U+0C92-0CA8, U+0CAA-0CB3, U+0CB5-0CB9, U+0CBC-0CC4, U+0CC6-0CC8, U+0CCA-0CCD, U+0CD5-0CD6, U+0CDD-0CDE, U+0CE0-0CE3, U+0CE6-0CEF, U+0CF1-0CF3;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -46,7 +50,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0A81-0A83, U+0A85-0A8D, U+0A8F-0A91, U+0A93-0AA8, U+0AAA-0AB0, U+0AB2-0AB3, U+0AB5-0AB9, U+0ABC-0AC5, U+0AC7-0AC9, U+0ACB-0ACD, U+0AD0, U+0AE0-0AE3, U+0AE6-0AF1, U+0AF9-0AFF;
|
||||
unicode-range:
|
||||
U+0A81-0A83, U+0A85-0A8D, U+0A8F-0A91, U+0A93-0AA8, U+0AAA-0AB0, U+0AB2-0AB3, U+0AB5-0AB9, U+0ABC-0AC5, U+0AC7-0AC9, U+0ACB-0ACD, U+0AD0, U+0AE0-0AE3, U+0AE6-0AF1, U+0AF9-0AFF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -54,7 +59,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0A01-0A03, U+0A05-0A0A, U+0A0F-0A10, U+0A13-0A28, U+0A2A-0A30, U+0A32-0A33, U+0A35-0A36, U+0A38-0A39, U+0A3C, U+0A3E-0A42, U+0A47-0A48, U+0A4B-0A4D, U+0A51, U+0A59-0A5C, U+0A5E, U+0A66-0A76;
|
||||
unicode-range:
|
||||
U+0A01-0A03, U+0A05-0A0A, U+0A0F-0A10, U+0A13-0A28, U+0A2A-0A30, U+0A32-0A33, U+0A35-0A36, U+0A38-0A39, U+0A3C, U+0A3E-0A42, U+0A47-0A48, U+0A4B-0A4D, U+0A51, U+0A59-0A5C, U+0A5E, U+0A66-0A76;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -62,7 +68,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0B01-0B03, U+0B05-0B0C, U+0B0F-0B10, U+0B13-0B28, U+0B2A-0B30, U+0B32-0B33, U+0B35-0B39, U+0B3C-0B44, U+0B47-0B48, U+0B4B-0B4D, U+0B55-0B57, U+0B5C-0B5D, U+0B5F-0B63, U+0B66-0B77;
|
||||
unicode-range:
|
||||
U+0B01-0B03, U+0B05-0B0C, U+0B0F-0B10, U+0B13-0B28, U+0B2A-0B30, U+0B32-0B33, U+0B35-0B39, U+0B3C-0B44, U+0B47-0B48, U+0B4B-0B4D, U+0B55-0B57, U+0B5C-0B5D, U+0B5F-0B63, U+0B66-0B77;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -70,7 +77,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0D81-0D83, U+0D85-0D96, U+0D9A-0DB1, U+0DB3-0DBB, U+0DBD, U+0DC0-0DC6, U+0DCA, U+0DCF-0DD4, U+0DD6, U+0DD8-0DDF, U+0DE6-0DEF, U+0DF2-0DF4, U+111E1-111F4;
|
||||
unicode-range:
|
||||
U+0D81-0D83, U+0D85-0D96, U+0D9A-0DB1, U+0DB3-0DBB, U+0DBD, U+0DC0-0DC6, U+0DCA, U+0DCF-0DD4, U+0DD6, U+0DD8-0DDF, U+0DE6-0DEF, U+0DF2-0DF4, U+111E1-111F4;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -86,7 +94,9 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+0E81-0E82, U+0E84, U+0E86-0E8A, U+0E8C-0EA3, U+0EA5, U+0EA7-0EBD, U+0EC0-0EC4, U+0EC6, U+0EC8-0ECE, U+0ED0-0ED9, U+0EDC-0EDF;
|
||||
unicode-range:
|
||||
U+0E81-0E82, U+0E84, U+0E86-0E8A, U+0E8C-0EA3, U+0EA5, U+0EA7-0EBD, U+0EC0-0EC4, U+0EC6, U+0EC8-0ECE, U+0ED0-0ED9,
|
||||
U+0EDC-0EDF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -118,7 +128,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+1200-1248, U+124A-124D, U+1250-1256, U+1258, U+125A-125D, U+1260-1288, U+128A-128D, U+1290-12B0, U+12B2-12B5, U+12B8-12BE, U+12C0, U+12C2-12C5, U+12C8-12D6, U+12D8-1310, U+1312-1315, U+1318-135A, U+135D-137C, U+1380-1399, U+2D80-2D96, U+2DA0-2DA6, U+2DA8-2DAE, U+2DB0-2DB6, U+2DB8-2DBE, U+2DC0-2DC6, U+2DC8-2DCE, U+2DD0-2DD6, U+2DD8-2DDE, U+AB01-AB06, U+AB09-AB0E, U+AB11-AB16, U+AB20-AB26, U+AB28-AB2E, U+1E7E0-1E7E6, U+1E7E8-1E7EB, U+1E7ED-1E7EE, U+1E7F0-1E7FE;
|
||||
unicode-range:
|
||||
U+1200-1248, U+124A-124D, U+1250-1256, U+1258, U+125A-125D, U+1260-1288, U+128A-128D, U+1290-12B0, U+12B2-12B5, U+12B8-12BE, U+12C0, U+12C2-12C5, U+12C8-12D6, U+12D8-1310, U+1312-1315, U+1318-135A, U+135D-137C, U+1380-1399, U+2D80-2D96, U+2DA0-2DA6, U+2DA8-2DAE, U+2DB0-2DB6, U+2DB8-2DBE, U+2DC0-2DC6, U+2DC8-2DCE, U+2DD0-2DD6, U+2DD8-2DDE, U+AB01-AB06, U+AB09-AB0E, U+AB11-AB16, U+AB20-AB26, U+AB28-AB2E, U+1E7E0-1E7E6, U+1E7E8-1E7EB, U+1E7ED-1E7EE, U+1E7F0-1E7FE;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Fallback';
|
||||
@@ -198,7 +209,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+2190-2199, U+2300-230F, U+2311-2315, U+2317, U+231C-231F, U+2322-2323, U+2329-232A, U+232C-2335, U+237C, U+2380-2394, U+2396-239A, U+23AF, U+23BE-23CD, U+23D0-23DB, U+23E2-23E8, U+2460-24FF, U+25CC, U+260A-260D, U+2613, U+2624-262F, U+2638-263B, U+263D-2653, U+2669-267E, U+2690-269D, U+26A2-26A9, U+26AD-26BC, U+26CE, U+26E2-26FF, U+271D-2721, U+2776-2793, U+1F100-1F10C, U+1F110-1F16C, U+1F170-1F190, U+1F19B-1F1AC, U+1F546-1F549, U+1F54F, U+1F610, U+1F700-1F773;
|
||||
unicode-range:
|
||||
U+2190-2199, U+2300-230F, U+2311-2315, U+2317, U+231C-231F, U+2322-2323, U+2329-232A, U+232C-2335, U+237C, U+2380-2394, U+2396-239A, U+23AF, U+23BE-23CD, U+23D0-23DB, U+23E2-23E8, U+2460-24FF, U+25CC, U+260A-260D, U+2613, U+2624-262F, U+2638-263B, U+263D-2653, U+2669-267E, U+2690-269D, U+26A2-26A9, U+26AD-26BC, U+26CE, U+26E2-26FF, U+271D-2721, U+2776-2793, U+1F100-1F10C, U+1F110-1F16C, U+1F170-1F190, U+1F19B-1F1AC, U+1F546-1F549, U+1F54F, U+1F610, U+1F700-1F773;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Symbols';
|
||||
@@ -206,7 +218,8 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+21AF, U+21E6-21F0, U+21F3, U+2316, U+2318, U+231A-231B, U+2324-2328, U+232B, U+237B, U+237D-237F, U+23CE-23CF, U+23E9-23EA, U+23ED-23EF, U+23F1-2426, U+2440-244A, U+25A0-25CB, U+25CD-2609, U+260E-2612, U+2614-2623, U+2630-2637, U+263C, U+2654-2668, U+267F-268F, U+269E-26A1, U+26AA-26AC, U+26BD-26CD, U+26CF-26E1, U+2700-2704, U+2706-2709, U+270B-271C, U+2722-2727, U+2729-274B, U+274D, U+274F-2753, U+2756-2775, U+2794, U+2798-27AF, U+27B1-27BE, U+2800-28FF, U+2B00-2B0D, U+2B12-2B2F, U+2B4D-2B73, U+2B76-2B95, U+2B97-2BFD, U+2BFF, U+1F000-1F02B, U+1F030-1F093, U+1F0A0-1F0AE, U+1F0B1-1F0BF, U+1F0C1-1F0CF, U+1F0D1-1F0F5, U+1F10D-1F10F, U+1F16D-1F16F, U+1F1AD, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F321-1F32C, U+1F336, U+1F378, U+1F37D, U+1F393-1F39F, U+1F3A7, U+1F3AC-1F3AE, U+1F3C2, U+1F3C4, U+1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4DA, U+1F4DF, U+1F4E4-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50A, U+1F50D, U+1F512-1F513, U+1F53E-1F545, U+1F54A, U+1F550-1F579, U+1F57B-1F594, U+1F597-1F5A3, U+1F5A5-1F5FA, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CB, U+1F6CD-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F774-1F776, U+1F77B-1F7D9, U+1F7E0-1F7EB, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8B1, U+1F900-1F90B, U+1F93B, U+1F946, U+1FA00-1FA53, U+1FA60-1FA6D, U+1FA70-1FA74, U+1FA78-1FA7A, U+1FA80-1FA86, U+1FA90-1FAA8, U+1FAB0-1FAB6, U+1FAC0-1FAC2, U+1FAD0-1FAD6, U+1FB00-1FB92, U+1FB94-1FBCA, U+1FBF0-1FBF9;
|
||||
unicode-range:
|
||||
U+21AF, U+21E6-21F0, U+21F3, U+2316, U+2318, U+231A-231B, U+2324-2328, U+232B, U+237B, U+237D-237F, U+23CE-23CF, U+23E9-23EA, U+23ED-23EF, U+23F1-2426, U+2440-244A, U+25A0-25CB, U+25CD-2609, U+260E-2612, U+2614-2623, U+2630-2637, U+263C, U+2654-2668, U+267F-268F, U+269E-26A1, U+26AA-26AC, U+26BD-26CD, U+26CF-26E1, U+2700-2704, U+2706-2709, U+270B-271C, U+2722-2727, U+2729-274B, U+274D, U+274F-2753, U+2756-2775, U+2794, U+2798-27AF, U+27B1-27BE, U+2800-28FF, U+2B00-2B0D, U+2B12-2B2F, U+2B4D-2B73, U+2B76-2B95, U+2B97-2BFD, U+2BFF, U+1F000-1F02B, U+1F030-1F093, U+1F0A0-1F0AE, U+1F0B1-1F0BF, U+1F0C1-1F0CF, U+1F0D1-1F0F5, U+1F10D-1F10F, U+1F16D-1F16F, U+1F1AD, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F321-1F32C, U+1F336, U+1F378, U+1F37D, U+1F393-1F39F, U+1F3A7, U+1F3AC-1F3AE, U+1F3C2, U+1F3C4, U+1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4DA, U+1F4DF, U+1F4E4-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50A, U+1F50D, U+1F512-1F513, U+1F53E-1F545, U+1F54A, U+1F550-1F579, U+1F57B-1F594, U+1F597-1F5A3, U+1F5A5-1F5FA, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CB, U+1F6CD-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F774-1F776, U+1F77B-1F7D9, U+1F7E0-1F7EB, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8B1, U+1F900-1F90B, U+1F93B, U+1F946, U+1FA00-1FA53, U+1FA60-1FA6D, U+1FA70-1FA74, U+1FA78-1FA7A, U+1FA80-1FA86, U+1FA90-1FAA8, U+1FAB0-1FAB6, U+1FAC0-1FAC2, U+1FAD0-1FAD6, U+1FB00-1FB92, U+1FB94-1FBCA, U+1FBF0-1FBF9;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fluxer Math';
|
||||
@@ -214,5 +227,6 @@
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
unicode-range: U+2102, U+210A-2113, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2200-22FF, U+27C0-27FF, U+2900-2AFF, U+1D400-1D454, U+1D456-1D49C, U+1D49E-1D49F, U+1D4A2, U+1D4A5-1D4A6, U+1D4A9-1D4AC, U+1D4AE-1D4B9, U+1D4BB, U+1D4BD-1D4C3, U+1D4C5-1D505, U+1D507-1D50A, U+1D50D-1D514, U+1D516-1D51C, U+1D51E-1D539, U+1D53B-1D53E, U+1D540-1D544, U+1D546, U+1D54A-1D550, U+1D552-1D6A5, U+1D6A8-1D7CB, U+1D7CE-1D7FF;
|
||||
unicode-range:
|
||||
U+2102, U+210A-2113, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2200-22FF, U+27C0-27FF, U+2900-2AFF, U+1D400-1D454, U+1D456-1D49C, U+1D49E-1D49F, U+1D4A2, U+1D4A5-1D4A6, U+1D4A9-1D4AC, U+1D4AE-1D4B9, U+1D4BB, U+1D4BD-1D4C3, U+1D4C5-1D505, U+1D507-1D50A, U+1D50D-1D514, U+1D516-1D51C, U+1D51E-1D539, U+1D53B-1D53E, U+1D540-1D544, U+1D546, U+1D54A-1D550, U+1D552-1D6A5, U+1D6A8-1D7CB, U+1D7CE-1D7FF;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,44 @@
|
||||
|
||||
import {resolveRetryAfterMs} from '@app/features/messaging/utils/RetryAfterUtils';
|
||||
import {HttpError} from '@app/features/platform/types/EndpointError';
|
||||
import * as SlowmodeCommands from '@app/features/slowmode/commands/SlowmodeCommands';
|
||||
import Slowmode from '@app/features/slowmode/state/Slowmode';
|
||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||
import type {PendingMessageSend} from '@app/features/slowmode/commands/SlowmodeCommands';
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
|
||||
interface SlowmodeStore {
|
||||
clearChannel(channelId: string): void;
|
||||
getLastSendTimestamp(channelId: string): number | null;
|
||||
getSlowmodeRemaining(channelId: string, rateLimitPerUser: number): number;
|
||||
}
|
||||
|
||||
interface SlowmodeCommandModule {
|
||||
clampSlowmodeRetryAfterMs(retryAfterMs: number | null): number;
|
||||
confirmMessageSend(channelId: string, sentAt: string, pending?: PendingMessageSend): void;
|
||||
discardPendingMessageSend(channelId: string, pending: PendingMessageSend): void;
|
||||
recordPendingMessageSend(channelId: string): PendingMessageSend;
|
||||
updateSlowmodeRemaining(channelId: string, retryAfterMs: number): void;
|
||||
}
|
||||
|
||||
let Slowmode: SlowmodeStore;
|
||||
let SlowmodeCommands: SlowmodeCommandModule;
|
||||
|
||||
beforeAll(async () => {
|
||||
const debug = vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => undefined);
|
||||
try {
|
||||
const [slowmodeModule, commandModule, persistenceModule] = await Promise.all([
|
||||
import('@app/features/slowmode/state/Slowmode'),
|
||||
import('@app/features/slowmode/commands/SlowmodeCommands'),
|
||||
import('@app/features/platform/utils/MobXPersistence'),
|
||||
]);
|
||||
Slowmode = slowmodeModule.default;
|
||||
SlowmodeCommands = commandModule;
|
||||
await persistenceModule.awaitHydration('Slowmode');
|
||||
expect(debug).toHaveBeenCalledExactlyOnceWith('Store Slowmode hydrated from AppStorage and is now persisting.');
|
||||
} finally {
|
||||
debug.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
const CHANNEL_ID = '1234567890123456789';
|
||||
const RATE_LIMIT_PER_USER = 5;
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {installVoiceMenuTestBootstrap} from '@app/features/ui/action_menu/items/__fixtures__/VoiceMenuTestBootstrap';
|
||||
import type {VoiceParticipantMenuSource} from '@app/features/ui/action_menu/items/VoiceParticipantMenuTypes';
|
||||
import type {User} from '@app/features/user/models/User';
|
||||
import {act, createElement} from 'react';
|
||||
import {createRoot, type Root} from 'react-dom/client';
|
||||
import {afterEach, expect, test, vi} from 'vitest';
|
||||
|
||||
vi.mock('@lingui/core/macro', () => {
|
||||
const descriptor = (value: unknown): unknown => (typeof value === 'string' ? {message: value} : value);
|
||||
return {msg: descriptor, t: descriptor, plural: () => '', select: () => '', selectOrdinal: () => ''};
|
||||
});
|
||||
vi.mock('@lingui/react/macro', () => ({
|
||||
Trans: () => null,
|
||||
useLingui: () => ({i18n: {_: (descriptor: {message?: string}) => descriptor.message ?? '', locale: 'en'}}),
|
||||
}));
|
||||
|
||||
installVoiceMenuTestBootstrap();
|
||||
|
||||
const {useVoiceParticipantMenuData} = await import('@app/features/ui/action_menu/items/VoiceParticipantMenuData');
|
||||
const {default: EntranceSoundListenerPrefs} = await import('@app/features/voice/state/EntranceSoundListenerPrefs');
|
||||
const {default: MediaEngine} = await import('@app/features/voice/engine/MediaEngineFacade');
|
||||
const {observer} = await import('mobx-react-lite');
|
||||
|
||||
(globalThis as {IS_REACT_ACT_ENVIRONMENT?: boolean}).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const TARGET_USER = {id: '111', username: 'target'} as unknown as User;
|
||||
const PARTICIPANT_SOURCE: VoiceParticipantMenuSource = {kind: 'participant'};
|
||||
const CAMERA_SOURCE: VoiceParticipantMenuSource = {kind: 'camera'};
|
||||
const noop = (): void => undefined;
|
||||
|
||||
interface MenuLeaf {
|
||||
label?: string;
|
||||
checked?: boolean;
|
||||
value?: number;
|
||||
items?: Array<MenuLeaf>;
|
||||
}
|
||||
|
||||
interface MenuGroup {
|
||||
items: Array<MenuLeaf>;
|
||||
}
|
||||
|
||||
const shadowedMediaEngineKeys: Array<string> = [];
|
||||
let root: Root | null = null;
|
||||
let container: HTMLDivElement | null = null;
|
||||
|
||||
function shadowMediaEngine(key: string, value: unknown): void {
|
||||
Object.defineProperty(MediaEngine, key, {value, configurable: true, writable: true});
|
||||
shadowedMediaEngineKeys.push(key);
|
||||
}
|
||||
|
||||
function findLeaf(groups: Array<MenuGroup>, label: string): MenuLeaf | null {
|
||||
for (const group of groups) {
|
||||
for (const item of group.items) {
|
||||
if (item.label === label) return item;
|
||||
for (const child of item.items ?? []) {
|
||||
if (child.label === label) return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function renderMenu(
|
||||
options: Parameters<typeof useVoiceParticipantMenuData>[0],
|
||||
): Promise<{groups: () => Array<MenuGroup>}> {
|
||||
const latest: {groups: Array<MenuGroup>} = {groups: []};
|
||||
const Probe = observer(function Probe() {
|
||||
latest.groups = useVoiceParticipantMenuData(options).groups as unknown as Array<MenuGroup>;
|
||||
return null;
|
||||
});
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
await act(async () => {
|
||||
root?.render(createElement(Probe));
|
||||
});
|
||||
return {groups: () => latest.groups};
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await act(async () => {
|
||||
root?.unmount();
|
||||
});
|
||||
root = null;
|
||||
container?.remove();
|
||||
container = null;
|
||||
for (const key of shadowedMediaEngineKeys.splice(0)) {
|
||||
delete (MediaEngine as unknown as Record<string, unknown>)[key];
|
||||
}
|
||||
EntranceSoundListenerPrefs.reset(TARGET_USER.id);
|
||||
});
|
||||
|
||||
test('entrance-sound checkbox tracks the pref while the menu stays open', async () => {
|
||||
const menu = await renderMenu({
|
||||
user: TARGET_USER,
|
||||
surface: 'call-tile',
|
||||
source: PARTICIPANT_SOURCE,
|
||||
onClose: noop,
|
||||
});
|
||||
expect(findLeaf(menu.groups(), 'Mute entrance sound')?.checked).toBe(false);
|
||||
await act(async () => {
|
||||
EntranceSoundListenerPrefs.setMuted(TARGET_USER.id, true);
|
||||
});
|
||||
expect(findLeaf(menu.groups(), 'Mute entrance sound')?.checked).toBe(true);
|
||||
await act(async () => {
|
||||
EntranceSoundListenerPrefs.setVolume(TARGET_USER.id, 42);
|
||||
});
|
||||
expect(findLeaf(menu.groups(), 'Entrance sound volume')?.value).toBe(42);
|
||||
});
|
||||
|
||||
test('disable video locally is offered for another user camera tile in the local room', async () => {
|
||||
shadowMediaEngine('connected', true);
|
||||
shadowMediaEngine('connectionId', 'self-connection');
|
||||
shadowMediaEngine('channelId', 'channel-1');
|
||||
shadowMediaEngine('getVoiceStateByConnectionId', (connectionId: string) =>
|
||||
connectionId === 'target-connection' ? {channel_id: 'channel-1', user_id: TARGET_USER.id} : null,
|
||||
);
|
||||
const menu = await renderMenu({
|
||||
user: TARGET_USER,
|
||||
connectionId: 'target-connection',
|
||||
surface: 'call-tile',
|
||||
source: CAMERA_SOURCE,
|
||||
onClose: noop,
|
||||
});
|
||||
expect(findLeaf(menu.groups(), 'Disable video locally')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('offers a single inline user-volume slider and no nested secondary submenu wrapper', async () => {
|
||||
const REMOVED_SUBMENU_LABELS = [
|
||||
'Media controls',
|
||||
'Device controls',
|
||||
'Display options',
|
||||
'User actions',
|
||||
'Relationship',
|
||||
'Relationship actions',
|
||||
'Moderation',
|
||||
'Moderation actions',
|
||||
'Advanced',
|
||||
'Advanced actions',
|
||||
];
|
||||
shadowMediaEngine('connected', true);
|
||||
shadowMediaEngine('connectionId', 'self-connection');
|
||||
shadowMediaEngine('channelId', 'channel-1');
|
||||
shadowMediaEngine('getVoiceStateByConnectionId', (connectionId: string) =>
|
||||
connectionId === 'target-connection' ? {channel_id: 'channel-1', user_id: TARGET_USER.id} : null,
|
||||
);
|
||||
const menu = await renderMenu({
|
||||
user: TARGET_USER,
|
||||
connectionId: 'target-connection',
|
||||
surface: 'participant-list',
|
||||
source: PARTICIPANT_SOURCE,
|
||||
onClose: noop,
|
||||
});
|
||||
const groups = menu.groups();
|
||||
const topLevelItems = groups.flatMap((group) => group.items);
|
||||
const topLevelSliders = topLevelItems.filter((item) => typeof item.value === 'number');
|
||||
expect(topLevelSliders).toHaveLength(1);
|
||||
expect(topLevelSliders[0]?.label).toBe('User volume');
|
||||
const submenuLabels = topLevelItems.filter((item) => Array.isArray(item.items)).map((item) => item.label);
|
||||
expect(submenuLabels.every((label) => label === 'Entrance sound')).toBe(true);
|
||||
for (const label of REMOVED_SUBMENU_LABELS) {
|
||||
expect(findLeaf(groups, label)).toBeNull();
|
||||
}
|
||||
});
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {installVoiceMenuTestBootstrap} from '@app/features/ui/action_menu/items/__fixtures__/VoiceMenuTestBootstrap';
|
||||
import type {VoiceParticipantMenuScreenShareSource} from '@app/features/ui/action_menu/items/VoiceParticipantMenuTypes';
|
||||
import type {I18n} from '@lingui/core';
|
||||
import {expect, test, vi} from 'vitest';
|
||||
|
||||
vi.mock('@lingui/core/macro', () => {
|
||||
const descriptor = (value: unknown): unknown => (typeof value === 'string' ? {message: value} : value);
|
||||
return {msg: descriptor, t: descriptor, plural: () => '', select: () => '', selectOrdinal: () => ''};
|
||||
});
|
||||
vi.mock('@lingui/react/macro', () => ({
|
||||
Trans: () => null,
|
||||
useLingui: () => ({i18n: {_: (descriptor: {message?: string}) => descriptor.message ?? '', locale: 'en'}}),
|
||||
}));
|
||||
vi.mock('@app/features/voice/components/ActiveScreenShareMenu', () => ({
|
||||
changeActiveScreenShare: vi.fn(async () => undefined),
|
||||
stopActiveScreenShare: vi.fn(async () => undefined),
|
||||
}));
|
||||
vi.mock('@app/features/voice/components/modals/ScreenSharePickerModal', () => ({
|
||||
openScreenSharePreviewPrivacyModal: vi.fn(),
|
||||
}));
|
||||
vi.mock('@app/features/voice/engine/MediaEngineFacade', () => ({
|
||||
default: {
|
||||
applyLocalAudioPreferencesForUser: vi.fn(),
|
||||
getVoiceStateByConnectionId: () => null,
|
||||
connectionId: null,
|
||||
},
|
||||
}));
|
||||
vi.mock('@app/features/voice/state/PopoutWindowManager', () => ({
|
||||
default: {openTilePopout: vi.fn()},
|
||||
isVoicePopoutSupported: () => false,
|
||||
}));
|
||||
vi.mock('@app/features/voice/state/StreamAudioPrefs', () => ({
|
||||
default: {setMuted: vi.fn(), setVolume: vi.fn()},
|
||||
}));
|
||||
vi.mock('@app/features/voice/state/VoiceSettings', () => ({
|
||||
default: {showMyOwnScreenShare: false, pauseOwnScreenSharePreviewOnUnfocus: false},
|
||||
}));
|
||||
vi.mock('@app/features/voice/commands/VoiceSettingsCommands', () => ({
|
||||
update: vi.fn(),
|
||||
}));
|
||||
|
||||
installVoiceMenuTestBootstrap();
|
||||
|
||||
const {buildVoiceParticipantStreamMenu} = await import(
|
||||
'@app/features/ui/action_menu/items/VoiceParticipantStreamMenuBuilder'
|
||||
);
|
||||
|
||||
const i18n = {
|
||||
locale: 'en',
|
||||
_: (descriptor: {message?: string}) => descriptor.message ?? '',
|
||||
} as unknown as I18n;
|
||||
|
||||
interface MenuLeaf {
|
||||
label?: string;
|
||||
items?: Array<MenuLeaf>;
|
||||
}
|
||||
|
||||
function streamMenu(source: VoiceParticipantMenuScreenShareSource): Array<{items: Array<MenuLeaf>}> {
|
||||
return buildVoiceParticipantStreamMenu({
|
||||
i18n,
|
||||
userId: '111',
|
||||
channelId: null,
|
||||
participantIdentity: 'user_111_conn',
|
||||
displayName: 'Alice',
|
||||
isCurrentUserConnectedToVoice: false,
|
||||
source,
|
||||
streamVolume: 100,
|
||||
isStreamMuted: false,
|
||||
showMyOwnScreenShare: false,
|
||||
pauseOwnScreenSharePreviewOnUnfocus: false,
|
||||
onClose: () => undefined,
|
||||
}) as unknown as Array<{items: Array<MenuLeaf>}>;
|
||||
}
|
||||
|
||||
function findLeaf(groups: Array<{items: Array<MenuLeaf>}>, label: string): MenuLeaf | null {
|
||||
for (const group of groups) {
|
||||
for (const item of group.items) {
|
||||
if (item.label === label) return item;
|
||||
for (const child of item.items ?? []) {
|
||||
if (child.label === label) return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const OWN_STREAM_SOURCE: VoiceParticipantMenuScreenShareSource = {
|
||||
kind: 'screen-share',
|
||||
streamKey: 'stream-key',
|
||||
state: {kind: 'own'},
|
||||
};
|
||||
|
||||
const WATCHED_REMOTE_STREAM_SOURCE: VoiceParticipantMenuScreenShareSource = {
|
||||
kind: 'screen-share',
|
||||
streamKey: 'stream-key',
|
||||
state: {kind: 'remote-watched', hasAudio: true, onStopWatching: () => undefined},
|
||||
};
|
||||
|
||||
test('own stream keeps a More options submenu with the screen-share preferences', () => {
|
||||
const groups = streamMenu(OWN_STREAM_SOURCE);
|
||||
const moreOptions = findLeaf(groups, 'More options');
|
||||
expect(moreOptions).not.toBeNull();
|
||||
expect(findLeaf(groups, 'Show My Screen Share')).not.toBeNull();
|
||||
expect(findLeaf(groups, 'Report Problem')).toBeNull();
|
||||
});
|
||||
|
||||
test('remote watched stream omits the now-empty More options submenu and keeps audio controls', () => {
|
||||
const groups = streamMenu(WATCHED_REMOTE_STREAM_SOURCE);
|
||||
expect(findLeaf(groups, 'More options')).toBeNull();
|
||||
expect(findLeaf(groups, 'Mute')).not.toBeNull();
|
||||
expect(findLeaf(groups, 'Stream volume')).not.toBeNull();
|
||||
expect(findLeaf(groups, 'Report Problem')).toBeNull();
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const HARNESS_ENDPOINT = 'https://primary.test/api';
|
||||
|
||||
export function installVoiceMenuTestBootstrap(): void {
|
||||
const host = globalThis as unknown as {window?: Record<string, unknown>};
|
||||
if (typeof host.window === 'undefined') {
|
||||
host.window = host as unknown as Record<string, unknown>;
|
||||
}
|
||||
host.window.__FLUXER_BOOTSTRAP__ = {
|
||||
config: {
|
||||
releaseChannel: 'stable',
|
||||
bootstrapApiEndpoint: HARNESS_ENDPOINT,
|
||||
bootstrapApiPublicEndpoint: HARNESS_ENDPOINT,
|
||||
},
|
||||
instance: {
|
||||
api_code_version: Number.MAX_SAFE_INTEGER,
|
||||
endpoints: {
|
||||
api: HARNESS_ENDPOINT,
|
||||
api_client: HARNESS_ENDPOINT,
|
||||
api_public: HARNESS_ENDPOINT,
|
||||
gateway: 'wss://gateway.primary.test',
|
||||
media: 'https://media.primary.test',
|
||||
static_cdn: 'https://cdn.primary.test',
|
||||
marketing: 'https://primary.test',
|
||||
admin: 'https://admin.primary.test',
|
||||
invite: 'https://primary.test/invite',
|
||||
gift: 'https://primary.test/gift',
|
||||
webapp: 'https://app.primary.test',
|
||||
upload_relay: 'https://upload.primary.test',
|
||||
},
|
||||
captcha: {provider: 'none', hcaptcha_site_key: null, turnstile_site_key: null},
|
||||
features: {
|
||||
voice_enabled: false,
|
||||
stripe_enabled: false,
|
||||
self_hosted: false,
|
||||
presigned_attachment_uploads: false,
|
||||
emails_enabled: false,
|
||||
},
|
||||
gif: {provider: 'klipy', display_name: 'Klipy', attribution_required: false},
|
||||
sso: {enabled: false, enforced: false, display_name: null, redirect_uri: ''},
|
||||
registration: {mode: 'open', admin_registration_urls_enabled: true},
|
||||
community: {single_community: false, single_community_guild_id: null, direct_messages_disabled: false},
|
||||
services: {gif_enabled: true, youtube_enabled: false, bluesky_enabled: false},
|
||||
limits: undefined,
|
||||
push: {public_vapid_key: null},
|
||||
app_public: {
|
||||
branding: {
|
||||
product_name: 'Fluxer',
|
||||
icon_url: null,
|
||||
symbol_url: null,
|
||||
logo_url: null,
|
||||
wordmark_url: null,
|
||||
favicon_url: null,
|
||||
theme_color: null,
|
||||
},
|
||||
setup: {configured: true, admin_url: null},
|
||||
legal: {terms_url: null, privacy_url: null},
|
||||
registration: {collect_date_of_birth: true},
|
||||
},
|
||||
},
|
||||
geoip: {
|
||||
countryCode: null,
|
||||
regionCode: null,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
ageRestrictedGeos: [],
|
||||
ageBlockedGeos: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -6,9 +6,11 @@ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
|
||||
async function loadTick() {
|
||||
vi.resetModules();
|
||||
const {Logger} = await import('@app/features/platform/utils/AppLogger');
|
||||
const debug = vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => undefined);
|
||||
const {default: Window} = await import('@app/features/window/state/Window');
|
||||
const {default: Tick} = await import('@app/features/ui/state/Tick');
|
||||
return {Tick, Window};
|
||||
return {debug, Tick, Window};
|
||||
}
|
||||
|
||||
describe('Tick', () => {
|
||||
@@ -17,14 +19,16 @@ describe('Tick', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('keeps counting while the window is visible but not focused', async () => {
|
||||
const {Tick, Window} = await loadTick();
|
||||
const {debug, Tick, Window} = await loadTick();
|
||||
const dispose = autorun(() => Tick.nowSecond);
|
||||
const started = Tick.nowSecond;
|
||||
Window.setFocused(false);
|
||||
expect(debug).toHaveBeenCalledExactlyOnceWith('Window focus changed: false');
|
||||
vi.advanceTimersByTime(3000);
|
||||
expect(Window.visible).toBe(true);
|
||||
expect(Tick.nowSecond).toBe(started + 3);
|
||||
@@ -32,9 +36,10 @@ describe('Tick', () => {
|
||||
});
|
||||
|
||||
it('stops counting while the window is hidden', async () => {
|
||||
const {Tick, Window} = await loadTick();
|
||||
const {debug, Tick, Window} = await loadTick();
|
||||
const dispose = autorun(() => Tick.nowSecond);
|
||||
Window.setVisible(false);
|
||||
expect(debug).toHaveBeenCalledExactlyOnceWith('Window visibility changed: false');
|
||||
const stopped = Tick.nowSecond;
|
||||
vi.advanceTimersByTime(5000);
|
||||
expect(Tick.nowSecond).toBe(stopped);
|
||||
@@ -42,24 +47,28 @@ describe('Tick', () => {
|
||||
});
|
||||
|
||||
it('resynchronises to wall clock as soon as the window becomes visible again', async () => {
|
||||
const {Tick, Window} = await loadTick();
|
||||
const {debug, Tick, Window} = await loadTick();
|
||||
const dispose = autorun(() => Tick.nowSecond);
|
||||
Window.setVisible(false);
|
||||
const stopped = Tick.nowSecond;
|
||||
vi.advanceTimersByTime(60_000);
|
||||
expect(Tick.nowSecond).toBe(stopped);
|
||||
Window.setVisible(true);
|
||||
expect(debug).toHaveBeenNthCalledWith(1, 'Window visibility changed: false');
|
||||
expect(debug).toHaveBeenNthCalledWith(2, 'Window visibility changed: true');
|
||||
expect(debug).toHaveBeenCalledTimes(2);
|
||||
expect(Tick.nowSecond).toBe(stopped + 60);
|
||||
dispose();
|
||||
});
|
||||
|
||||
it('does not run an interval while nothing observes the clock', async () => {
|
||||
const {Tick} = await loadTick();
|
||||
const {debug, Tick} = await loadTick();
|
||||
const idle = Tick.nowSecond;
|
||||
vi.advanceTimersByTime(5000);
|
||||
expect(Tick.nowSecond).toBe(idle);
|
||||
const dispose = autorun(() => Tick.nowSecond);
|
||||
expect(Tick.nowSecond).toBe(idle + 5);
|
||||
expect(debug).not.toHaveBeenCalled();
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,6 +40,7 @@ interface FakeGraphHarness {
|
||||
interface FakeWindowHarness {
|
||||
requestedOptions: Array<AudioContextOptions | undefined>;
|
||||
contexts: Array<FakeAudioContext>;
|
||||
constructionErrors: Array<DOMException>;
|
||||
bodyListeners: Array<string>;
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ function installFakeWindow(
|
||||
honourSampleRate?: boolean;
|
||||
} = {},
|
||||
): FakeWindowHarness {
|
||||
const harness: FakeWindowHarness = {requestedOptions: [], contexts: [], bodyListeners: []};
|
||||
const harness: FakeWindowHarness = {requestedOptions: [], contexts: [], constructionErrors: [], bodyListeners: []};
|
||||
const throwOn = config.throwOn ?? ((): boolean => false);
|
||||
const honourSampleRate = config.honourSampleRate ?? true;
|
||||
|
||||
@@ -64,7 +65,9 @@ function installFakeWindow(
|
||||
constructor(options?: AudioContextOptions) {
|
||||
harness.requestedOptions.push(options);
|
||||
if (throwOn(options)) {
|
||||
throw new DOMException('sample rate not supported', 'NotSupportedError');
|
||||
const error = new DOMException('sample rate not supported', 'NotSupportedError');
|
||||
harness.constructionErrors.push(error);
|
||||
throw error;
|
||||
}
|
||||
this.sampleRate = (honourSampleRate ? options?.sampleRate : undefined) ?? HARDWARE_SAMPLE_RATE;
|
||||
harness.contexts.push(this as unknown as FakeAudioContext);
|
||||
@@ -219,15 +222,30 @@ describe('VoiceSharedAudioContext', () => {
|
||||
it('returns null without throwing when the construction fails', async () => {
|
||||
const harness = installFakeWindow({throwOn: () => true});
|
||||
const {getSharedVoiceAudioContext} = await loadModule();
|
||||
const warning = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
||||
try {
|
||||
let context: AudioContext | null | undefined;
|
||||
expect(() => {
|
||||
context = getSharedVoiceAudioContext();
|
||||
}).not.toThrow();
|
||||
|
||||
let context: AudioContext | null | undefined;
|
||||
expect(() => {
|
||||
context = getSharedVoiceAudioContext();
|
||||
}).not.toThrow();
|
||||
|
||||
expect(context).toBeNull();
|
||||
expect(harness.contexts).toHaveLength(0);
|
||||
expect(harness.requestedOptions).toHaveLength(1);
|
||||
expect(context).toBeNull();
|
||||
expect(harness.contexts).toHaveLength(0);
|
||||
expect(harness.requestedOptions).toEqual([{latencyHint: 'interactive'}]);
|
||||
expect(harness.constructionErrors).toHaveLength(1);
|
||||
expect(warning).toHaveBeenCalledTimes(1);
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/^%c\[\d{2}:\d{2}:\d{2}\] \[VoiceSharedAudioContext\] \[Warn\]$/),
|
||||
'color:#ffc107;font-weight:normal',
|
||||
'Failed to construct voice AudioContext',
|
||||
{
|
||||
options: {latencyHint: 'interactive'},
|
||||
error: harness.constructionErrors[0],
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
warning.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('caches the context across calls and rebuilds it once it has closed', async () => {
|
||||
@@ -267,10 +285,25 @@ describe('VoiceSharedAudioContext', () => {
|
||||
});
|
||||
|
||||
it('createVoiceAudioContext returns null instead of throwing when the rate is rejected', async () => {
|
||||
installFakeWindow({throwOn: (options) => options?.sampleRate !== undefined});
|
||||
const harness = installFakeWindow({throwOn: (options) => options?.sampleRate !== undefined});
|
||||
const {createVoiceAudioContext} = await loadModule();
|
||||
|
||||
expect(createVoiceAudioContext({latencyHint: 'interactive', sampleRate: 48000})).toBeNull();
|
||||
const warning = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
||||
try {
|
||||
expect(createVoiceAudioContext({latencyHint: 'interactive', sampleRate: 48000})).toBeNull();
|
||||
expect(harness.constructionErrors).toHaveLength(1);
|
||||
expect(warning).toHaveBeenCalledTimes(1);
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/^%c\[\d{2}:\d{2}:\d{2}\] \[VoiceSharedAudioContext\] \[Warn\]$/),
|
||||
'color:#ffc107;font-weight:normal',
|
||||
'Failed to construct voice AudioContext',
|
||||
{
|
||||
options: {latencyHint: 'interactive', sampleRate: 48000},
|
||||
error: harness.constructionErrors[0],
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
warning.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('leaves the context untouched when the browser has no WaveShaper', async () => {
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import type {GuildReadyData} from '@app/features/gateway/types/GatewayGuildTypes';
|
||||
import type {VoiceState} from '@app/features/gateway/types/GatewayVoiceTypes';
|
||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||
import {autorun} from 'mobx';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {describe, expect, it, vi} from 'vitest';
|
||||
import {VoiceEngineV2AppVoiceStateAdapter} from './VoiceEngineV2AppVoiceStateAdapter';
|
||||
|
||||
function voiceState(overrides: Partial<VoiceState> = {}): VoiceState {
|
||||
@@ -59,16 +60,27 @@ describe('VoiceEngineV2AppVoiceStateAdapter', () => {
|
||||
});
|
||||
|
||||
it('does not reinsert locally removed stale connections', () => {
|
||||
const adapter = new VoiceEngineV2AppVoiceStateAdapter();
|
||||
adapter.handleGatewayVoiceStateUpdate('guild-1', voiceState({connection_id: 'stale-connection'}));
|
||||
expect(adapter.getVoiceStateByConnectionId('stale-connection')).toBeDefined();
|
||||
const debug = vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => undefined);
|
||||
try {
|
||||
const adapter = new VoiceEngineV2AppVoiceStateAdapter();
|
||||
adapter.handleGatewayVoiceStateUpdate('guild-1', voiceState({connection_id: 'stale-connection'}));
|
||||
expect(adapter.getVoiceStateByConnectionId('stale-connection')).toBeDefined();
|
||||
|
||||
adapter.removeVoiceStateConnection('stale-connection');
|
||||
adapter.handleGatewayVoiceStateUpdate('guild-1', voiceState({connection_id: 'stale-connection'}));
|
||||
adapter.removeVoiceStateConnection('stale-connection');
|
||||
adapter.handleGatewayVoiceStateUpdate('guild-1', voiceState({connection_id: 'stale-connection'}));
|
||||
|
||||
expect(adapter.isConnectionIgnored('stale-connection')).toBe(true);
|
||||
expect(adapter.getVoiceStateByConnectionId('stale-connection')).toBeNull();
|
||||
expect(adapter.getAllVoiceStatesInChannel('guild-1', 'channel-1')['stale-connection']).toBeUndefined();
|
||||
expect(adapter.isConnectionIgnored('stale-connection')).toBe(true);
|
||||
expect(adapter.getVoiceStateByConnectionId('stale-connection')).toBeNull();
|
||||
expect(adapter.getAllVoiceStatesInChannel('guild-1', 'channel-1')['stale-connection']).toBeUndefined();
|
||||
expect(debug).toHaveBeenCalledExactlyOnceWith('Ignored voice state update for locally removed connection', {
|
||||
guildId: 'guild-1',
|
||||
channelId: 'channel-1',
|
||||
userId: 'user-1',
|
||||
connectionId: 'stale-connection',
|
||||
});
|
||||
} finally {
|
||||
debug.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('publishes self-video true-to-false changes through MobX observation', () => {
|
||||
|
||||
@@ -28,7 +28,38 @@ AppStorage.subscribe(
|
||||
{key: 'VoiceSettings'},
|
||||
);
|
||||
|
||||
const {default: VoiceSettings} = await import('./VoiceSettings');
|
||||
async function loadVoiceSettings() {
|
||||
const [{Logger}, persistenceModule] = await Promise.all([
|
||||
import('@app/features/platform/utils/AppLogger'),
|
||||
import('@app/features/platform/utils/MobXPersistence'),
|
||||
]);
|
||||
const debug = vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => undefined);
|
||||
try {
|
||||
const [{default: VoiceSettings}, {default: MediaPermission}] = await Promise.all([
|
||||
import('./VoiceSettings'),
|
||||
import('@app/features/permissions/system/state/MediaPermission'),
|
||||
]);
|
||||
await Promise.all([
|
||||
persistenceModule.awaitHydration('MacPermissions'),
|
||||
persistenceModule.awaitHydration('VoiceSettings'),
|
||||
vi.waitFor(() => expect(MediaPermission.isInitialized()).toBe(true)),
|
||||
]);
|
||||
expect(debug).toHaveBeenCalledTimes(3);
|
||||
expect(debug).toHaveBeenCalledWith('Store MacPermissions hydrated from AppStorage and is now persisting.');
|
||||
expect(debug).toHaveBeenCalledWith('Initial permission state', {
|
||||
microphone: 'granted',
|
||||
camera: 'granted',
|
||||
micDenied: false,
|
||||
cameraDenied: false,
|
||||
});
|
||||
expect(debug).toHaveBeenCalledWith('Store VoiceSettings hydrated from AppStorage and is now persisting.');
|
||||
return VoiceSettings;
|
||||
} finally {
|
||||
debug.mockRestore();
|
||||
}
|
||||
}
|
||||
|
||||
const VoiceSettings = await loadVoiceSettings();
|
||||
|
||||
describe('AV1/HEVC screen-share opt-in', () => {
|
||||
it('rewrites a stored AV1 screen-share preference back to automatic on first launch', () => {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||
import type {HardwareEncodeReport} from '@app/features/voice/utils/GpuEncoderCapabilities';
|
||||
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
import {afterEach, beforeEach, describe, expect, it, type MockInstance, vi} from 'vitest';
|
||||
|
||||
let av1OptIn = false;
|
||||
let hevcOptIn = false;
|
||||
let desktop = true;
|
||||
let gpuReport: HardwareEncodeReport | null = null;
|
||||
let codecCapabilityInfo: MockInstance<(...args: Array<unknown>) => void>;
|
||||
|
||||
vi.mock('@app/features/voice/state/VoiceSettings', () => ({
|
||||
default: {
|
||||
@@ -70,7 +72,22 @@ const ALL_SOFTWARE: HardwareEncodeReport = {
|
||||
};
|
||||
|
||||
describe('screen-share AV1/HEVC opt-in gate', () => {
|
||||
afterEach(() => {
|
||||
try {
|
||||
expect(codecCapabilityInfo.mock.calls.length).toBeGreaterThan(0);
|
||||
for (const call of codecCapabilityInfo.mock.calls) {
|
||||
expect(call).toEqual([
|
||||
'Codec capabilities probed',
|
||||
{capabilities: {vp8: true, vp9: true, h264: true, h265: true, av1: true}},
|
||||
]);
|
||||
}
|
||||
} finally {
|
||||
codecCapabilityInfo.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
codecCapabilityInfo = vi.spyOn(Logger.prototype, 'info').mockImplementation(() => undefined);
|
||||
av1OptIn = false;
|
||||
hevcOptIn = false;
|
||||
desktop = true;
|
||||
|
||||
@@ -3,9 +3,10 @@ name = "fluxer_app_proxy"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.104"
|
||||
axum = { version = "0.8.9", features = ["macros"] }
|
||||
base64 = "0.22"
|
||||
fluxer_common = { path = "../fluxer_common" }
|
||||
|
||||
@@ -45,7 +45,7 @@ pub struct InviteMetaResolver {
|
||||
enum InviteMetaStorage {
|
||||
Postgres(PostgresInviteMetaStorage),
|
||||
#[cfg(feature = "scylla")]
|
||||
Scylla(ScyllaInviteMetaStorage),
|
||||
Scylla(Box<ScyllaInviteMetaStorage>),
|
||||
}
|
||||
|
||||
struct PostgresInviteMetaStorage {
|
||||
@@ -183,13 +183,13 @@ impl InviteMetaResolver {
|
||||
let cache = invite_meta_cache(config);
|
||||
|
||||
Ok(Self {
|
||||
storage: InviteMetaStorage::Scylla(ScyllaInviteMetaStorage {
|
||||
storage: InviteMetaStorage::Scylla(Box::new(ScyllaInviteMetaStorage {
|
||||
db,
|
||||
stmt_invite,
|
||||
stmt_guild,
|
||||
stmt_channel,
|
||||
stmt_user,
|
||||
}),
|
||||
})),
|
||||
cache,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.104"
|
||||
aws-credential-types = "1.2.14"
|
||||
aws-sigv4 = "1.4.5"
|
||||
axum = { version = "0.8.9", default-features = false }
|
||||
|
||||
+2
-2
@@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "autotools"
|
||||
|
||||
@@ -7,61 +7,61 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CRATE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
require_pkg_config() {
|
||||
if ! command -v pkg-config >/dev/null 2>&1; then
|
||||
echo "error: pkg-config is required but was not found in PATH." >&2
|
||||
echo " Install via: brew install pkg-config (macOS)" >&2
|
||||
echo " apt-get install pkg-config (Debian/Ubuntu)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v pkg-config >/dev/null 2>&1; then
|
||||
echo "error: pkg-config is required but was not found in PATH." >&2
|
||||
echo " Install via: brew install pkg-config (macOS)" >&2
|
||||
echo " apt-get install pkg-config (Debian/Ubuntu)" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_webrtc_pkgconfig() {
|
||||
if ! pkg-config --exists webrtc-audio-processing-2; then
|
||||
echo "error: pkg-config cannot locate webrtc-audio-processing-2." >&2
|
||||
echo " See fluxer_desktop/native/audio-apm/README.md for install steps." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! pkg-config --exists webrtc-audio-processing-2; then
|
||||
echo "error: pkg-config cannot locate webrtc-audio-processing-2." >&2
|
||||
echo " See fluxer_desktop/native/audio-apm/README.md for install steps." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_absl_pkgconfig() {
|
||||
if ! pkg-config --exists absl_base; then
|
||||
echo "error: pkg-config cannot locate absl_base." >&2
|
||||
echo " Install via: brew install abseil (macOS)" >&2
|
||||
echo " apt-get install libabsl-dev (Debian/Ubuntu)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! pkg-config --exists absl_base; then
|
||||
echo "error: pkg-config cannot locate absl_base." >&2
|
||||
echo " Install via: brew install abseil (macOS)" >&2
|
||||
echo " apt-get install libabsl-dev (Debian/Ubuntu)" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
inject_absl_include_path() {
|
||||
local absl_includes
|
||||
absl_includes="$(pkg-config --cflags-only-I absl_base)"
|
||||
if [[ -z "${absl_includes}" ]]; then
|
||||
echo "error: pkg-config returned no include path for absl_base." >&2
|
||||
exit 1
|
||||
fi
|
||||
local absl_includes
|
||||
absl_includes="$(pkg-config --cflags-only-I absl_base)"
|
||||
if [[ -z "${absl_includes}" ]]; then
|
||||
echo "error: pkg-config returned no include path for absl_base." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local prev_cxxflags="${CXXFLAGS-}"
|
||||
local prev_bindgen="${BINDGEN_EXTRA_CLANG_ARGS-}"
|
||||
local prev_cxxflags="${CXXFLAGS-}"
|
||||
local prev_bindgen="${BINDGEN_EXTRA_CLANG_ARGS-}"
|
||||
|
||||
export CXXFLAGS="${absl_includes} ${prev_cxxflags}"
|
||||
export BINDGEN_EXTRA_CLANG_ARGS="${absl_includes} ${prev_bindgen}"
|
||||
export CXXFLAGS="${absl_includes} ${prev_cxxflags}"
|
||||
export BINDGEN_EXTRA_CLANG_ARGS="${absl_includes} ${prev_bindgen}"
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "${CRATE_DIR}"
|
||||
cd "${CRATE_DIR}"
|
||||
|
||||
local cargo_subcmd="build"
|
||||
if [[ $# -gt 0 && "$1" != -* && "$1" != "--" ]]; then
|
||||
cargo_subcmd="$1"
|
||||
shift
|
||||
fi
|
||||
local cargo_subcmd="build"
|
||||
if [[ $# -gt 0 && "$1" != -* && "$1" != "--" ]]; then
|
||||
cargo_subcmd="$1"
|
||||
shift
|
||||
fi
|
||||
|
||||
require_pkg_config
|
||||
assert_webrtc_pkgconfig
|
||||
assert_absl_pkgconfig
|
||||
inject_absl_include_path
|
||||
require_pkg_config
|
||||
assert_webrtc_pkgconfig
|
||||
assert_absl_pkgconfig
|
||||
inject_absl_include_path
|
||||
|
||||
exec cargo "${cargo_subcmd}" --features real-apm "$@"
|
||||
exec cargo "${cargo_subcmd}" --features real-apm "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+2
-2
@@ -165,9 +165,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
+2
-2
@@ -165,9 +165,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
[graph]
|
||||
all-features = true
|
||||
no-default-features = false
|
||||
|
||||
[output]
|
||||
feature-depth = 1
|
||||
|
||||
[advisories]
|
||||
version = 2
|
||||
yanked = "deny"
|
||||
unmaintained = "all"
|
||||
ignore = []
|
||||
|
||||
[licenses]
|
||||
version = 2
|
||||
confidence-threshold = 0.93
|
||||
unused-allowed-license = "allow"
|
||||
allow = [
|
||||
"MIT",
|
||||
"MIT-0",
|
||||
"Apache-2.0",
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"ISC",
|
||||
"MPL-2.0",
|
||||
"NCSA",
|
||||
"Unicode-3.0",
|
||||
"Zlib",
|
||||
"CC0-1.0",
|
||||
"AGPL-3.0-or-later",
|
||||
"BSL-1.0",
|
||||
"CDLA-Permissive-2.0",
|
||||
]
|
||||
exceptions = []
|
||||
|
||||
[licenses.private]
|
||||
ignore = true
|
||||
registries = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "ring"
|
||||
expression = "ISC AND MIT AND OpenSSL"
|
||||
license-files = [
|
||||
{ path = "LICENSE", hash = 0xbd0eed23 },
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "webrtc-audio-processing@2.1.0"
|
||||
expression = "BSD-3-Clause"
|
||||
license-files = [
|
||||
{ path = "COPYING", hash = 0x3d3ea952 },
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "webrtc-audio-processing-config@2.1.0"
|
||||
expression = "BSD-3-Clause"
|
||||
license-files = [
|
||||
{ path = "COPYING", hash = 0x7e861f0d },
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "webrtc-audio-processing-sys@2.1.0"
|
||||
expression = "BSD-3-Clause"
|
||||
license-files = [
|
||||
{ path = "COPYING", hash = 0x7e861f0d },
|
||||
]
|
||||
|
||||
[bans]
|
||||
multiple-versions = "warn"
|
||||
wildcards = "deny"
|
||||
allow-wildcard-paths = true
|
||||
highlight = "all"
|
||||
workspace-default-features = "allow"
|
||||
external-default-features = "allow"
|
||||
deny = [
|
||||
{ crate = "fuse", reason = "libfuse2-based Rust wrapper; use a maintained FUSE3-native crate only if Fluxer ever needs FUSE directly" },
|
||||
{ crate = "fuse-sys", reason = "libfuse2 FFI crate; Fluxer AppImages must not reintroduce libfuse2 through native Rust dependencies" },
|
||||
]
|
||||
skip = [
|
||||
{ crate = "bitflags@1.3.2", reason = "hunspell bindgen requires the legacy bitflags API" },
|
||||
{ crate = "foldhash@0.1.5", reason = "transitive dependencies require the prior hash table API" },
|
||||
{ crate = "hashbrown@0.15.5", reason = "transitive dependencies require the older hash table API" },
|
||||
{ crate = "hashbrown@0.16.1", reason = "transitive dependencies require the prior hash table API" },
|
||||
{ crate = "hermit-abi@0.3.9", reason = "udev requires the prior Hermit ABI" },
|
||||
{ crate = "itertools@0.13.0", reason = "bindgen and criterion require the prior iterator API" },
|
||||
{ crate = "libloading@0.8.9", reason = "bindgen requires the prior dynamic loading API" },
|
||||
{ crate = "nix@0.29.0", reason = "evdev requires the prior Unix API" },
|
||||
{ crate = "nom@7.1.3", reason = "bindgen requires the prior parser API" },
|
||||
{ crate = "rustc-hash@1.1.0", reason = "hunspell bindgen requires the legacy hash API" },
|
||||
{ crate = "shlex@1.3.0", reason = "bindgen requires the prior shell parsing API" },
|
||||
{ crate = "syn@1.0.109", reason = "hunspell bindgen requires the legacy macro API" },
|
||||
{ crate = "syn@2.0.119", reason = "napi derive requires the prior macro API" },
|
||||
{ crate = "windows-sys@0.48.0", reason = "udev requires the legacy Windows API" },
|
||||
{ crate = "windows-sys@0.59.0", reason = "hunspell build tooling requires the prior Windows API" },
|
||||
{ crate = "wit-bindgen@0.51.0", reason = "the WASI preview 3 compatibility layer requires the prior binding API" },
|
||||
]
|
||||
skip-tree = []
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
allow-git = []
|
||||
|
||||
[sources.allow-org]
|
||||
github = []
|
||||
+2
-2
@@ -211,9 +211,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
@@ -52,12 +52,11 @@ impl EncoderFrameRate {
|
||||
let denominator = u64::from(self.denominator);
|
||||
assert!(numerator > 0, "fps numerator positive");
|
||||
assert!(denominator > 0, "fps denominator positive");
|
||||
((1_000_000u64 * denominator) + numerator - 1) / numerator
|
||||
(1_000_000u64 * denominator).div_ceil(numerator)
|
||||
}
|
||||
|
||||
pub fn gop_pic_size(self) -> u16 {
|
||||
let rounded = (u64::from(self.numerator) + u64::from(self.denominator) - 1)
|
||||
/ u64::from(self.denominator);
|
||||
let rounded = u64::from(self.numerator).div_ceil(u64::from(self.denominator));
|
||||
let bounded = rounded.clamp(1, u64::from(ENCODER_FRAME_RATE_MAX));
|
||||
bounded as u16
|
||||
}
|
||||
|
||||
+2
-2
@@ -245,9 +245,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
@@ -228,12 +228,11 @@ fn retain_screen_audio_sink_handle(
|
||||
"DirectAudioCapture.setScreenAudioSink received an empty native external sink handle",
|
||||
));
|
||||
}
|
||||
let handle = unsafe {
|
||||
NativeScreenFrameSinkHandle::retain_from_raw(data.cast::<NativeScreenFrameSinkHandle>())
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
generic_error("DirectAudioCapture.setScreenAudioSink received an invalid handle")
|
||||
})?;
|
||||
let handle = unsafe { data.cast::<NativeScreenFrameSinkHandle>().as_ref() }
|
||||
.and_then(NativeScreenFrameSinkHandle::retain_ref)
|
||||
.ok_or_else(|| {
|
||||
generic_error("DirectAudioCapture.setScreenAudioSink received an invalid handle")
|
||||
})?;
|
||||
Ok(Arc::new(handle))
|
||||
}
|
||||
|
||||
|
||||
@@ -740,8 +740,8 @@ mod tests {
|
||||
.expect("apm");
|
||||
let frame_len = DIRECT_CAPTURE_APM_FRAME_SAMPLES;
|
||||
let mut samples = vec![0.0_f32; frame_len];
|
||||
for n in 0..frame_len {
|
||||
samples[n] = ((n as f32) / (frame_len as f32) - 0.5) * 0.5;
|
||||
for (n, sample) in samples.iter_mut().enumerate() {
|
||||
*sample = ((n as f32) / (frame_len as f32) - 0.5) * 0.5;
|
||||
}
|
||||
let original = samples.clone();
|
||||
let _ = apm.process_in_place(&mut samples).expect("process");
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
|
||||
+4
-4
@@ -44,9 +44,9 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
@@ -1479,9 +1479,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
@@ -952,12 +952,13 @@ fn retain_native_frame_sink_handle(
|
||||
));
|
||||
}
|
||||
|
||||
let handle = unsafe {
|
||||
NativeScreenFrameSinkHandle::retain_from_raw(data.cast::<NativeScreenFrameSinkHandle>())
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
invalid_arg("ScreenCapture.setFrameSinkHandle received an invalid native frame sink handle")
|
||||
})?;
|
||||
let handle = unsafe { data.cast::<NativeScreenFrameSinkHandle>().as_ref() }
|
||||
.and_then(NativeScreenFrameSinkHandle::retain_ref)
|
||||
.ok_or_else(|| {
|
||||
invalid_arg(
|
||||
"ScreenCapture.setFrameSinkHandle received an invalid native frame sink handle",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Arc::new(handle))
|
||||
}
|
||||
@@ -967,7 +968,7 @@ fn enqueue_native_bus_video_frame(
|
||||
frame: &VideoFrame,
|
||||
) -> EnqueueOutcome {
|
||||
if let Some(dmabuf) = frame.dmabuf.as_ref() {
|
||||
if sink.handle().enqueue_dmabuf.is_none() {
|
||||
if !sink.supports_dmabuf() {
|
||||
return EnqueueOutcome::Rejected;
|
||||
}
|
||||
let plane_count = dmabuf.plane_count as usize;
|
||||
@@ -975,18 +976,13 @@ fn enqueue_native_bus_video_frame(
|
||||
return EnqueueOutcome::Rejected;
|
||||
}
|
||||
|
||||
let mut duped_fds: Vec<i32> = Vec::with_capacity(plane_count);
|
||||
let mut duped_fds: Vec<OwnedFd> = Vec::with_capacity(plane_count);
|
||||
for raw in dmabuf.fds.iter().take(plane_count) {
|
||||
let duped = unsafe { libc::dup(*raw) };
|
||||
if duped < 0 {
|
||||
for fd in duped_fds {
|
||||
unsafe {
|
||||
libc::close(fd);
|
||||
}
|
||||
}
|
||||
return EnqueueOutcome::Rejected;
|
||||
}
|
||||
duped_fds.push(duped);
|
||||
duped_fds.push(unsafe { OwnedFd::from_raw_fd(duped) });
|
||||
}
|
||||
|
||||
let desc = BusDmabufDesc {
|
||||
@@ -1001,7 +997,7 @@ fn enqueue_native_bus_video_frame(
|
||||
timestamp_us: frame.timestamp_us,
|
||||
};
|
||||
|
||||
return unsafe { sink.enqueue_dmabuf_take_fds(desc, &duped_fds) };
|
||||
return sink.enqueue_dmabuf_take_fds(desc, duped_fds);
|
||||
}
|
||||
|
||||
sink.enqueue_nv12_copy(
|
||||
|
||||
@@ -138,15 +138,14 @@ fn retain_screen_audio_sink_handle(
|
||||
"ProcessLoopback.setScreenAudioSink received an empty native external sink handle",
|
||||
));
|
||||
}
|
||||
let handle = unsafe {
|
||||
NativeScreenFrameSinkHandle::retain_from_raw(data.cast::<NativeScreenFrameSinkHandle>())
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
napi::Error::new(
|
||||
Status::InvalidArg,
|
||||
"ProcessLoopback.setScreenAudioSink received an invalid native sink handle",
|
||||
)
|
||||
})?;
|
||||
let handle = unsafe { data.cast::<NativeScreenFrameSinkHandle>().as_ref() }
|
||||
.and_then(NativeScreenFrameSinkHandle::retain_ref)
|
||||
.ok_or_else(|| {
|
||||
napi::Error::new(
|
||||
Status::InvalidArg,
|
||||
"ProcessLoopback.setScreenAudioSink received an invalid native sink handle",
|
||||
)
|
||||
})?;
|
||||
Ok(Arc::new(handle))
|
||||
}
|
||||
|
||||
|
||||
@@ -960,15 +960,14 @@ fn retain_native_frame_sink_handle(
|
||||
));
|
||||
}
|
||||
|
||||
let handle = unsafe {
|
||||
NativeScreenFrameSinkHandle::retain_from_raw(data.cast::<NativeScreenFrameSinkHandle>())
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
napi::Error::new(
|
||||
Status::InvalidArg,
|
||||
"ScreenCapture.setFrameSinkHandle received an invalid native frame sink handle",
|
||||
)
|
||||
})?;
|
||||
let handle = unsafe { data.cast::<NativeScreenFrameSinkHandle>().as_ref() }
|
||||
.and_then(NativeScreenFrameSinkHandle::retain_ref)
|
||||
.ok_or_else(|| {
|
||||
napi::Error::new(
|
||||
Status::InvalidArg,
|
||||
"ScreenCapture.setFrameSinkHandle received an invalid native frame sink handle",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Arc::new(handle))
|
||||
}
|
||||
@@ -980,7 +979,7 @@ fn try_enqueue_native_cv_pixel_buffer(
|
||||
height: u32,
|
||||
sample_buffer: &CMSampleBuffer,
|
||||
) -> EnqueueOutcome {
|
||||
if sink.handle().enqueue_mac_cv_pixel_buffer.is_none() {
|
||||
if !sink.supports_mac_cv_pixel_buffer() {
|
||||
return EnqueueOutcome::Rejected;
|
||||
}
|
||||
let retained =
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
|
||||
+2
-2
@@ -165,9 +165,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
Generated
+4
-4
@@ -34,9 +34,9 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
@@ -216,9 +216,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
@@ -30,12 +30,6 @@ pub struct NSErrorBlock {
|
||||
pub user_invoke: NsErrorUserInvoke,
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn ns_error_trampoline(block: *mut NSErrorBlock, err: *mut c_void) {
|
||||
if let Some(block) = unsafe { block.as_mut() } {
|
||||
unsafe { (block.user_invoke)(block.ctx, err) };
|
||||
}
|
||||
}
|
||||
|
||||
pub type ContentErrorUserInvoke =
|
||||
unsafe extern "C" fn(ctx: *mut c_void, content: *mut c_void, err: *mut c_void);
|
||||
pub type ContentErrorInvoke =
|
||||
@@ -52,16 +46,6 @@ pub struct ContentErrorBlock {
|
||||
pub user_invoke: ContentErrorUserInvoke,
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn content_error_trampoline(
|
||||
block: *mut ContentErrorBlock,
|
||||
content: *mut c_void,
|
||||
err: *mut c_void,
|
||||
) {
|
||||
if let Some(block) = unsafe { block.as_mut() } {
|
||||
unsafe { (block.user_invoke)(block.ctx, content, err) };
|
||||
}
|
||||
}
|
||||
|
||||
pub const NS_ERROR_BLOCK_DESCRIPTOR: BlockDescriptorSig = BlockDescriptorSig {
|
||||
reserved: 0,
|
||||
size: std::mem::size_of::<NSErrorBlock>() as u64,
|
||||
@@ -78,6 +62,22 @@ pub const CONTENT_ERROR_BLOCK_DESCRIPTOR: BlockDescriptorSig = BlockDescriptorSi
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
unsafe extern "C" fn ns_error_trampoline(block: *mut NSErrorBlock, err: *mut c_void) {
|
||||
if let Some(block) = unsafe { block.as_mut() } {
|
||||
unsafe { (block.user_invoke)(block.ctx, err) };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn content_error_trampoline(
|
||||
block: *mut ContentErrorBlock,
|
||||
content: *mut c_void,
|
||||
err: *mut c_void,
|
||||
) {
|
||||
if let Some(block) = unsafe { block.as_mut() } {
|
||||
unsafe { (block.user_invoke)(block.ctx, content, err) };
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct RoundTripCtx {
|
||||
seen_err: *mut c_void,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use fluxer_screen_frame_bus::{CpuStagingBackend, StagingSurfacePair};
|
||||
|
||||
const BENCH_WIDTH: usize = 1920;
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::sync::OnceLock;
|
||||
use crate::frame_pool::PooledFrame;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::os::fd::{IntoRawFd, OwnedFd};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -325,30 +325,30 @@ pub type NativeScreenFrameSinkEnqueueScreenAudioFn = unsafe extern "C" fn(
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NativeScreenFrameSinkHandle {
|
||||
pub magic: u32,
|
||||
pub version: u32,
|
||||
pub context: *const c_void,
|
||||
pub retain: NativeScreenFrameSinkRetainFn,
|
||||
pub release: NativeScreenFrameSinkReleaseFn,
|
||||
pub enqueue_nv12: Option<NativeScreenFrameSinkEnqueueNv12Fn>,
|
||||
pub enqueue_bgra: Option<NativeScreenFrameSinkEnqueueBgraFn>,
|
||||
pub enqueue_mac_cv_pixel_buffer: Option<NativeScreenFrameSinkEnqueueMacCvPixelBufferFn>,
|
||||
pub enqueue_dmabuf: Option<NativeScreenFrameSinkEnqueueDmabufFn>,
|
||||
pub enqueue_shared_texture: Option<NativeScreenFrameSinkEnqueueSharedTextureFn>,
|
||||
pub enqueue_screen_audio: Option<NativeScreenFrameSinkEnqueueScreenAudioFn>,
|
||||
magic: u32,
|
||||
version: u32,
|
||||
context: *const c_void,
|
||||
retain: NativeScreenFrameSinkRetainFn,
|
||||
release: NativeScreenFrameSinkReleaseFn,
|
||||
enqueue_nv12: Option<NativeScreenFrameSinkEnqueueNv12Fn>,
|
||||
enqueue_bgra: Option<NativeScreenFrameSinkEnqueueBgraFn>,
|
||||
enqueue_mac_cv_pixel_buffer: Option<NativeScreenFrameSinkEnqueueMacCvPixelBufferFn>,
|
||||
enqueue_dmabuf: Option<NativeScreenFrameSinkEnqueueDmabufFn>,
|
||||
enqueue_shared_texture: Option<NativeScreenFrameSinkEnqueueSharedTextureFn>,
|
||||
enqueue_screen_audio: Option<NativeScreenFrameSinkEnqueueScreenAudioFn>,
|
||||
}
|
||||
|
||||
unsafe impl Send for NativeScreenFrameSinkHandle {}
|
||||
unsafe impl Sync for NativeScreenFrameSinkHandle {}
|
||||
|
||||
impl NativeScreenFrameSinkHandle {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
fn is_valid(&self) -> bool {
|
||||
self.magic == NATIVE_SCREEN_FRAME_SINK_HANDLE_MAGIC
|
||||
&& self.version == NATIVE_SCREEN_FRAME_SINK_HANDLE_VERSION
|
||||
&& !self.context.is_null()
|
||||
}
|
||||
|
||||
pub unsafe fn retain_ref(&self) -> Option<NativeScreenFrameSinkHandleRef> {
|
||||
pub fn retain_ref(&self) -> Option<NativeScreenFrameSinkHandleRef> {
|
||||
if !self.is_valid() {
|
||||
return None;
|
||||
}
|
||||
@@ -356,13 +356,6 @@ impl NativeScreenFrameSinkHandle {
|
||||
Some(NativeScreenFrameSinkHandleRef { handle: *self })
|
||||
}
|
||||
|
||||
pub unsafe fn retain_from_raw(raw: *const Self) -> Option<NativeScreenFrameSinkHandleRef> {
|
||||
if raw.is_null() {
|
||||
return None;
|
||||
}
|
||||
unsafe { (*raw).retain_ref() }
|
||||
}
|
||||
|
||||
pub fn native_outcome(outcome: EnqueueOutcome) -> u32 {
|
||||
match outcome {
|
||||
EnqueueOutcome::Accepted => NATIVE_SCREEN_FRAME_SINK_ACCEPTED,
|
||||
@@ -388,10 +381,6 @@ unsafe impl Send for NativeScreenFrameSinkHandleRef {}
|
||||
unsafe impl Sync for NativeScreenFrameSinkHandleRef {}
|
||||
|
||||
impl NativeScreenFrameSinkHandleRef {
|
||||
pub fn handle(&self) -> &NativeScreenFrameSinkHandle {
|
||||
&self.handle
|
||||
}
|
||||
|
||||
pub fn enqueue_nv12_copy(
|
||||
&self,
|
||||
data: &[u8],
|
||||
@@ -442,6 +431,11 @@ impl NativeScreenFrameSinkHandleRef {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn supports_mac_cv_pixel_buffer(&self) -> bool {
|
||||
self.handle.enqueue_mac_cv_pixel_buffer.is_some()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub unsafe fn enqueue_mac_cv_pixel_buffer(
|
||||
&self,
|
||||
@@ -467,12 +461,25 @@ impl NativeScreenFrameSinkHandleRef {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub unsafe fn enqueue_dmabuf_take_fds(&self, desc: DmabufDesc, fds: &[i32]) -> EnqueueOutcome {
|
||||
pub fn supports_dmabuf(&self) -> bool {
|
||||
self.handle.enqueue_dmabuf.is_some()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn enqueue_dmabuf_take_fds(&self, desc: DmabufDesc, fds: Vec<OwnedFd>) -> EnqueueOutcome {
|
||||
let Some(enqueue) = self.handle.enqueue_dmabuf else {
|
||||
return EnqueueOutcome::Rejected;
|
||||
};
|
||||
let fd_count = fds.len();
|
||||
if fd_count == 0 || fd_count > 4 || usize::from(desc.plane_count) != fd_count {
|
||||
return EnqueueOutcome::Rejected;
|
||||
}
|
||||
let mut raw_fds = [-1; 4];
|
||||
for (raw_fd, fd) in raw_fds.iter_mut().zip(fds) {
|
||||
*raw_fd = fd.into_raw_fd();
|
||||
}
|
||||
NativeScreenFrameSinkHandle::outcome_from_native(unsafe {
|
||||
enqueue(self.handle.context, desc, fds.as_ptr(), fds.len())
|
||||
enqueue(self.handle.context, desc, raw_fds.as_ptr(), fd_count)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
|
||||
+2
-2
@@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
|
||||
@@ -792,10 +792,10 @@ fn retain_native_frame_sink_handle(
|
||||
}
|
||||
|
||||
let handle = unsafe {
|
||||
fluxer_screen_frame_bus::NativeScreenFrameSinkHandle::retain_from_raw(
|
||||
data.cast::<fluxer_screen_frame_bus::NativeScreenFrameSinkHandle>(),
|
||||
)
|
||||
data.cast::<fluxer_screen_frame_bus::NativeScreenFrameSinkHandle>()
|
||||
.as_ref()
|
||||
}
|
||||
.and_then(fluxer_screen_frame_bus::NativeScreenFrameSinkHandle::retain_ref)
|
||||
.ok_or_else(|| {
|
||||
napi::Error::new(
|
||||
Status::InvalidArg,
|
||||
|
||||
@@ -37,15 +37,14 @@ fn retain_screen_audio_sink_handle(
|
||||
"ProcessLoopback.setScreenAudioSink received an empty native external sink handle",
|
||||
));
|
||||
}
|
||||
let handle = unsafe {
|
||||
NativeScreenFrameSinkHandle::retain_from_raw(data.cast::<NativeScreenFrameSinkHandle>())
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
Status::InvalidArg,
|
||||
"ProcessLoopback.setScreenAudioSink received an invalid native sink handle",
|
||||
)
|
||||
})?;
|
||||
let handle = unsafe { data.cast::<NativeScreenFrameSinkHandle>().as_ref() }
|
||||
.and_then(NativeScreenFrameSinkHandle::retain_ref)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
Status::InvalidArg,
|
||||
"ProcessLoopback.setScreenAudioSink received an invalid native sink handle",
|
||||
)
|
||||
})?;
|
||||
Ok(Arc::new(handle))
|
||||
}
|
||||
|
||||
@@ -341,7 +340,7 @@ impl Callbacks {
|
||||
FrameSamples::Owned(owned) => owned.as_slice(),
|
||||
};
|
||||
let channels = u32::from(audio_contract::TARGET_CHANNELS);
|
||||
if channels == 0 || samples.is_empty() || samples.len() % (channels as usize) != 0 {
|
||||
if channels == 0 || samples.is_empty() || !samples.len().is_multiple_of(channels as usize) {
|
||||
return true;
|
||||
}
|
||||
let frames = samples.len() as u32 / channels;
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
"@types/node": "24.10.9",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260130.1",
|
||||
"electron": "42.10.0",
|
||||
"electron-builder": "26.9.0",
|
||||
"electron-builder": "26.15.3",
|
||||
"electron-builder-squirrel-windows": "26.15.3",
|
||||
"esbuild": "0.27.2",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
|
||||
@@ -109,7 +109,7 @@ impl Node {
|
||||
}
|
||||
|
||||
impl MemberOSet {
|
||||
fn from_sorted(&mut self, mut keys: Vec<MemberKey>) {
|
||||
fn replace_from_keys(&mut self, mut keys: Vec<MemberKey>) {
|
||||
keys.sort_unstable();
|
||||
keys.dedup();
|
||||
self.root = build_balanced(&keys);
|
||||
@@ -192,7 +192,7 @@ fn from_sorted<'a>(
|
||||
return Err(Error::BadArg);
|
||||
}
|
||||
let mut set = resource.inner.lock().map_err(|_| Error::BadArg)?;
|
||||
set.from_sorted(keys);
|
||||
set.replace_from_keys(keys);
|
||||
Ok(atoms::ok().encode(env))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
{erl_opts, [debug_info, nowarn_deprecated_function]}.
|
||||
{deps, [
|
||||
{cowboy, "2.14.2"},
|
||||
{cowboy, "2.18.0"},
|
||||
{base64url, "1.0.1"},
|
||||
{jose, "1.11.10"},
|
||||
{ezstd, "1.1.0"},
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
{project_plugins, [
|
||||
{erlfmt, {git, "https://github.com/WhatsApp/erlfmt.git", {tag, "v1.8.0"}}},
|
||||
{rebar3_lint, {git, "https://github.com/project-fifo/rebar3_lint.git", {branch, "master"}}}
|
||||
{rebar3_lint, {git, "https://github.com/project-fifo/rebar3_lint.git", {branch, "master"}}},
|
||||
{rebar3_audit, "0.2.7"}
|
||||
]}.
|
||||
|
||||
{pre_hooks, [{compile, "../tools/ci/run.sh build-gateway-nifs --gateway-dir ."}]}.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{"1.2.0",
|
||||
[{<<"base64url">>,{pkg,<<"base64url">>,<<"1.0.1">>},0},
|
||||
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.14.2">>},0},
|
||||
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.16.0">>},1},
|
||||
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.18.0">>},0},
|
||||
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.19.0">>},1},
|
||||
{<<"enats">>,{pkg,<<"enats">>,<<"1.2.0">>},0},
|
||||
{<<"enats_msg">>,{pkg,<<"enats_msg">>,<<"1.0.3">>},1},
|
||||
{<<"eqwalizer_support">>,
|
||||
@@ -12,26 +12,26 @@
|
||||
{<<"ezstd">>,{pkg,<<"ezstd">>,<<"1.1.0">>},0},
|
||||
{<<"jose">>,{pkg,<<"jose">>,<<"1.11.10">>},0},
|
||||
{<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.4.0">>},1},
|
||||
{<<"ranch">>,{pkg,<<"ranch">>,<<"2.2.0">>},1}]}.
|
||||
{<<"ranch">>,{pkg,<<"ranch">>,<<"2.2.1">>},1}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>},
|
||||
{<<"cowboy">>, <<"4008BE1DF6ADE45E4F2A4E9E2D22B36D0B5ABA4E20B0A0D7049E28D124E34847">>},
|
||||
{<<"cowlib">>, <<"54592074EBBBB92EE4746C8A8846E5605052F29309D3A873468D76CDF932076F">>},
|
||||
{<<"cowboy">>, <<"BFF388EB4D6356CB3F88C26E65B515976BF04B401B805D31550C3E60EBA8FE18">>},
|
||||
{<<"cowlib">>, <<"C9D11C9D035472E27A740C9F327786C61ED209269B4BE0260D59D3EC07B8949F">>},
|
||||
{<<"enats">>, <<"D7459C804013CAFA4AF880B18D446C48890D28D372D62AD66C76187E5779248D">>},
|
||||
{<<"enats_msg">>, <<"50631124F37D88BE76A91A5B96A6565C5981EBF917CD819F0175CA658A966F43">>},
|
||||
{<<"ezstd">>, <<"D3B483D6ACFADFB65DBA4015371E6D54526DBF3D9EF0941B5ADD8BF5890731F4">>},
|
||||
{<<"jose">>, <<"A903F5227417BD2A08C8A00A0CBCC458118BE84480955E8D251297A425723F83">>},
|
||||
{<<"opentelemetry_api">>, <<"63CA1742F92F00059298F478048DFB826F4B20D49534493D6919A0DB39B6DB04">>},
|
||||
{<<"ranch">>, <<"25528F82BC8D7C6152C57666CA99EC716510FE0925CB188172F41CE93117B1B0">>}]},
|
||||
{<<"ranch">>, <<"FC2BB0E800EFBEAB8FF82EB325450B9C08653B14C3F69E8B09BBEA304973105D">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>},
|
||||
{<<"cowboy">>, <<"569081DA046E7B41B5DF36AA359BE71A0C8874E5B9CFF6F747073FC57BAF1AB9">>},
|
||||
{<<"cowlib">>, <<"7F478D80D66B747344F0EA7708C187645CFCC08B11AA424632F78E25BF05DB51">>},
|
||||
{<<"cowboy">>, <<"62D0B26ABCF455054972B0DA242389C69D5982CE5914AFB8C344517F667B9600">>},
|
||||
{<<"cowlib">>, <<"6DC66E3135B229193EA4DCB14294E79520C923D391315C9C962EF0B4BEA72356">>},
|
||||
{<<"enats">>, <<"20DEB3CB1D3E960194DF8B136C40D2DB085B485BBA5E493B340AB5F9FD2BED22">>},
|
||||
{<<"enats_msg">>, <<"C4F2139E5144FABC99AFE01B8B016AD9DA278CDDC60857AC5D5AFB0AD1283534">>},
|
||||
{<<"ezstd">>, <<"28CFA0ED6CC3922095AD5BA0F23392A1664273358B17184BAA909868361184E7">>},
|
||||
{<<"jose">>, <<"0D6CD36FF8BA174DB29148FC112B5842186B68A90CE9FC2B3EC3AFE76593E614">>},
|
||||
{<<"opentelemetry_api">>, <<"3DFBBFAA2C2ED3121C5C483162836C4F9027DEF469C41578AF5EF32589FCFC58">>},
|
||||
{<<"ranch">>, <<"FA0B99A1780C80218A4197A59EA8D3BDAE32FBFF7E88527D7D8A4787EFF4F8E7">>}]}
|
||||
{<<"ranch">>, <<"55F05CCE20EC2DA1D90DE5D5981AFB93DBFC01325FC7E933189AA5C62F037C24">>}]}
|
||||
].
|
||||
|
||||
@@ -5,36 +5,36 @@
|
||||
set -eu
|
||||
|
||||
is_positive_int() {
|
||||
case "${1:-}" in
|
||||
'' | 0* | *[!0-9]*) return 1 ;;
|
||||
*) [ "$1" -gt 0 ] ;;
|
||||
esac
|
||||
case "${1:-}" in
|
||||
'' | 0* | *[!0-9]*) return 1 ;;
|
||||
*) [ "$1" -gt 0 ] ;;
|
||||
esac
|
||||
}
|
||||
|
||||
available_cpu_count() {
|
||||
cpu_quota=""
|
||||
cpu_period=""
|
||||
if [ -r /sys/fs/cgroup/cpu.max ]; then
|
||||
read -r cpu_quota cpu_period < /sys/fs/cgroup/cpu.max || true
|
||||
else
|
||||
for cgroup_dir in /sys/fs/cgroup/cpu /sys/fs/cgroup/cpu,cpuacct; do
|
||||
if [ -r "$cgroup_dir/cpu.cfs_quota_us" ] && [ -r "$cgroup_dir/cpu.cfs_period_us" ]; then
|
||||
read -r cpu_quota < "$cgroup_dir/cpu.cfs_quota_us" || true
|
||||
read -r cpu_period < "$cgroup_dir/cpu.cfs_period_us" || true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if is_positive_int "$cpu_quota" && is_positive_int "$cpu_period"; then
|
||||
echo "$(((cpu_quota + cpu_period - 1) / cpu_period))"
|
||||
return 0
|
||||
fi
|
||||
cpu_count="$(nproc 2>/dev/null || echo 1)"
|
||||
if is_positive_int "$cpu_count"; then
|
||||
echo "$cpu_count"
|
||||
else
|
||||
echo 1
|
||||
fi
|
||||
cpu_quota=""
|
||||
cpu_period=""
|
||||
if [ -r /sys/fs/cgroup/cpu.max ]; then
|
||||
read -r cpu_quota cpu_period </sys/fs/cgroup/cpu.max || true
|
||||
else
|
||||
for cgroup_dir in /sys/fs/cgroup/cpu /sys/fs/cgroup/cpu,cpuacct; do
|
||||
if [ -r "$cgroup_dir/cpu.cfs_quota_us" ] && [ -r "$cgroup_dir/cpu.cfs_period_us" ]; then
|
||||
read -r cpu_quota <"$cgroup_dir/cpu.cfs_quota_us" || true
|
||||
read -r cpu_period <"$cgroup_dir/cpu.cfs_period_us" || true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if is_positive_int "$cpu_quota" && is_positive_int "$cpu_period"; then
|
||||
echo "$(((cpu_quota + cpu_period - 1) / cpu_period))"
|
||||
return 0
|
||||
fi
|
||||
cpu_count="$(nproc 2>/dev/null || echo 1)"
|
||||
if is_positive_int "$cpu_count"; then
|
||||
echo "$cpu_count"
|
||||
else
|
||||
echo 1
|
||||
fi
|
||||
}
|
||||
|
||||
: "${FLUXER_ERLANG_NODE_NAME:=fluxer_gateway@127.0.0.1}"
|
||||
@@ -42,11 +42,11 @@ available_cpu_count() {
|
||||
: "${FLUXER_ERLANG_DIST_PORT:=8081}"
|
||||
|
||||
if ! is_positive_int "${FLUXER_ERLANG_SCHEDULERS:-}"; then
|
||||
FLUXER_ERLANG_SCHEDULERS="$(available_cpu_count)"
|
||||
FLUXER_ERLANG_SCHEDULERS="$(available_cpu_count)"
|
||||
fi
|
||||
|
||||
if ! is_positive_int "${FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS:-}"; then
|
||||
FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS="$(((FLUXER_ERLANG_SCHEDULERS * 2 + 2) / 3))"
|
||||
FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS="$(((FLUXER_ERLANG_SCHEDULERS * 2 + 2) / 3))"
|
||||
fi
|
||||
|
||||
export FLUXER_ERLANG_NODE_NAME
|
||||
|
||||
@@ -6,11 +6,11 @@ set -eu
|
||||
|
||||
command="${1:-}"
|
||||
case "$command" in
|
||||
eval|console|remote_console|remote|remsh|console_clean|console_boot|daemon|daemon_boot|daemon_attach|escript)
|
||||
echo "fluxer_gateway: '$command' is disabled in production; use bounded rpc/runtime tools instead." >&2
|
||||
exit 64
|
||||
;;
|
||||
eval | console | remote_console | remote | remsh | console_clean | console_boot | daemon | daemon_boot | daemon_attach | escript)
|
||||
echo "fluxer_gateway: '$command' is disabled in production; use bounded rpc/runtime tools instead." >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
|
||||
script_dir="$(CDPATH= cd "$(dirname "$0")" && pwd -P)"
|
||||
script_dir="$(CDPATH='' cd "$(dirname "$0")" && pwd -P)"
|
||||
exec "$script_dir/fluxer_gateway.real" "$@"
|
||||
|
||||
@@ -130,7 +130,7 @@ strip_members_test() ->
|
||||
<<"channels">> => [#{<<"id">> => <<"10">>}],
|
||||
<<"roles">> => [#{<<"id">> => <<"role1">>}]
|
||||
},
|
||||
Stripped = strip_members(Data),
|
||||
Stripped = #{} = strip_members(Data),
|
||||
?assertEqual(false, maps:is_key(<<"members">>, Stripped)),
|
||||
?assertEqual(false, maps:is_key(<<"member_role_index">>, Stripped)),
|
||||
?assertEqual([#{<<"id">> => <<"10">>}], maps:get(<<"channels">>, Stripped)),
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
|
||||
-define(SESSION_CONNECT_MAX_WORKERS, 8).
|
||||
-define(SESSION_CONNECT_DEFAULT_MAX_QUEUE, 1024).
|
||||
-define(CONNECT_SNAPSHOT_HEAVY_MEMBER_KEYS, [
|
||||
<<"members">>, members_normalized, <<"member_role_index">>, members_sorted_ids
|
||||
]).
|
||||
|
||||
-spec ensure_session_connect_queue(term()) -> queue:queue().
|
||||
ensure_session_connect_queue(Value) when is_list(Value) ->
|
||||
@@ -366,95 +363,11 @@ queued_session_pid(_) ->
|
||||
-spec start_worker(map(), map()) -> map().
|
||||
start_worker(Item, State) ->
|
||||
Self = self(),
|
||||
Snapshot = build_connect_snapshot(Item, State),
|
||||
Snapshot = guild_data:build_connect_snapshot(Item, State),
|
||||
{_Pid, Ref} = spawn_monitor(fun() -> compute_and_send_done(Item, Self, Snapshot) end),
|
||||
WorkerRefs = maps:get(session_connect_worker_refs, State, #{}),
|
||||
State#{session_connect_worker_refs => WorkerRefs#{Ref => true}}.
|
||||
|
||||
-spec build_connect_snapshot(map(), map()) -> map().
|
||||
build_connect_snapshot(Item, State) ->
|
||||
Base = maps:with(
|
||||
[
|
||||
id,
|
||||
data,
|
||||
sessions,
|
||||
member_count,
|
||||
voice_server_pid,
|
||||
voice_states,
|
||||
member_list_engine,
|
||||
virtual_channel_access
|
||||
],
|
||||
State
|
||||
),
|
||||
maybe_trim_connect_snapshot(Item, Base, State).
|
||||
|
||||
-spec maybe_trim_connect_snapshot(map(), map(), map()) -> map().
|
||||
maybe_trim_connect_snapshot(Item, Base, State) ->
|
||||
case has_members_ets(State) of
|
||||
true -> trim_connect_snapshot(Item, Base);
|
||||
false -> Base
|
||||
end.
|
||||
|
||||
-spec has_members_ets(map()) -> boolean().
|
||||
has_members_ets(#{data := #{members_ets := Tab}}) -> is_reference(Tab);
|
||||
has_members_ets(_) -> false.
|
||||
|
||||
-spec trim_connect_snapshot(map(), map()) -> map().
|
||||
trim_connect_snapshot(Item, #{data := Data} = Base) when is_map(Data) ->
|
||||
Retained = retained_member_map(Item, Base, Data),
|
||||
Trimmed = maps:without(?CONNECT_SNAPSHOT_HEAVY_MEMBER_KEYS, Data),
|
||||
Base#{
|
||||
data => Trimmed#{
|
||||
<<"members">> => Retained,
|
||||
members_normalized => Retained,
|
||||
<<"member_role_index">> =>
|
||||
guild_data_index_members:build_member_role_index(Retained)
|
||||
}
|
||||
};
|
||||
trim_connect_snapshot(_Item, Base) ->
|
||||
Base.
|
||||
|
||||
-spec retained_member_map(map(), map(), map()) -> #{integer() => map()}.
|
||||
retained_member_map(Item, Base, Data) ->
|
||||
UserIds = [connect_user_id(Item) | voice_state_user_ids(Base)],
|
||||
lists:foldl(
|
||||
fun(UserId, Acc) -> retain_member(UserId, Data, Acc) end,
|
||||
#{},
|
||||
UserIds
|
||||
).
|
||||
|
||||
-spec retain_member(term(), map(), #{integer() => map()}) -> #{integer() => map()}.
|
||||
retain_member(UserId, Data, Acc) when is_integer(UserId) ->
|
||||
case guild_data_index_members:get_member_ets(UserId, Data) of
|
||||
Member when is_map(Member) -> Acc#{UserId => Member};
|
||||
_ -> Acc
|
||||
end;
|
||||
retain_member(_UserId, _Data, Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec connect_user_id(map()) -> integer() | undefined.
|
||||
connect_user_id(Item) ->
|
||||
Request = maps:get(request, Item, #{}),
|
||||
case maps:get(user_id, Request, undefined) of
|
||||
UserId when is_integer(UserId) -> UserId;
|
||||
_ -> undefined
|
||||
end.
|
||||
|
||||
-spec voice_state_user_ids(map()) -> [integer()].
|
||||
voice_state_user_ids(Base) ->
|
||||
maps:fold(
|
||||
fun(_Key, VoiceState, Acc) -> add_voice_state_user_id(VoiceState, Acc) end,
|
||||
[],
|
||||
voice_state_utils:voice_states(Base)
|
||||
).
|
||||
|
||||
-spec add_voice_state_user_id(term(), [integer()]) -> [integer()].
|
||||
add_voice_state_user_id(VoiceState, Acc) ->
|
||||
case voice_state_utils:voice_state_user_id(VoiceState) of
|
||||
UserId when is_integer(UserId) -> [UserId | Acc];
|
||||
_ -> Acc
|
||||
end.
|
||||
|
||||
-spec compute_and_send_done(map(), pid(), map()) -> ok.
|
||||
compute_and_send_done(Item, GuildPid, Snapshot) ->
|
||||
Request = maps:get(request, Item, #{}),
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
-export([get_vanity_url_channel/1]).
|
||||
-export([get_first_viewable_text_channel/1]).
|
||||
-export([get_guild_state/2]).
|
||||
-export([build_connect_snapshot/2]).
|
||||
-export([fetch_latest_voice_states/1]).
|
||||
-export([find_everyone_viewable_text_channel/2]).
|
||||
|
||||
@@ -21,6 +22,10 @@
|
||||
-type user_id() :: integer().
|
||||
-type guild_id() :: integer().
|
||||
|
||||
-define(CONNECT_SNAPSHOT_HEAVY_MEMBER_KEYS, [
|
||||
<<"members">>, members_normalized, <<"member_role_index">>, members_sorted_ids
|
||||
]).
|
||||
|
||||
-export_type([guild_state/0, guild_reply/1, user_id/0]).
|
||||
|
||||
-spec get_guild_data(map(), guild_state()) -> guild_reply(map()).
|
||||
@@ -147,6 +152,92 @@ get_guild_state(UserId, State) ->
|
||||
JoinedAt
|
||||
).
|
||||
|
||||
-spec build_connect_snapshot(map(), guild_state()) -> map().
|
||||
build_connect_snapshot(Item, State) ->
|
||||
Base = maps:with(
|
||||
[
|
||||
id,
|
||||
data,
|
||||
sessions,
|
||||
member_count,
|
||||
voice_server_pid,
|
||||
voice_states,
|
||||
member_list_engine,
|
||||
virtual_channel_access
|
||||
],
|
||||
State
|
||||
),
|
||||
maybe_trim_connect_snapshot(Item, Base, State).
|
||||
|
||||
-spec maybe_trim_connect_snapshot(map(), map(), guild_state()) -> map().
|
||||
maybe_trim_connect_snapshot(Item, Base, State) ->
|
||||
case has_members_ets(State) of
|
||||
true -> trim_connect_snapshot(Item, Base);
|
||||
false -> Base
|
||||
end.
|
||||
|
||||
-spec has_members_ets(guild_state()) -> boolean().
|
||||
has_members_ets(#{data := #{members_ets := Tab}}) -> is_reference(Tab);
|
||||
has_members_ets(_) -> false.
|
||||
|
||||
-spec trim_connect_snapshot(map(), map()) -> map().
|
||||
trim_connect_snapshot(Item, #{data := Data} = Base) when is_map(Data) ->
|
||||
Retained = retained_member_map(Item, Base, Data),
|
||||
Trimmed = maps:without(?CONNECT_SNAPSHOT_HEAVY_MEMBER_KEYS, Data),
|
||||
Base#{
|
||||
data => Trimmed#{
|
||||
<<"members">> => Retained,
|
||||
members_normalized => Retained,
|
||||
<<"member_role_index">> =>
|
||||
guild_data_index_members:build_member_role_index(Retained)
|
||||
}
|
||||
};
|
||||
trim_connect_snapshot(_Item, Base) ->
|
||||
Base.
|
||||
|
||||
-spec retained_member_map(map(), map(), map()) -> #{integer() => map()}.
|
||||
retained_member_map(Item, Base, Data) ->
|
||||
UserIds = [connect_user_id(Item) | voice_state_user_ids(Base)],
|
||||
lists:foldl(
|
||||
fun(UserId, Acc) -> retain_member(UserId, Data, Acc) end,
|
||||
#{},
|
||||
UserIds
|
||||
).
|
||||
|
||||
-spec retain_member(term(), map(), #{integer() => map()}) -> #{integer() => map()}.
|
||||
retain_member(UserId, Data, Acc) when is_integer(UserId) ->
|
||||
case guild_data_index_members:get_member_ets(UserId, Data) of
|
||||
Member when is_map(Member) -> Acc#{UserId => Member};
|
||||
_ -> Acc
|
||||
end;
|
||||
retain_member(_UserId, _Data, Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec connect_user_id(map()) -> integer() | undefined.
|
||||
connect_user_id(Item) ->
|
||||
Request = maps:get(request, Item, #{}),
|
||||
case maps:get(user_id, Request, undefined) of
|
||||
UserId when is_integer(UserId) -> UserId;
|
||||
_ -> undefined
|
||||
end.
|
||||
|
||||
-spec voice_state_user_ids(map()) -> [integer()].
|
||||
voice_state_user_ids(Base) ->
|
||||
maps:fold(
|
||||
fun(_Key, VoiceState, Acc) -> add_voice_state_user_id(VoiceState, Acc) end,
|
||||
[],
|
||||
voice_state_utils:voice_states(Base)
|
||||
).
|
||||
|
||||
-spec add_voice_state_user_id(term(), [integer()]) -> [integer()].
|
||||
add_voice_state_user_id(VoiceState, Acc) when is_map(VoiceState) ->
|
||||
case voice_state_utils:voice_state_user_id(VoiceState) of
|
||||
UserId when is_integer(UserId) -> [UserId | Acc];
|
||||
_ -> Acc
|
||||
end;
|
||||
add_voice_state_user_id(_VoiceState, Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec resolve_voice_members([map()], map()) -> [map()].
|
||||
resolve_voice_members(VoiceStates, Data) ->
|
||||
lists:filtermap(fun(VS) -> resolve_voice_member_entry(VS, Data) end, VoiceStates).
|
||||
@@ -164,14 +255,18 @@ resolve_voice_member_entry(VoiceState, Data) ->
|
||||
resolve_voice_member_by_lookup(VoiceState, Data) ->
|
||||
case voice_state_utils:voice_state_user_id(VoiceState) of
|
||||
UserId when is_integer(UserId) ->
|
||||
case guild_data_index_members:get_member_ets(UserId, Data) of
|
||||
Member when is_map(Member) -> {true, Member};
|
||||
_ -> false
|
||||
end;
|
||||
resolve_voice_member_from_index(UserId, Data);
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec resolve_voice_member_from_index(integer(), map()) -> {true, map()} | false.
|
||||
resolve_voice_member_from_index(UserId, Data) ->
|
||||
case guild_data_index_members:get_member_ets(UserId, Data) of
|
||||
Member when is_map(Member) -> {true, Member};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
-spec fetch_latest_voice_states(guild_state()) -> guild_state().
|
||||
fetch_latest_voice_states(State) ->
|
||||
case maps:get(voice_server_pid, State, undefined) of
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
-export([
|
||||
normalize_data/1,
|
||||
normalize_map/1,
|
||||
member_map/1,
|
||||
member_values/1,
|
||||
member_list/1,
|
||||
@@ -36,6 +37,12 @@
|
||||
|
||||
-spec normalize_data(term()) -> term().
|
||||
normalize_data(Data) when is_map(Data) ->
|
||||
normalize_map(Data);
|
||||
normalize_data(Data) ->
|
||||
Data.
|
||||
|
||||
-spec normalize_map(map()) -> guild_data().
|
||||
normalize_map(Data) ->
|
||||
Data0 =
|
||||
case guild_data_normalize:guild_data(Data) of
|
||||
Normalized when is_map(Normalized) -> Normalized;
|
||||
@@ -60,9 +67,7 @@ normalize_data(Data) when is_map(Data) ->
|
||||
guild_data_index_roles:build_role_perms_cache(Roles),
|
||||
overwrite_perms_cache =>
|
||||
guild_data_index_channels:build_overwrite_perms_cache(Channels)
|
||||
};
|
||||
normalize_data(Data) ->
|
||||
Data.
|
||||
}.
|
||||
|
||||
-spec member_map(term()) -> map().
|
||||
member_map(Data) -> guild_data_index_members:member_map(Data).
|
||||
@@ -104,7 +109,8 @@ role_list(Data) -> guild_data_index_roles:role_list(Data).
|
||||
role_index(Data) -> guild_data_index_roles:role_index(Data).
|
||||
|
||||
-spec put_roles(term(), guild_data()) -> guild_data().
|
||||
put_roles(Roles, Data) -> guild_data_index_roles:put_roles(Roles, Data).
|
||||
put_roles(Roles, Data) ->
|
||||
ensure_guild_data(guild_data_index_roles:put_roles(Roles, Data)).
|
||||
|
||||
-spec channel_list(term()) -> [map()].
|
||||
channel_list(Data) -> guild_data_index_channels:channel_list(Data).
|
||||
@@ -113,7 +119,8 @@ channel_list(Data) -> guild_data_index_channels:channel_list(Data).
|
||||
channel_index(Data) -> guild_data_index_channels:channel_index(Data).
|
||||
|
||||
-spec put_channels(term(), guild_data()) -> guild_data().
|
||||
put_channels(Channels, Data) -> guild_data_index_channels:put_channels(Channels, Data).
|
||||
put_channels(Channels, Data) ->
|
||||
ensure_guild_data(guild_data_index_channels:put_channels(Channels, Data)).
|
||||
|
||||
-spec build_role_perms_cache([map()]) -> map().
|
||||
build_role_perms_cache(Roles) -> guild_data_index_roles:build_role_perms_cache(Roles).
|
||||
@@ -150,6 +157,10 @@ extract_integer_list(Value) ->
|
||||
ensure_data_map(State) ->
|
||||
map_utils:ensure_map(map_utils:get_safe(State, data, #{})).
|
||||
|
||||
-spec ensure_guild_data(term()) -> guild_data().
|
||||
ensure_guild_data(Data) when is_map(Data) ->
|
||||
Data.
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
@@ -159,13 +170,13 @@ normalize_data_builds_indexes_test() ->
|
||||
<<"roles">> => [#{<<"id">> => <<"100">>}],
|
||||
<<"channels">> => [#{<<"id">> => <<"200">>}]
|
||||
},
|
||||
Normalized = normalize_data(Data),
|
||||
Normalized = normalize_map(Data),
|
||||
?assert(is_map(maps:get(<<"members">>, Normalized))),
|
||||
?assertMatch(#{100 := _}, role_index(Normalized)),
|
||||
?assertMatch(#{200 := _}, channel_index(Normalized)).
|
||||
|
||||
normalize_data_empty_lists_test() ->
|
||||
Normalized = normalize_data(#{<<"members">> => [], <<"roles">> => [], <<"channels">> => []}),
|
||||
Normalized = normalize_map(#{<<"members">> => [], <<"roles">> => [], <<"channels">> => []}),
|
||||
?assertEqual(#{}, maps:get(<<"members">>, Normalized)),
|
||||
?assertEqual([], maps:get(<<"roles">>, Normalized)),
|
||||
?assertEqual([], maps:get(<<"channels">>, Normalized)).
|
||||
@@ -175,7 +186,7 @@ normalize_data_non_map_input_test() ->
|
||||
?assertEqual(42, normalize_data(42)).
|
||||
|
||||
normalize_data_missing_keys_defaults_test() ->
|
||||
Normalized = normalize_data(#{}),
|
||||
Normalized = normalize_map(#{}),
|
||||
?assertEqual(#{}, maps:get(<<"members">>, Normalized)),
|
||||
?assertEqual([], maps:get(<<"roles">>, Normalized)),
|
||||
?assertEqual([], maps:get(<<"channels">>, Normalized)).
|
||||
|
||||
@@ -23,7 +23,7 @@ init_base_state(GuildState) ->
|
||||
Data0 = maps:get(data, TransferSafe, #{}),
|
||||
ExistingVoice = maps:get(voice_states, TransferSafe, #{}),
|
||||
{VoiceStates, Data1} = extract_voice_states_from_data(Data0, ExistingVoice),
|
||||
NormalizedData = guild_data_index:normalize_data(Data1),
|
||||
NormalizedData = guild_data_index:normalize_map(Data1),
|
||||
MemberTab = ets:new(guild_members_data, [set, public, {read_concurrency, true}]),
|
||||
populate_member_ets(MemberTab, NormalizedData),
|
||||
BaseState = TransferSafe#{
|
||||
@@ -122,7 +122,7 @@ handle_reload(NewData, State) ->
|
||||
{ReloadVoiceStates, ReloadData} = extract_voice_states_from_data(
|
||||
NewData, ExistingVoiceStates
|
||||
),
|
||||
NormalizedNewData = guild_data_index:normalize_data(ReloadData),
|
||||
NormalizedNewData = guild_data_index:normalize_map(ReloadData),
|
||||
NewState0 = State#{voice_states => ReloadVoiceStates, data => NormalizedNewData},
|
||||
NewState1 = guild_availability:handle_unavailability_transition(State, NewState0),
|
||||
NewState2 = guild_sessions:refresh_all_viewable_channels(NewState1),
|
||||
|
||||
@@ -457,8 +457,8 @@ permission_cache_rebuilds_on_member_role_change_test() ->
|
||||
{ok, Snapshot} = permission_cache_refresh(
|
||||
guild_member_update, EventData, OldState, NewState
|
||||
),
|
||||
Member = guild_permissions:find_member_by_user_id(UserId, Snapshot),
|
||||
?assertEqual([10, 11], maps:get(<<"roles">>, Member)).
|
||||
#{<<"roles">> := Roles} = guild_permissions:find_member_by_user_id(UserId, Snapshot),
|
||||
?assertEqual([10, 11], Roles).
|
||||
|
||||
permission_cache_rebuilds_on_member_timeout_change_test() ->
|
||||
UserId = 4242,
|
||||
@@ -473,8 +473,9 @@ permission_cache_rebuilds_on_member_timeout_change_test() ->
|
||||
{ok, Snapshot} = permission_cache_refresh(
|
||||
guild_member_update, EventData, OldState, NewState
|
||||
),
|
||||
Member = guild_permissions:find_member_by_user_id(UserId, Snapshot),
|
||||
?assertEqual(Until, maps:get(<<"communication_disabled_until">>, Member)).
|
||||
#{<<"communication_disabled_until">> := MemberUntil} =
|
||||
guild_permissions:find_member_by_user_id(UserId, Snapshot),
|
||||
?assertEqual(Until, MemberUntil).
|
||||
|
||||
permission_cache_rebuilds_for_other_mutating_events_test() ->
|
||||
State = permission_cache_state(7710004, permission_cache_member(4242, [10], <<"old">>)),
|
||||
@@ -497,7 +498,7 @@ permission_cache_refresh(Event, EventData, OldState, NewState) ->
|
||||
permission_cache_state(GuildId, Member) ->
|
||||
#{
|
||||
id => GuildId,
|
||||
data => guild_data_index:normalize_data(#{
|
||||
data => guild_data_index:normalize_map(#{
|
||||
<<"guild">> => #{<<"owner_id">> => <<"999">>},
|
||||
<<"roles">> => [prune_role(10, 0), prune_role(11, 0)],
|
||||
<<"members">> => [Member],
|
||||
|
||||
@@ -79,23 +79,26 @@ range(_OSet, _Start, _Count) ->
|
||||
to_list(_OSet) ->
|
||||
erlang:nif_error(nif_not_loaded).
|
||||
|
||||
-spec nif_path() -> file:filename_all().
|
||||
-spec nif_path() -> string().
|
||||
nif_path() ->
|
||||
filename:join(priv_dir(), ?NIF_NAME).
|
||||
require_string_path(filename:join(priv_dir(), ?NIF_NAME)).
|
||||
|
||||
-spec priv_dir() -> file:filename_all().
|
||||
-spec require_string_path(file:filename_all()) -> string().
|
||||
require_string_path(Path) when is_list(Path) -> Path.
|
||||
|
||||
-spec priv_dir() -> string().
|
||||
priv_dir() ->
|
||||
case code:priv_dir(fluxer_gateway) of
|
||||
{error, _Reason} -> priv_dir_from_beam();
|
||||
Dir -> Dir
|
||||
Dir when is_list(Dir) -> Dir
|
||||
end.
|
||||
|
||||
-spec priv_dir_from_beam() -> file:filename_all().
|
||||
-spec priv_dir_from_beam() -> string().
|
||||
priv_dir_from_beam() ->
|
||||
case code:which(?MODULE) of
|
||||
Beam when is_list(Beam) ->
|
||||
AppDir = filename:dirname(filename:dirname(Beam)),
|
||||
filename:join(AppDir, "priv");
|
||||
require_string_path(filename:join(AppDir, "priv"));
|
||||
_ ->
|
||||
"priv"
|
||||
end.
|
||||
|
||||
@@ -141,17 +141,15 @@ get_session_ranges(SessionId, ListId, Tab) ->
|
||||
list_ids(Tab) ->
|
||||
list_ids_loop(Tab, ets:next(Tab, {<<>>, <<>>}), []).
|
||||
|
||||
-spec fold_list_subs(
|
||||
binary(), ets:table(), fun((binary(), [range()], map()) -> map()), map()
|
||||
) -> map().
|
||||
-spec fold_list_subs(binary(), ets:table(), fun((binary(), [range()], Acc) -> Acc), Acc) -> Acc.
|
||||
fold_list_subs(ListId, Tab, Fun, Acc0) ->
|
||||
fold_list_subs_loop(Tab, ets:next(Tab, {ListId, <<>>}), ListId, Fun, Acc0).
|
||||
|
||||
-spec fold_lists(fun((binary(), list_subs(), map()) -> map()), map(), ets:table()) -> map().
|
||||
-spec fold_lists(fun((binary(), list_subs(), Acc) -> Acc), Acc, ets:table()) -> Acc.
|
||||
fold_lists(Fun, Acc0, Tab) ->
|
||||
fold_lists_loop(Fun, Acc0, Tab, ets:next(Tab, {<<>>, <<>>})).
|
||||
|
||||
-spec fold_list_ids(fun((binary(), Acc) -> Acc), Acc, ets:table()) -> Acc when Acc :: term().
|
||||
-spec fold_list_ids(fun((binary(), Acc) -> Acc), Acc, ets:table()) -> Acc.
|
||||
fold_list_ids(Fun, Acc0, Tab) ->
|
||||
fold_list_ids_loop(Fun, Acc0, Tab, ets:next(Tab, {<<>>, <<>>})).
|
||||
|
||||
@@ -191,8 +189,8 @@ list_ids_loop(Tab, Key, Acc) ->
|
||||
list_ids_loop(Tab, ets:next(Tab, Key), Acc).
|
||||
|
||||
-spec fold_list_subs_loop(
|
||||
ets:table(), term(), binary(), fun((binary(), [range()], map()) -> map()), map()
|
||||
) -> map().
|
||||
ets:table(), term(), binary(), fun((binary(), [range()], Acc) -> Acc), Acc
|
||||
) -> Acc.
|
||||
fold_list_subs_loop(_Tab, '$end_of_table', _ListId, _Fun, Acc) ->
|
||||
Acc;
|
||||
fold_list_subs_loop(_Tab, {Other, _}, ListId, _Fun, Acc) when Other =/= ListId ->
|
||||
@@ -202,8 +200,8 @@ fold_list_subs_loop(Tab, {ListId, SId} = Key, ListId, Fun, Acc) when is_binary(S
|
||||
fold_list_subs_loop(Tab, ets:next(Tab, Key), ListId, Fun, Fun(SId, Ranges, Acc)).
|
||||
|
||||
-spec fold_lists_loop(
|
||||
fun((binary(), list_subs(), map()) -> map()), map(), ets:table(), term()
|
||||
) -> map().
|
||||
fun((binary(), list_subs(), Acc) -> Acc), Acc, ets:table(), term()
|
||||
) -> Acc.
|
||||
fold_lists_loop(_Fun, Acc, _Tab, '$end_of_table') ->
|
||||
Acc;
|
||||
fold_lists_loop(_Fun, Acc, _Tab, {idx_tab, _}) ->
|
||||
@@ -214,8 +212,7 @@ fold_lists_loop(Fun, Acc, Tab, {ListId, _} = Key) when is_binary(ListId) ->
|
||||
NextListKey = skip_past_list(Tab, Key, ListId),
|
||||
fold_lists_loop(Fun, Acc2, Tab, NextListKey).
|
||||
|
||||
-spec fold_list_ids_loop(fun((binary(), Acc) -> Acc), Acc, ets:table(), term()) -> Acc when
|
||||
Acc :: term().
|
||||
-spec fold_list_ids_loop(fun((binary(), Acc) -> Acc), Acc, ets:table(), term()) -> Acc.
|
||||
fold_list_ids_loop(_Fun, Acc, _Tab, '$end_of_table') ->
|
||||
Acc;
|
||||
fold_list_ids_loop(_Fun, Acc, _Tab, {idx_tab, _}) ->
|
||||
|
||||
@@ -46,7 +46,7 @@ put_state(_) ->
|
||||
|
||||
-spec put_data(guild_id(), guild_data()) -> ok.
|
||||
put_data(GuildId, Data) when is_integer(GuildId), is_map(Data) ->
|
||||
NormalizedData = guild_data_index:normalize_data(Data),
|
||||
NormalizedData = guild_data_index:normalize_map(Data),
|
||||
put_normalized_data(GuildId, NormalizedData);
|
||||
put_data(_, _) ->
|
||||
ok.
|
||||
|
||||
@@ -57,7 +57,7 @@ process_presence(UserId, Payload, Member, State) ->
|
||||
),
|
||||
process_presence_change(UserId, OldPresence, PresenceMap, Status, State).
|
||||
|
||||
-spec process_presence_change(user_id(), map() | undefined, map(), atom(), guild_state()) ->
|
||||
-spec process_presence_change(user_id(), map(), map(), atom(), guild_state()) ->
|
||||
{noreply, guild_state()}.
|
||||
process_presence_change(UserId, PresenceMap, PresenceMap, Status, State) ->
|
||||
{noreply, maybe_handle_unchanged_presence(Status, UserId, State)};
|
||||
@@ -397,7 +397,7 @@ handle_bus_presence_unchanged_payload_is_noop_test() ->
|
||||
<<"afk">> => false,
|
||||
<<"user">> => #{<<"id">> => <<"1">>, <<"username">> => <<"Alpha">>}
|
||||
},
|
||||
Member = guild_permissions:find_member_by_user_id(1, State),
|
||||
Member = #{} = guild_permissions:find_member_by_user_id(1, State),
|
||||
PresenceMap = build_presence_map(Payload, Member),
|
||||
ets:insert(maps:get(member_presence, State), {1, PresenceMap}),
|
||||
{noreply, NewState} = handle_bus_presence(1, Payload, State),
|
||||
|
||||
@@ -29,7 +29,7 @@ update_state(Event, EventData, State) ->
|
||||
ensure_normalized(Data) ->
|
||||
case maps:is_key(members_normalized, Data) of
|
||||
true -> Data;
|
||||
false -> guild_data_index:normalize_data(Data)
|
||||
false -> guild_data_index:normalize_map(Data)
|
||||
end.
|
||||
|
||||
-spec update_data_for_event(event(), event_data(), guild_data(), guild_state()) -> guild_data().
|
||||
|
||||
@@ -140,7 +140,7 @@ sync_user_blocked_ids(UserId, BlockedIds) ->
|
||||
|
||||
-spec sync_user_blocked_ids_local(integer(), term()) -> ok.
|
||||
sync_user_blocked_ids_local(UserId, BlockedIds) ->
|
||||
case integer_list(BlockedIds) of
|
||||
case push_normalize:integer_list(BlockedIds) of
|
||||
{ok, TypedBlockedIds} ->
|
||||
put_blocked_ids_local(UserId, TypedBlockedIds);
|
||||
error ->
|
||||
@@ -175,7 +175,7 @@ invalidate_user_badge_count_local(UserId) ->
|
||||
|
||||
-spec invalidate_user_badge_counts_local(term()) -> ok.
|
||||
invalidate_user_badge_counts_local(UserIds) ->
|
||||
case integer_list(UserIds) of
|
||||
case push_normalize:integer_list(UserIds) of
|
||||
{ok, TypedUserIds} ->
|
||||
lists:foreach(fun invalidate_user_badge_count_local/1, TypedUserIds);
|
||||
error ->
|
||||
@@ -419,7 +419,7 @@ dispatch_if_eligible(
|
||||
|
||||
-spec handle_sync_user_blocked_ids(integer(), term(), state()) -> {noreply, state()}.
|
||||
handle_sync_user_blocked_ids(UserId, BlockedIds, State) ->
|
||||
case integer_list(BlockedIds) of
|
||||
case push_normalize:integer_list(BlockedIds) of
|
||||
{ok, TypedBlockedIds} ->
|
||||
push_ets_cache:put_blocked_ids(UserId, TypedBlockedIds),
|
||||
{noreply, State};
|
||||
@@ -465,20 +465,6 @@ log_message_worker_drop(dropped, Params) ->
|
||||
channel_id => maps:get(<<"channel_id">>, MessageData, undefined)
|
||||
}).
|
||||
|
||||
-spec integer_list(term()) -> {ok, [integer()]} | error.
|
||||
integer_list(Value) when is_list(Value) ->
|
||||
integer_list(Value, []);
|
||||
integer_list(_) ->
|
||||
error.
|
||||
|
||||
-spec integer_list([term()], [integer()]) -> {ok, [integer()]} | error.
|
||||
integer_list([], Acc) ->
|
||||
{ok, lists:reverse(Acc)};
|
||||
integer_list([Value | Rest], Acc) when is_integer(Value) ->
|
||||
integer_list(Rest, [Value | Acc]);
|
||||
integer_list(_, _) ->
|
||||
error.
|
||||
|
||||
-spec local_cache_mutation(fun(() -> ok)) -> ok.
|
||||
local_cache_mutation(Fun) ->
|
||||
case whereis(?MODULE) of
|
||||
|
||||
@@ -527,27 +527,19 @@ send_job_options(UserIds0, Options) ->
|
||||
ChannelName0 = maps:get(channel_name, Options),
|
||||
BadgeCountsTtlSeconds = maps:get(badge_counts_ttl_seconds, Options),
|
||||
MarkdownContext = maps:get(markdown_context, Options, #{}),
|
||||
case {integer_list(UserIds0), optional_binary(GuildName0), optional_binary(ChannelName0)} of
|
||||
case
|
||||
{
|
||||
push_normalize:integer_list(UserIds0),
|
||||
optional_binary(GuildName0),
|
||||
optional_binary(ChannelName0)
|
||||
}
|
||||
of
|
||||
{{ok, UserIds}, {ok, GuildName}, {ok, ChannelName}} when is_map(MarkdownContext) ->
|
||||
{ok, UserIds, GuildName, ChannelName, BadgeCountsTtlSeconds, MarkdownContext};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec integer_list(term()) -> {ok, [integer()]} | error.
|
||||
integer_list(Value) when is_list(Value) ->
|
||||
integer_list(Value, []);
|
||||
integer_list(_) ->
|
||||
error.
|
||||
|
||||
-spec integer_list([term()], [integer()]) -> {ok, [integer()]} | error.
|
||||
integer_list([], Acc) ->
|
||||
{ok, lists:reverse(Acc)};
|
||||
integer_list([Value | Rest], Acc) when is_integer(Value) ->
|
||||
integer_list(Rest, [Value | Acc]);
|
||||
integer_list(_, _) ->
|
||||
error.
|
||||
|
||||
-spec optional_binary(term()) -> {ok, binary() | undefined} | error.
|
||||
optional_binary(undefined) ->
|
||||
{ok, undefined};
|
||||
|
||||
@@ -104,16 +104,20 @@ resolve_notification_group(Data, MessageTag) ->
|
||||
Group when is_binary(Group), byte_size(Group) > 0 ->
|
||||
Group;
|
||||
_ ->
|
||||
case
|
||||
push_utils:normalize_binary(
|
||||
maps:get(<<"channel_id">>, Data, undefined), undefined
|
||||
)
|
||||
of
|
||||
ChannelId when is_binary(ChannelId), byte_size(ChannelId) > 0 ->
|
||||
<<"channel:", ChannelId/binary>>;
|
||||
_ ->
|
||||
derive_channel_tag_from_message_tag(MessageTag)
|
||||
end
|
||||
resolve_channel_notification_group(Data, MessageTag)
|
||||
end.
|
||||
|
||||
-spec resolve_channel_notification_group(map(), binary()) -> binary().
|
||||
resolve_channel_notification_group(Data, MessageTag) ->
|
||||
case
|
||||
push_utils:normalize_binary(
|
||||
maps:get(<<"channel_id">>, Data, undefined), undefined
|
||||
)
|
||||
of
|
||||
ChannelId when is_binary(ChannelId), byte_size(ChannelId) > 0 ->
|
||||
<<"channel:", ChannelId/binary>>;
|
||||
_ ->
|
||||
derive_channel_tag_from_message_tag(MessageTag)
|
||||
end.
|
||||
|
||||
-spec derive_channel_tag_from_message_tag(binary()) -> binary().
|
||||
|
||||
@@ -26,23 +26,26 @@ available() ->
|
||||
render_push_preview_nif(_Content, _ContextJson) ->
|
||||
erlang:nif_error(nif_not_loaded).
|
||||
|
||||
-spec nif_path() -> file:filename_all().
|
||||
-spec nif_path() -> string().
|
||||
nif_path() ->
|
||||
filename:join(priv_dir(), ?NIF_NAME).
|
||||
require_string_path(filename:join(priv_dir(), ?NIF_NAME)).
|
||||
|
||||
-spec priv_dir() -> file:filename_all().
|
||||
-spec require_string_path(file:filename_all()) -> string().
|
||||
require_string_path(Path) when is_list(Path) -> Path.
|
||||
|
||||
-spec priv_dir() -> string().
|
||||
priv_dir() ->
|
||||
case code:priv_dir(fluxer_gateway) of
|
||||
{error, _Reason} -> priv_dir_from_beam();
|
||||
Dir -> Dir
|
||||
Dir when is_list(Dir) -> Dir
|
||||
end.
|
||||
|
||||
-spec priv_dir_from_beam() -> file:filename_all().
|
||||
-spec priv_dir_from_beam() -> string().
|
||||
priv_dir_from_beam() ->
|
||||
case code:which(?MODULE) of
|
||||
Beam when is_list(Beam) ->
|
||||
AppDir = filename:dirname(filename:dirname(Beam)),
|
||||
filename:join(AppDir, "priv");
|
||||
require_string_path(filename:join(AppDir, "priv"));
|
||||
_ ->
|
||||
"priv"
|
||||
end.
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
-export([
|
||||
optional_guild_id/1,
|
||||
notification_level/1
|
||||
notification_level/1,
|
||||
integer_list/1
|
||||
]).
|
||||
|
||||
-spec optional_guild_id(term()) -> pos_integer() | undefined.
|
||||
@@ -21,3 +22,17 @@ notification_level(Value) ->
|
||||
Level when Level >= -1, Level =< 3 -> Level;
|
||||
_ -> undefined
|
||||
end.
|
||||
|
||||
-spec integer_list(term()) -> {ok, [integer()]} | error.
|
||||
integer_list(Value) when is_list(Value) ->
|
||||
integer_list(Value, []);
|
||||
integer_list(_) ->
|
||||
error.
|
||||
|
||||
-spec integer_list([term()], [integer()]) -> {ok, [integer()]} | error.
|
||||
integer_list([], Acc) ->
|
||||
{ok, lists:reverse(Acc)};
|
||||
integer_list([Value | Rest], Acc) when is_integer(Value) ->
|
||||
integer_list(Rest, [Value | Acc]);
|
||||
integer_list(_, _) ->
|
||||
error.
|
||||
|
||||
@@ -71,8 +71,18 @@ cleanup_indexed_down(SessionId, Ref, Pid, Sessions0, Refs1, State) ->
|
||||
end.
|
||||
|
||||
-spec cleanup_down_fallback(pid(), session_map(), map()) -> map().
|
||||
cleanup_down_fallback(Pid, Sessions0, State) ->
|
||||
Sessions1 = process_registry:cleanup_on_down(session, Pid, Sessions0),
|
||||
cleanup_down_fallback(DeadPid, Sessions0, State) ->
|
||||
Sessions1 = maps:fold(
|
||||
fun
|
||||
(SessionId, {SessionPid, _Ref}, Acc) when SessionPid =:= DeadPid ->
|
||||
ok = process_registry:safe_unregister({session, SessionId}, DeadPid),
|
||||
Acc;
|
||||
(SessionId, SessionRef, Acc) ->
|
||||
Acc#{SessionId => SessionRef}
|
||||
end,
|
||||
#{},
|
||||
Sessions0
|
||||
),
|
||||
replace_sessions(Sessions1, State).
|
||||
|
||||
-spec ref_index(map()) -> ref_index().
|
||||
|
||||
@@ -217,10 +217,10 @@ dedup_users(Users) ->
|
||||
Map = lists:foldl(fun add_user_by_id/2, #{}, Users),
|
||||
maps:values(Map).
|
||||
|
||||
-spec add_user_by_id(term(), #{user_id() => map()}) -> #{user_id() => map()}.
|
||||
-spec add_user_by_id(map() | undefined, #{user_id() => map()}) -> #{user_id() => map()}.
|
||||
add_user_by_id(undefined, Acc) ->
|
||||
Acc;
|
||||
add_user_by_id(U, Acc) ->
|
||||
add_user_by_id(U, Acc) when is_map(U) ->
|
||||
case user_id(U) of
|
||||
undefined -> Acc;
|
||||
Id -> Acc#{Id => U#{<<"id">> => Id}}
|
||||
|
||||
@@ -81,9 +81,9 @@ get_auth_context_matches_get_data_scalars_test() ->
|
||||
get_auth_context_is_reachable_through_the_query_handler_test() ->
|
||||
State = test_state(),
|
||||
Request = {get_guild_auth_context, #{user_id => 200, channel_id => 500}},
|
||||
{reply, Reply, _} = guild_query_handler:handle_call(Request, {self(), make_ref()}, State),
|
||||
Context = maps:get(auth_context, Reply),
|
||||
?assertEqual(500, maps:get(<<"id">>, maps:get(<<"parent_channel">>, Context))).
|
||||
{reply, #{auth_context := #{<<"parent_channel">> := #{<<"id">> := ParentChannelId}}}, _} =
|
||||
guild_query_handler:handle_call(Request, {self(), make_ref()}, State),
|
||||
?assertEqual(500, ParentChannelId).
|
||||
|
||||
get_guild_state_filters_channels_test() ->
|
||||
State = test_state(),
|
||||
|
||||
@@ -85,7 +85,7 @@ send_passive_updates_uses_current_channel_index_and_message_only_payload_test()
|
||||
ChannelId = 1497639278555484216,
|
||||
OldMessageId = 1500000000000000000,
|
||||
NewMessageId = 1500000000000000001,
|
||||
Data0 = guild_data_index:normalize_data(#{
|
||||
Data0 = guild_data_index:normalize_map(#{
|
||||
<<"guild">> => #{<<"id">> => GuildId, <<"owner_id">> => UserId},
|
||||
<<"channels">> => [#{<<"id">> => ChannelId, <<"last_message_id">> => OldMessageId}],
|
||||
<<"members">> => [#{<<"user">> => #{<<"id">> => UserId}, <<"roles">> => []}],
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
handle_role_delete_strips_from_members_test() ->
|
||||
RoleIdToDelete = <<"200">>,
|
||||
Data = guild_data_index:normalize_data(#{
|
||||
Data = guild_data_index:normalize_map(#{
|
||||
<<"roles">> => [
|
||||
#{<<"id">> => <<"100">>, <<"name">> => <<"Admin">>},
|
||||
#{<<"id">> => <<"200">>, <<"name">> => <<"Moderator">>}
|
||||
@@ -138,7 +138,7 @@ handle_role_delete_removes_role_from_list_test() ->
|
||||
?assertEqual(<<"Keep">>, maps:get(<<"name">>, hd(Roles))).
|
||||
|
||||
strip_role_from_members_no_affected_users_test() ->
|
||||
Data = guild_data_index:normalize_data(#{
|
||||
Data = guild_data_index:normalize_map(#{
|
||||
<<"roles">> => [],
|
||||
<<"members">> => #{
|
||||
1 => #{<<"user">> => #{<<"id">> => <<"1">>}, <<"roles">> => [<<"100">>]}
|
||||
|
||||
@@ -86,7 +86,9 @@ decoded_voice_state({pre_encoded, Bin}) -> json:decode(Bin);
|
||||
decoded_voice_state(Payload) -> Payload.
|
||||
|
||||
map_path_wire_bytes(VoiceState) ->
|
||||
iolist_to_binary(json:encode(guild_data_wire:payload(VoiceState))).
|
||||
iolist_to_binary(
|
||||
json:encode(guild_data_wire:payload(VoiceState), fun json:encode_value/2)
|
||||
).
|
||||
|
||||
confirm_join(ConnId, GuildState) ->
|
||||
VoiceServerState = (voice_server_state(GuildState))#{
|
||||
@@ -177,10 +179,11 @@ guild_fanout_pre_encodes_identical_wire_bytes_test() ->
|
||||
guild_fanout_payload_never_touches_dm_voice_states_test() ->
|
||||
{pre_encoded, Bin} = dispatch_payload_for(join_voice_state(<<"conn1">>)),
|
||||
State = #{dm_voice_states => #{}},
|
||||
EventData = #{} = json:decode(Bin),
|
||||
?assertEqual(
|
||||
State,
|
||||
session_dispatch_guild:update_dm_voice_states_map(
|
||||
voice_state_update, json:decode(Bin), State
|
||||
voice_state_update, EventData, State
|
||||
)
|
||||
).
|
||||
|
||||
@@ -191,9 +194,10 @@ dm_scoped_voice_state_stays_a_map_and_updates_dm_voice_states_test() ->
|
||||
State = session_dispatch_guild:update_dm_voice_states_map(
|
||||
voice_state_update, Payload, #{dm_voice_states => #{}}
|
||||
),
|
||||
#{dm_voice_states := #{<<"conn1">> := StoredVoiceState}} = State,
|
||||
?assertEqual(
|
||||
broadcast_payload(DmVS),
|
||||
maps:get(<<"conn1">>, maps:get(dm_voice_states, State))
|
||||
StoredVoiceState
|
||||
).
|
||||
|
||||
dispatch_payload_for(VoiceState) ->
|
||||
|
||||
@@ -5,10 +5,11 @@ name = "fluxer-gifs"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
fluxer_common = { path = "../fluxer_common" }
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.104"
|
||||
base64 = "0.22.1"
|
||||
fluxer-svc = { path = "../fluxer_svc", default-features = false }
|
||||
hmac = "0.13.0"
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "fluxer-media-proxy"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
@@ -19,7 +20,7 @@ harness = false
|
||||
|
||||
[dependencies]
|
||||
fluxer_common = { path = "../fluxer_common" }
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.104"
|
||||
axum = {version = "0.8.9", default-features = false, features = ["http1", "json", "query", "tokio"]}
|
||||
base64 = "0.22.1"
|
||||
bytes = "1.11.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[graph]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
all-features = false
|
||||
all-features = true
|
||||
no-default-features = false
|
||||
|
||||
[advisories]
|
||||
@@ -28,12 +28,19 @@ registries = []
|
||||
[bans]
|
||||
multiple-versions = "warn"
|
||||
wildcards = "deny"
|
||||
allow-wildcard-paths = true
|
||||
highlight = "simplest-path"
|
||||
workspace-default-features = "allow"
|
||||
external-default-features = "allow"
|
||||
allow = []
|
||||
deny = []
|
||||
skip = []
|
||||
skip = [
|
||||
{ crate = "getrandom@0.3.4", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "http@0.2.12", reason = "transitive dependency requires the older HTTP API" },
|
||||
{ crate = "http-body@0.4.6", reason = "transitive dependency requires the older HTTP body API" },
|
||||
{ crate = "rand_core@0.9.5", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "socket2@0.5.10", reason = "transitive dependency requires the older socket API" },
|
||||
]
|
||||
skip-tree = []
|
||||
|
||||
[sources]
|
||||
|
||||
Generated
+327
-55
@@ -63,9 +63,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -90,6 +90,24 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "aws-credential-types"
|
||||
version = "1.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.0"
|
||||
@@ -112,6 +130,112 @@ dependencies = [
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sigv4"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"hex",
|
||||
"hmac",
|
||||
"http 0.2.12",
|
||||
"http 1.4.2",
|
||||
"percent-encoding",
|
||||
"sha2",
|
||||
"time",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-async"
|
||||
version = "1.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-http"
|
||||
version = "0.63.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231"
|
||||
dependencies = [
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"bytes-utils",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime-api"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api-macros",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"http 1.4.2",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime-api-macros"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-types"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b42fcf341259d85ca10fac9a2f6448a8ec691c6955a18e45bc3b71a85fab85"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
"bytes-utils",
|
||||
"http 0.2.12",
|
||||
"http 1.4.2",
|
||||
"http-body 0.4.6",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"itoa",
|
||||
"num-integer",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"ryu",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.9"
|
||||
@@ -122,8 +246,8 @@ dependencies = [
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
@@ -152,8 +276,8 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
@@ -168,6 +292,16 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64-simd"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195"
|
||||
dependencies = [
|
||||
"outref",
|
||||
"vsimd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
@@ -195,6 +329,16 @@ version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "bytes-utils"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.63"
|
||||
@@ -221,9 +365,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
@@ -343,9 +487,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
@@ -375,10 +519,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.2"
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
@@ -409,6 +553,12 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -447,10 +597,11 @@ dependencies = [
|
||||
"bytes",
|
||||
"cc",
|
||||
"clap",
|
||||
"fluxer_common",
|
||||
"hex",
|
||||
"hmac",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"libc",
|
||||
"moka",
|
||||
@@ -465,7 +616,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"svg-hush",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -484,6 +634,33 @@ dependencies = [
|
||||
"libfuzzer-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluxer_common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aws-credential-types",
|
||||
"aws-sigv4",
|
||||
"axum",
|
||||
"base64",
|
||||
"hmac",
|
||||
"maxminddb",
|
||||
"moka",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
@@ -670,6 +847,17 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.2"
|
||||
@@ -680,6 +868,17 @@ dependencies = [
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
@@ -687,7 +886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http 1.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -698,8 +897,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@@ -734,8 +933,8 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
@@ -751,7 +950,7 @@ version = "0.27.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||
dependencies = [
|
||||
"http",
|
||||
"http 1.4.2",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
@@ -770,8 +969,8 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
"libc",
|
||||
@@ -910,6 +1109,12 @@ version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||
|
||||
[[package]]
|
||||
name = "ipnetwork"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
@@ -1069,6 +1274,19 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "maxminddb"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf6467428ad055b71e588bcedcbaf2ff605b3251deb0c52be4a04b674c546dd"
|
||||
dependencies = [
|
||||
"ipnetwork",
|
||||
"log",
|
||||
"memchr",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
@@ -1118,6 +1336,30 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
@@ -1136,6 +1378,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "outref"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -1198,6 +1446,12 @@ 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"
|
||||
@@ -1226,12 +1480,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.9"
|
||||
@@ -1399,10 +1647,11 @@ checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
@@ -1439,7 +1688,7 @@ checksum = "07bc3f1384cffa4f274dad2d4ddd73aed32fed8f786d96c6be8aa4e5fd3c3b58"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"http",
|
||||
"http 1.4.2",
|
||||
"reqwest",
|
||||
"thiserror",
|
||||
"tower-service",
|
||||
@@ -1455,7 +1704,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
"getrandom 0.2.17",
|
||||
"http",
|
||||
"http 1.4.2",
|
||||
"hyper",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
@@ -1815,19 +2064,6 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "svg-hush"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "929223e80cdcec0482207576ea09692dd71b2b559057fc172e292ecec9a97559"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"data-url",
|
||||
"quick-error",
|
||||
"url",
|
||||
"xml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@@ -1907,6 +2143,36 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
@@ -2006,8 +2272,8 @@ dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
@@ -2143,6 +2409,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
@@ -2172,6 +2444,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vsimd"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
@@ -2640,12 +2918,6 @@ dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.2"
|
||||
|
||||
@@ -7,11 +7,12 @@ use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(text) = std::str::from_utf8(data) {
|
||||
let path = external_path::build_external_media_proxy_path(text);
|
||||
let _ = external_path::reconstruct_original_url(&path);
|
||||
let sig = signing::create_signature(&path, b"fuzz-secret");
|
||||
let _ = signing::verify_signature(&path, &sig, b"fuzz-secret");
|
||||
let _ = signing::verify_signature(text, &sig, b"other-secret");
|
||||
if let Ok(path) = external_path::build_external_media_proxy_path(text) {
|
||||
let _ = external_path::reconstruct_original_url(&path);
|
||||
let sig = signing::create_signature(&path, b"fuzz-secret");
|
||||
let _ = signing::verify_signature(&path, &sig, b"fuzz-secret");
|
||||
let _ = signing::verify_signature(text, &sig, b"other-secret");
|
||||
}
|
||||
let _ = external_path::reconstruct_original_url(text);
|
||||
let _ = external_path::percent_decode(text, true);
|
||||
let _ = external_path::percent_decode(text, false);
|
||||
|
||||
@@ -1946,17 +1946,6 @@ static inline float fluxer_pq_oetf(float l) {
|
||||
return powf(num / den, m2);
|
||||
}
|
||||
|
||||
static inline float fluxer_hlg_oetf_display(float dl) {
|
||||
if (dl <= 0.0f) return 0.0f;
|
||||
if (dl >= 1.0f) dl = 1.0f;
|
||||
float scene = powf(dl, 1.0f / 1.2f);
|
||||
if (scene <= 1.0f / 12.0f) return sqrtf(3.0f * scene);
|
||||
const float a = 0.17883277f;
|
||||
const float b = 0.28466892f;
|
||||
const float c = 0.55991073f;
|
||||
return a * logf(12.0f * scene - b) + c;
|
||||
}
|
||||
|
||||
static inline float fluxer_srgb_oetf(float e) {
|
||||
if (e <= 0.0f) return 0.0f;
|
||||
if (e >= 1.0f) return 1.0f;
|
||||
|
||||
@@ -3,10 +3,11 @@ name = "fluxer-messages"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
fluxer_common = { path = "../fluxer_common" }
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.104"
|
||||
base64 = "0.22.1"
|
||||
chrono = { version = "0.4.45", default-features = false, features = ["serde"] }
|
||||
fluxer-svc = { path = "../fluxer_svc" }
|
||||
|
||||
@@ -90,7 +90,7 @@ pub struct MessagesShard {
|
||||
enum MessagesStorage {
|
||||
Postgres(PostgresMessagesStorage),
|
||||
#[cfg(feature = "scylla")]
|
||||
Scylla(ScyllaMessagesStorage),
|
||||
Scylla(Box<ScyllaMessagesStorage>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -339,7 +339,7 @@ impl MessagesShard {
|
||||
.await?;
|
||||
|
||||
Ok(Self {
|
||||
storage: MessagesStorage::Scylla(ScyllaMessagesStorage {
|
||||
storage: MessagesStorage::Scylla(Box::new(ScyllaMessagesStorage {
|
||||
db,
|
||||
stmt_get_by_id,
|
||||
stmt_get_latest,
|
||||
@@ -352,7 +352,7 @@ impl MessagesShard {
|
||||
stmt_get_attachment_decay,
|
||||
stmt_get_attachment_decay_many,
|
||||
stmt_delete_message,
|
||||
}),
|
||||
})),
|
||||
transport,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ name = "fluxer-snowflakes"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.104"
|
||||
fluxer-svc = { path = "../fluxer_svc", default-features = false }
|
||||
rmp-serde = "1.3"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
|
||||
@@ -5,7 +5,7 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.104"
|
||||
async-nats = "0.49"
|
||||
async-trait = "0.1"
|
||||
axum = { version = "0.8.9", default-features = false, features = ["http1", "tokio"] }
|
||||
@@ -17,7 +17,6 @@ libc = "0.2"
|
||||
moka = { version = "0.12.15", features = ["future"] }
|
||||
rmp-serde = "1.3"
|
||||
rustls = { version = "0.23", features = ["ring"] }
|
||||
rustls-pemfile = "2.2"
|
||||
scylla = { version = "1.6.0", features = ["chrono-04"], optional = true }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.150"
|
||||
|
||||
@@ -4,9 +4,11 @@ use crate::config::ServiceConfig;
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, Utc};
|
||||
use deadpool_postgres::{Client, Manager, Pool, Runtime, Transaction};
|
||||
use rustls::RootCertStore;
|
||||
use rustls::{
|
||||
RootCertStore,
|
||||
pki_types::{CertificateDer, pem::PemObject},
|
||||
};
|
||||
use serde_json::{Map, Number, Value};
|
||||
use std::io::Cursor;
|
||||
use std::str::FromStr;
|
||||
use tokio_postgres::{Config as PgConfig, Row, Statement, config::SslMode, types::ToSql};
|
||||
use tokio_postgres_rustls::MakeRustlsConnect;
|
||||
@@ -99,9 +101,8 @@ fn build_tls_connector(ca_pem: Option<&str>) -> anyhow::Result<MakeRustlsConnect
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
if let Some(ca_pem) = ca_pem.filter(|value| !value.trim().is_empty()) {
|
||||
let normalized = ca_pem.replace("\\n", "\n");
|
||||
let mut reader = Cursor::new(normalized.as_bytes());
|
||||
let mut roots = RootCertStore::empty();
|
||||
for cert in rustls_pemfile::certs(&mut reader) {
|
||||
for cert in CertificateDer::pem_slice_iter(normalized.as_bytes()) {
|
||||
roots.add(cert.context("failed to parse FLUXER_POSTGRES_SSL_CA certificate")?)?;
|
||||
}
|
||||
if roots.is_empty() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user