Compare commits

..
Author SHA1 Message Date
Renovate Bot d7843051ac chore(deps): update node-patch-updates to v2.0.19 2026-08-06 05:03:37 +00:00
18 changed files with 165 additions and 402 deletions
-1
View File
@@ -1 +0,0 @@
Preserve all users' read receipts when batching room receipt updates.
-1
View File
@@ -1 +0,0 @@
Updated admin command names (list-users -> list, list-rooms -> list, and reload-mods -> reload), old names are now aliases. Contributed by @PerformativeJade
+2 -21
View File
@@ -455,28 +455,9 @@
#
#sender_idle_timeout = 180
# Federation sender retry backoff base (seconds).
# Federation sender transaction retry backoff limit (seconds).
#
# This period will be doubled for each failed federation request until
# either the remote server becomes healthy, or the value is clamped to
# `sender_retry_backoff_limit`.
#
#sender_retry_backoff_base = 60
# Federation sender retry backoff limit (seconds).
#
# Defaults to one week. Requests will never stop being retried if their
# backoff period exceeds this value, however the maximum amount of time
# between each request will instead be clamped at this value.
#
# The backoff period is reset if a successful request is made, or
# continuwuity receives a request from the server that is being backed off
# from.
#
# It is not recommended to lower this value below 48 hours or above
# 1 year.
#
#sender_retry_backoff_limit = 806400
#sender_retry_backoff_limit = 86400
# Appservice URL request connection timeout. Defaults to 35 seconds as
# generally appservices are hosted within the same network.
+1 -1
View File
@@ -4,7 +4,7 @@ # `!admin rooms`
Commands for managing rooms
## `!admin rooms list`
## `!admin rooms list-rooms`
List all rooms the server knows about
+1 -1
View File
@@ -36,7 +36,7 @@ ## `!admin server admin-notice`
Send a message to the admin room
## `!admin server reload`
## `!admin server reload-mods`
Hot-reload the server
+2 -2
View File
@@ -4,7 +4,7 @@ # `!admin users`
Commands for managing local users
## `!admin users create`
## `!admin users create-user`
Create a new user
@@ -92,7 +92,7 @@ ## `!admin users disable-login`
Disables login for the specified user without deactivating or locking their account. This prevents the user from obtaining new access tokens, but does not invalidate existing sessions.
## `!admin users list`
## `!admin users list-users`
List local users in the database
+110 -126
View File
@@ -16,21 +16,21 @@
}
},
"node_modules/@emnapi/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"@emnapi/wasi-threads": "1.2.3",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -39,9 +39,9 @@
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
"integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -125,13 +125,13 @@
}
},
"node_modules/@rsbuild/core": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.1.5.tgz",
"integrity": "sha512-7TW4U1SH7VxQZzSTIOzvwj5lo9uNTmGpsmTXFr4axQAE4giLDKP3kVUA1ZW4P3/Mz4QQJJyvZP29mVcb8kZCfg==",
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.1.10.tgz",
"integrity": "sha512-lwxC5w88U2AMv6aNwG3VH7+AV33N4JJQjD//egVWFsMbV+OE/bnfCsurSpX8s3lGyRYkJMwUVN+YXMbmmYZFfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/core": "~2.1.3",
"@rspack/core": "~2.1.8",
"@swc/helpers": "^0.5.23"
},
"bin": {
@@ -169,30 +169,30 @@
}
},
"node_modules/@rspack/binding": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.1.3.tgz",
"integrity": "sha512-4UGXJqUHmm36tWG1GgFZz3p8sQ5JuSgWI+gq1xPPoihS41uYJL3cXuJnirTeWsmVrusmYZTQhgU3tl3VYGcJYg==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.1.8.tgz",
"integrity": "sha512-tmAyHzDbPiy8V7HvQqtuPsbs6dPgwV0YjzW5XrPRV9gzf+Hdm7pvsZJKE1QKO9WV5RuvGYav98xIX6O+abZxzQ==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.1.3",
"@rspack/binding-darwin-x64": "2.1.3",
"@rspack/binding-linux-arm64-gnu": "2.1.3",
"@rspack/binding-linux-arm64-musl": "2.1.3",
"@rspack/binding-linux-riscv64-gnu": "2.1.3",
"@rspack/binding-linux-riscv64-musl": "2.1.3",
"@rspack/binding-linux-x64-gnu": "2.1.3",
"@rspack/binding-linux-x64-musl": "2.1.3",
"@rspack/binding-wasm32-wasi": "2.1.3",
"@rspack/binding-win32-arm64-msvc": "2.1.3",
"@rspack/binding-win32-ia32-msvc": "2.1.3",
"@rspack/binding-win32-x64-msvc": "2.1.3"
"@rspack/binding-darwin-arm64": "2.1.8",
"@rspack/binding-darwin-x64": "2.1.8",
"@rspack/binding-linux-arm64-gnu": "2.1.8",
"@rspack/binding-linux-arm64-musl": "2.1.8",
"@rspack/binding-linux-riscv64-gnu": "2.1.8",
"@rspack/binding-linux-riscv64-musl": "2.1.8",
"@rspack/binding-linux-x64-gnu": "2.1.8",
"@rspack/binding-linux-x64-musl": "2.1.8",
"@rspack/binding-wasm32-wasi": "2.1.8",
"@rspack/binding-win32-arm64-msvc": "2.1.8",
"@rspack/binding-win32-ia32-msvc": "2.1.8",
"@rspack/binding-win32-x64-msvc": "2.1.8"
}
},
"node_modules/@rspack/binding-darwin-arm64": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.1.3.tgz",
"integrity": "sha512-oOGI0RSL89Ehu9T22rugmfUY9OC2eBqLMeWRYsu7bhlUrjoXeVfGBBSEXCse666BQ1sAiM8hD/k7nqVria/okQ==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.1.8.tgz",
"integrity": "sha512-kia+eWtyWPvR4ntg1bWYoVU8nLPbUg2fG3zgBEocsTcsh5ZENSiEPxEKymDgMyIMONUqj611E0775cdUBoNmqw==",
"cpu": [
"arm64"
],
@@ -204,9 +204,9 @@
]
},
"node_modules/@rspack/binding-darwin-x64": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.1.3.tgz",
"integrity": "sha512-sVqWXNiFTMXAyN362y6IA+eJc8LXZKfHdhEJ/zDuMmRp+u2IvhgaF8tk3vX/OmeB9jydVjySijuiqk8No/FoCA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.1.8.tgz",
"integrity": "sha512-08pBkFhlD3Y3Qzh94w/Fc3skaIE3e96kl2P14m8+tnYTcglpOfpA2OwS3iHt9fOqy0HjoAVe6/MW3cBgs5iabA==",
"cpu": [
"x64"
],
@@ -218,9 +218,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.1.3.tgz",
"integrity": "sha512-aCy9Zli/2Qf+Ee5otXfFQ6mhv5fEyn0wIoBVmouqtJoqOO21et6UTtJ+LHLsMDolwGLyHERAljeSFSmYX3/O5A==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.1.8.tgz",
"integrity": "sha512-KLniMc9GzhKpVqhPzaJo3KJwzdAllXVVqZIk/uL1QipXOxs57fgM4u7IexKPFVla0o/u1PQG/Ah2YLDmda24Ow==",
"cpu": [
"arm64"
],
@@ -235,9 +235,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-musl": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.1.3.tgz",
"integrity": "sha512-flIE7eluz0d21Fn28EVm3vPwoJooOSqtmjLFVSuOMcoCbwV9clfor195oIrAppp/W7dL/3XquFuVfrsa01Jy8Q==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.1.8.tgz",
"integrity": "sha512-yUKAxHNGnICtw5RnxFWu4dHtsz/tdt7rbeFcsINNVre9HcrRxf5XP+FbOGL/SMxd9oM9XCo10paU2WckTKwbEA==",
"cpu": [
"arm64"
],
@@ -252,9 +252,9 @@
]
},
"node_modules/@rspack/binding-linux-riscv64-gnu": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-2.1.3.tgz",
"integrity": "sha512-yojg8elye1nhsNeGncw0NrZ4pMGF7NVebR80CLg72TXyzfYwFJlFTdU5yUYb1Gy+JXIvrSCwzQt2QkyiEvmkfg==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-2.1.8.tgz",
"integrity": "sha512-gg4S1jaitwYPHR9HZ3zNGH1EK2GXINm66p4kEpOP1gbc+akyOouVF/dMcu9NGPlRg58FbEhVRZYKu7Z/zcpKHg==",
"cpu": [
"riscv64"
],
@@ -269,9 +269,9 @@
]
},
"node_modules/@rspack/binding-linux-riscv64-musl": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-2.1.3.tgz",
"integrity": "sha512-6PvbXb3FOK4X3S5QGvoSW/sqExmsvAoPnQ/YSFrXvTphkXFezA7wnobmGBHT8JQP31hcFRzJHIZSIOKktzSzzg==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-2.1.8.tgz",
"integrity": "sha512-b/aU5j1h368SLNyz5u+flqpZVhzSZ1UIslaj9sZJuAvqkGWv3xsjc/28/PTo/RYXCxd0FNVAxTxWHKvRiAAS8w==",
"cpu": [
"riscv64"
],
@@ -286,9 +286,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-gnu": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.1.3.tgz",
"integrity": "sha512-lMXjoGKf0SnviH596fmTszgtnXLHmWOoE90G8grG9MvKVa3pelRmfps5ewZL9s8ENf3NXRfOxIhIf/M9as6MqA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.1.8.tgz",
"integrity": "sha512-EyegohSx0BJRqieCg9f/caCqFARRWkqI5hwJt6k530MoOTLeq8I3vsbeg24/2MktwIC1dmJi8bl0+WhPKQs4eQ==",
"cpu": [
"x64"
],
@@ -303,9 +303,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-musl": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.1.3.tgz",
"integrity": "sha512-0esT35v7pW2ZsJTMc/zDUHwpNXlPqyUCyuiLJvrAAUcdENrgOVe4DmFrgVJ2hwqI4GjeN1VBnGRJ8c+edAHH5w==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.1.8.tgz",
"integrity": "sha512-I6E+goN+UQ297q4r1qdbiAyNCI3t0+a5Y0xDIAPOZfRDRxDTnH/LF8/y65gjsJoKRKyn7zxRC0T/NURTkRNQ9A==",
"cpu": [
"x64"
],
@@ -320,9 +320,9 @@
]
},
"node_modules/@rspack/binding-wasm32-wasi": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.1.3.tgz",
"integrity": "sha512-UsrDjD59UEP0mhfN/Z+uTc3vLgiUvIr+mn92WC1sbQi9gtZohTYvaQYFhWuMkBqsACGcmZp704JAbbSrVrVYCA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.1.8.tgz",
"integrity": "sha512-om7GAKWAU3lcSvbCon2m7mzw8v9OTrO2LW2MZ1lGe/uVJJmwGGkl9HVoXFyWFLrN6YVFyx8iP+AkN4owDWB9Cw==",
"cpu": [
"wasm32"
],
@@ -330,15 +330,15 @@
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "1.11.1",
"@emnapi/runtime": "1.11.1",
"@emnapi/core": "1.11.3",
"@emnapi/runtime": "1.11.3",
"@napi-rs/wasm-runtime": "1.1.6"
}
},
"node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.1.3.tgz",
"integrity": "sha512-42RS/SwKBTkNvXIPZXqTn0yEFN8zwGRfRe/ly0GDvPp/KxpLMFWxJmLxgtoLompU+0UCGvV4KpcBENt3oWs6BA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.1.8.tgz",
"integrity": "sha512-WDnsP/SUb9zbxyGX9XjPw5AXrX86u5oidn0MDdfJduOOqdCSpHwmRjlQ8NUJhbBq9WqVJMFlcab7NwZVWX/yyg==",
"cpu": [
"arm64"
],
@@ -350,9 +350,9 @@
]
},
"node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.1.3.tgz",
"integrity": "sha512-thk43H1JHHbetNF3txcsGXeOwZi2m6Luf/uVUqNORXjxr5VV8woHAgaAx4QXVZRg70z1VDjd8mBWnHBnKI0FGA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.1.8.tgz",
"integrity": "sha512-QiMQMPNDiY3dhhaIdaFPzcPDC06cEYkNY89ea+EmDvNVgZq6V+2mFS/WnzZVMeEbGAYJCjsv/ABhhLT1hlYMvg==",
"cpu": [
"ia32"
],
@@ -364,9 +364,9 @@
]
},
"node_modules/@rspack/binding-win32-x64-msvc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.1.3.tgz",
"integrity": "sha512-ObaUcj+BHo/aBL1weyM3orApaHyAR5Phr3YNEpQEifxjZbNKM1iO5X5prN4OqEv3H+7o5e/Wh9Fy3N7Vvd+psA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.1.8.tgz",
"integrity": "sha512-b7sA5eB64vo2mbsuc//MOYzVLeCKHPn0dfP/GmNEoHdWbhRgZ/orZLWurYMQj04ELTLW6YCJEy59g5KRzNYHfw==",
"cpu": [
"x64"
],
@@ -378,13 +378,13 @@
]
},
"node_modules/@rspack/core": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.1.3.tgz",
"integrity": "sha512-1iGnxLrP+iyY0ZSjLZeQTaxrISpQz4yLyqJPmaF/l4uw27/dBcrloszAeLOJQ9jiv7EJtU0T5zB+LOFPpivIxA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.1.8.tgz",
"integrity": "sha512-na1kyA6Mj8/LWw9O3A8NsrG9rNKN3Iq2WiXrEuIwsU5r/Nl/evm3hO7bWKHxgsRyydI6W7okwx3MXgf8rzel6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/binding": "2.1.3"
"@rspack/binding": "2.1.8"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
@@ -419,20 +419,21 @@
}
},
"node_modules/@rspress/core": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.18.tgz",
"integrity": "sha512-DBpsr/6XAItQkZPL5FSjthLGuzdG9ks/7EOxkqaaXp93uAT54LO0PD5Dkoy6ydup1PnvkpHyiHpUyDcnHHTVhg==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.19.tgz",
"integrity": "sha512-m0KfQNNXxjP975652nIOwUTqrvbJapRp4NpnoQMc5heawGhkC4+F7zTHvxY+huvzGYnKtL2fMMTrgWhEjKkrug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@rsbuild/core": "^2.1.5",
"@rsbuild/core": "^2.1.8",
"@rsbuild/plugin-react": "~2.1.0",
"@rspress/shared": "2.0.18",
"@rspress/shared": "2.0.19",
"@shikijs/rehype": "^4.2.0",
"@types/unist": "^3.0.3",
"@unhead/react": "^2.1.15",
"@types/mdast": "^4.0.4",
"@types/react": "^19.2.17",
"@unhead/react": "^2.1.16",
"body-scroll-lock": "4.0.0-beta.0",
"clsx": "2.1.1",
"copy-to-clipboard": "^3.3.3",
@@ -443,10 +444,9 @@
"mdast-util-mdxjs-esm": "^2.0.1",
"medium-zoom": "1.1.0",
"nprogress": "^0.2.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-lazy-with-preload": "^2.2.1",
"react-reconciler": "0.33.0",
"react-render-to-markdown": "19.1.0",
"react-router-dom": "^7.18.1",
"rehype-external-links": "^3.0.0",
@@ -460,9 +460,9 @@
"scroll-into-view-if-needed": "^3.1.0",
"shiki": "^4.2.0",
"unified": "^11.0.5",
"unist-util-remove": "^4.0.0",
"unist-util-visit": "^5.1.0",
"unist-util-visit-children": "^3.0.0"
"unist-util-visit-children": "^3.0.0",
"vfile": "^6.0.3"
},
"bin": {
"rspress": "bin/rspress.js"
@@ -472,9 +472,9 @@
}
},
"node_modules/@rspress/plugin-client-redirects": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.18.tgz",
"integrity": "sha512-q70ufN0S125kl0QlucO7JuiZ0WJaxFOtPPoGu7eWwam9fb+i4YsWnOWtAKSKGH4JP5+897mgpDPP+dmfl9DPCw==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.19.tgz",
"integrity": "sha512-BUwgEyj8vCkU58cV3Gp97aTSnfGJm7aXh40K0Dot1BqsCQ8ekdsMuaLXrICROdP9/ctd4oALtLBkOE+PKTa4Sg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -485,9 +485,9 @@
}
},
"node_modules/@rspress/plugin-sitemap": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.18.tgz",
"integrity": "sha512-XfaXzZx+ASN9OMTc0fOvW8QmbmyCyDWkO8xJj4XF4E0YJtsu7j/65+v8/KXXdwpywOfUIQ6M94/meRTy2urNmQ==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.19.tgz",
"integrity": "sha512-9ydrpj9hmmXWKWKIXUqJ1UrfxSvfxhHZRBZLb5OkTlOuQBNAg6BpsrW1WHoJqJr7p+3zqlz7S/9HTzSA3+EYcA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -498,14 +498,16 @@
}
},
"node_modules/@rspress/shared": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.18.tgz",
"integrity": "sha512-GJswqJQCPSxvBt5r+gJzz8Em8EEK/sOmCQjTpemTvldvxr1Lva85BGVnSQZOgofnM0nrjt18kn4mmnuuSstbpA==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.19.tgz",
"integrity": "sha512-INrETllWuR49lksqCz+xeLSO6rKtHA+5Ix2YQWcP9nerCuTSyGYwH8eBC0HrIacJkGHxB8wkkbV99tyzGtY8Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rsbuild/core": "^2.1.5",
"@rsbuild/core": "^2.1.8",
"@shikijs/rehype": "^4.2.0",
"@types/react": "^19.2.17",
"mdast-util-mdx-jsx": "^3.2.0",
"unified": "^11.0.5"
}
},
@@ -723,7 +725,6 @@
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1083,13 +1084,13 @@
"license": "ISC"
},
"node_modules/@unhead/react": {
"version": "2.1.15",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.15.tgz",
"integrity": "sha512-5hfAaZ3XJq9JkspRzZdSPsMrXXA8v/SKiEOxZcN9L40o44byF/50bcQuOLgSSCAx8802mI5VG32KZXWTtsLu9Q==",
"version": "2.1.17",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.17.tgz",
"integrity": "sha512-KmcYksDjlLozL0fxUjIDwH/0k6+Lg2HWHLUPlkRW8Gl08hdB1AbLB5iU9QxFvkaT8p0Q4dFEqZWnz3g6RMeiRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"unhead": "2.1.15"
"unhead": "2.1.17"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
@@ -1272,8 +1273,7 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
@@ -3176,9 +3176,9 @@
}
},
"node_modules/react": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3186,16 +3186,16 @@
}
},
"node_modules/react-dom": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.7"
"react": "^19.2.8"
}
},
"node_modules/react-lazy-with-preload": {
@@ -3736,9 +3736,9 @@
}
},
"node_modules/unhead": {
"version": "2.1.15",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.15.tgz",
"integrity": "sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA==",
"version": "2.1.17",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.17.tgz",
"integrity": "sha512-HLMKXOszRhAPBrr6VlqCeVeJq2kbC4kXwzGLEZvvojPLWNYTJw22xG7Bfwhsvs31+IBet3Wl8ADg9dwYdyphfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3810,22 +3810,6 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-remove": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz",
"integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-is": "^6.0.0",
"unist-util-visit-parents": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-stringify-position": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+1 -1
View File
@@ -18,7 +18,7 @@
#[derive(Debug, Subcommand)]
pub enum RoomCommand {
/// List all rooms the server knows about
#[command(name = "list", alias = "list-rooms")]
#[clap(alias = "list")]
ListRooms {
page: Option<usize>,
+1 -1
View File
@@ -40,7 +40,7 @@ pub enum ServerCommand {
},
/// Hot-reload the server
#[command(name = "reload", alias = "reload-mods")]
#[clap(alias = "reload")]
ReloadMods,
#[cfg(unix)]
+2 -2
View File
@@ -10,7 +10,7 @@
#[derive(Debug, Subcommand)]
pub enum UserCommand {
/// Create a new user
#[command(name = "create", alias = "create-user")]
#[clap(alias = "create")]
CreateUser {
/// Username of the new user
username: String,
@@ -163,7 +163,7 @@ pub enum UserCommand {
},
/// List local users in the database
#[command(name = "list", alias = "list-users")]
#[clap(alias = "list")]
ListUsers,
/// Lists all the rooms (local and remote) that the specified user is
-3
View File
@@ -149,9 +149,6 @@ async fn verify<B: AsRef<[u8]> + Sync>(
)));
}
// Ping the server as healthy
services.federation.mark_healthy(&output.origin);
Ok(output.origin)
},
| Err(err) =>
+2 -25
View File
@@ -573,30 +573,9 @@ pub struct Config {
#[serde(default = "default_sender_idle_timeout")]
pub sender_idle_timeout: u64,
/// Federation sender retry backoff base (seconds).
/// Federation sender transaction retry backoff limit (seconds).
///
/// This period will be doubled for each failed federation request until
/// either the remote server becomes healthy, or the value is clamped to
/// `sender_retry_backoff_limit`.
///
/// default: 60
#[serde(default = "default_sender_retry_backoff_base")]
pub sender_retry_backoff_base: u64,
/// Federation sender retry backoff limit (seconds).
///
/// Defaults to one week. Requests will never stop being retried if their
/// backoff period exceeds this value, however the maximum amount of time
/// between each request will instead be clamped at this value.
///
/// The backoff period is reset if a successful request is made, or
/// continuwuity receives a request from the server that is being backed off
/// from.
///
/// It is not recommended to lower this value below 48 hours or above
/// 1 year.
///
/// default: 806400
/// default: 86400
#[serde(default = "default_sender_retry_backoff_limit")]
pub sender_retry_backoff_limit: u64,
@@ -2833,8 +2812,6 @@ fn default_sender_timeout() -> u64 { 180 }
fn default_sender_idle_timeout() -> u64 { 180 }
fn default_sender_retry_backoff_base() -> u64 { 60 }
fn default_sender_retry_backoff_limit() -> u64 { 86400 }
fn default_appservice_timeout() -> u64 { 35 }
+2 -2
View File
@@ -38,8 +38,8 @@
string::{str_from_bytes, string_from_bytes},
sys::compute::available_parallelism,
time::{
exponential_backoff::should_continue_backoff, now_millis as millis_since_unix_epoch,
timepoint_ago, timepoint_from_now,
exponential_backoff::{continue_exponential_backoff, continue_exponential_backoff_secs},
now_millis as millis_since_unix_epoch, timepoint_ago, timepoint_from_now,
},
};
+26 -11
View File
@@ -1,23 +1,38 @@
use std::time::Duration;
use std::{cmp, time::Duration};
/// Returns false if the backoff interval has expired based on the inputs,
/// meaning the operation should be retried.
/// Returns false if the exponential backoff has expired based on the inputs
#[inline]
#[must_use]
pub fn should_continue_backoff(
pub fn continue_exponential_backoff_secs(
min: u64,
max: u64,
elapsed: Duration,
tries: u32,
) -> bool {
let min = Duration::from_secs(min);
let max = Duration::from_secs(max);
continue_exponential_backoff(min, max, elapsed, tries)
}
/// Returns false if the exponential backoff has expired based on the inputs
#[inline]
#[must_use]
pub fn continue_exponential_backoff(
min: Duration,
max: Duration,
elapsed: Duration,
tries: u32,
) -> bool {
elapsed < next_interval(min, max, tries)
let min = min.saturating_mul(tries).saturating_mul(tries);
let min = cmp::min(min, max);
elapsed < min
}
/// Determines the interval that should be waited before retrying the operation
/// using the algorithm: `(min * retries).min(max)`.
/// Determines the minimum number of backoff seconds
#[must_use]
#[inline]
pub fn next_interval(min: Duration, max: Duration, retries: u32) -> Duration {
// TODO(nex): jitter?
min.saturating_mul(retries).min(max)
pub fn min_exp_backoff_duration(min: u64, max: u64, retries: u32) -> Duration {
let min = Duration::from_secs(min)
.saturating_mul(retries)
.saturating_mul(retries);
Duration::from_secs(max).min(min)
}
+2 -10
View File
@@ -131,8 +131,6 @@ pub async fn execute_on<'i, T, PathBuilderInput>(
))));
}
self.ensure_remote_is_healthy(dest)?;
let actual = self
.services
.client
@@ -185,13 +183,8 @@ async fn perform<T>(
| Ok(response) =>
self.handle_response::<T>(dest, actual, &method, &url, response)
.await,
| Err(error) => {
if error.is_connect() {
debug_info!("{dest} is unhealthy due to a connect error");
self.hit_unhealthy(dest.to_owned());
}
Err(handle_error(actual, &method, &url, error).expect_err("always returns error"))
},
| Err(error) =>
Err(handle_error(actual, &method, &url, error).expect_err("always returns error")),
}
}
@@ -244,7 +237,6 @@ async fn handle_response<T>(
parts,
body.as_ref(),
))
.inspect(|_| self.mark_healthy(dest))
.map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
}
}
+2 -110
View File
@@ -1,25 +1,13 @@
mod execute;
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc, time::Duration};
use assign::assign;
use async_trait::async_trait;
use conduwuit::{
Error, Result, Server, SyncRwLock, debug,
utils::{math::Expected, millis_since_unix_epoch, time::exponential_backoff::next_interval},
};
use conduwuit::{Result, Server};
pub(crate) use execute::FederationPathBuilderInput;
use http::StatusCode;
use ruma::{
OwnedServerName, ServerName,
api::error::{ErrorKind, LimitExceededErrorData, RetryAfter},
};
use crate::{Dep, client, moderation, server_keys};
pub struct Service {
services: Services,
remote_health: SyncRwLock<HashMap<OwnedServerName, (u32, u64)>>,
}
struct Services {
@@ -29,7 +17,6 @@ struct Services {
moderation: Dep<moderation::Service>,
}
#[async_trait]
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
@@ -39,103 +26,8 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
server_keys: args.depend::<server_keys::Service>("server_keys"),
moderation: args.depend::<moderation::Service>("moderation"),
},
remote_health: SyncRwLock::new(HashMap::new()),
}))
}
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
async fn clear_cache(&self) {
let mut map = self.remote_health.write();
map.clear();
}
}
impl Service {
/// Checks if a remote is "healthy". "Healthy" is defined by either:
///
/// * The remote has not been marked as having a failed request, OR
/// * The next retry timestamp is in the past
pub fn is_healthy(&self, server_name: &ServerName) -> bool {
let map = self.remote_health.read();
let unix_now = millis_since_unix_epoch();
if let Some((_, next_retry)) = map.get(server_name) {
unix_now >= *next_retry
} else {
true
}
}
/// Returns how long the server should wait before attempting to contact the
/// remote again.
pub fn retry_after(&self, server_name: &ServerName) -> Option<Duration> {
let map = self.remote_health.read();
let unix_now = millis_since_unix_epoch();
map.get(server_name)
.map(|(_, next_retry)| Duration::from_millis((*next_retry).expected_sub(unix_now)))
}
/// Marks or updates a remote's health status as unhealthy. If the remote is
/// not already marked as unhealthy, a new entry is created. Otherwise, the
/// retry count is incremented and
pub fn hit_unhealthy(&self, server_name: OwnedServerName) {
let unix_now = millis_since_unix_epoch();
let mut map = self.remote_health.write();
let sn2 = server_name.clone(); // for logging since map.entry() moves
let (retries, next_retry) = map.entry(server_name).or_default();
if *next_retry > unix_now {
// Don't update the retry marker if we are already in a backoff
// period. This prevents the backoff skyrocketing if multiple
// concurrent or closely-related requests fail and consequently try
// to mark as offline.
return;
}
let min = Duration::from_secs(self.services.server.config.sender_retry_backoff_base);
let max = Duration::from_secs(self.services.server.config.sender_retry_backoff_limit);
*retries = retries.saturating_add(1);
*next_retry = unix_now.saturating_add(
u64::try_from(next_interval(min, max, *retries).as_millis())
.expect("backoff milliseconds should not exceed u64::MAX"),
);
debug!(
"{} is (now) unhealthy ({} retries, blocked until: {})",
sn2, *retries, *next_retry
);
}
/// Marks a server as "healthy" by removing it from the health map.
///
/// TODO: flush senders too
pub fn mark_healthy(&self, server_name: &ServerName) {
// TODO: We need to make sure the sender flush DOESN'T trigger if this is called
// by the senders themselves.
let mut map = self.remote_health.write();
if map.remove(server_name).is_some() {
debug!("{} is now healthy", server_name);
}
}
/// Returns a rate-limited error if the remote is unhealthy.
fn ensure_remote_is_healthy(&self, server_name: &ServerName) -> Result<()> {
if self.is_healthy(server_name) {
Ok(())
} else {
let retry_after = self
.retry_after(server_name)
.expect("remote is unhealthy and must have an accompanying retry timestamp");
Err(Error::Request(
ErrorKind::LimitExceeded(assign!(LimitExceededErrorData::new(), {
retry_after: Some(RetryAfter::Delay(retry_after)),
})),
format!(
"Remote server is currently unhealthy (not retrying for another {} seconds)",
retry_after.as_secs()
)
.into(),
StatusCode::TOO_MANY_REQUESTS,
))
}
}
}
+3 -52
View File
@@ -152,21 +152,15 @@ pub fn pack_receipts<I>(receipts: I) -> Raw<SyncEphemeralRoomEvent<ReceiptEventC
where
I: Iterator<Item = Raw<AnySyncEphemeralRoomEvent>>,
{
let mut json: BTreeMap<OwnedEventId, Receipts> = BTreeMap::new();
let mut json = BTreeMap::new();
for value in receipts {
let receipt = serde_json::from_str::<SyncEphemeralRoomEvent<ReceiptEventContent>>(
value.json().get(),
);
match receipt {
| Ok(value) =>
for (event, receipt_types) in value.content {
let event_receipts = json.entry(event).or_default();
for (receipt_type, users) in receipt_types {
event_receipts
.entry(receipt_type)
.or_default()
.extend(users);
}
for (event, receipt) in value.content {
json.insert(event, receipt);
},
| _ => {
debug!("failed to parse receipt: {:?}", receipt);
@@ -181,46 +175,3 @@ pub fn pack_receipts<I>(receipts: I) -> Raw<SyncEphemeralRoomEvent<ReceiptEventC
.expect("received valid json"),
)
}
#[cfg(test)]
mod tests {
use ruma::{
events::receipt::{Receipt, ReceiptType},
user_id,
};
use super::*;
fn receipt(event_id: OwnedEventId, user_id: OwnedUserId) -> Raw<AnySyncEphemeralRoomEvent> {
let content = ReceiptEventContent::from_iter([(
event_id,
BTreeMap::from([(
ReceiptType::Read,
BTreeMap::from([(user_id, Receipt::default())]),
)]),
)]);
Raw::from_json(
serde_json::value::to_raw_value(&SyncEphemeralRoomEvent::new(content))
.expect("receipt event serializes"),
)
}
#[test]
fn pack_receipts_keeps_users_reading_the_same_event() {
let event_id: OwnedEventId = "$event:example.org".try_into().expect("valid event ID");
let packed = pack_receipts(
[
receipt(event_id.clone(), user_id!("@alice:example.org").to_owned()),
receipt(event_id.clone(), user_id!("@bob:example.org").to_owned()),
]
.into_iter(),
);
let packed: SyncEphemeralRoomEvent<ReceiptEventContent> =
serde_json::from_str(packed.json().get()).expect("packed receipt is valid");
let users = &packed.content[&event_id][&ReceiptType::Read];
assert_eq!(users.len(), 2);
assert!(users.contains_key(user_id!("@alice:example.org")));
assert!(users.contains_key(user_id!("@bob:example.org")));
}
}
+8 -32
View File
@@ -10,15 +10,14 @@
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use conduwuit::{
debug_info, debug_warn, info,
utils::{should_continue_backoff, time::exponential_backoff::next_interval},
debug_info, debug_warn, info, utils::time::exponential_backoff::min_exp_backoff_duration,
};
use conduwuit_core::{
Error, Event, Result, at, debug, err, error,
matrix::pdu::sticky,
result::LogErr,
utils::{
ReadyExt, calculate_hash,
ReadyExt, calculate_hash, continue_exponential_backoff_secs,
future::TryExtExt,
stream::{BroadbandExt, IterStream, WidebandExt},
},
@@ -143,15 +142,7 @@ async fn handle_response<'a>(
) {
match response {
| Ok(dest) => self.handle_response_ok(&dest, futures, statuses).await,
| Err((dest, e)) => {
if e.status_code().is_server_error()
&& let Destination::Federation(dest) = &dest
{
debug!("{dest} is now unhealthy due to server error response: {e:?}");
self.services.federation.hit_unhealthy(dest.clone());
}
Self::handle_response_err(dest, statuses, &e);
},
| Err((dest, e)) => Self::handle_response_err(dest, statuses, &e),
}
}
@@ -180,9 +171,6 @@ async fn handle_response_ok<'a>(
) {
let _cork = self.db.db.cork();
self.db.delete_all_active_requests_for(dest).await;
if let Destination::Federation(server_name) = dest {
self.services.federation.mark_healthy(server_name);
}
// Find events that have been added since starting the last request
let new_events = self
@@ -394,28 +382,18 @@ fn should_attempt_send(
dest: &Destination,
statuses: &mut CurTransactionStatus,
) -> Result<(bool, bool)> {
if let Destination::Federation(server_name) = dest {
let status = statuses
.entry(dest.clone())
.or_insert(TransactionStatus::Running);
let retry = matches!(status, TransactionStatus::Failed(_, _));
return match self.services.federation.retry_after(server_name) {
| None => Ok((true, retry)),
| Some(t) => Ok((t.as_millis() > 0, retry)),
};
}
let (mut allow, mut retry) = (true, false);
statuses
.entry(dest.clone())
.and_modify(|e| match e {
| TransactionStatus::Failed(tries, time) => {
let min = Duration::from_secs(self.server.config.sender_retry_backoff_base);
let max = Duration::from_secs(self.server.config.sender_retry_backoff_limit);
if should_continue_backoff(min, max, time.elapsed(), *tries)
// Fail if a request has failed recently (exponential backoff)
let min = self.server.config.sender_timeout;
let max = self.server.config.sender_retry_backoff_limit;
if continue_exponential_backoff_secs(min, max, time.elapsed(), *tries)
&& !matches!(dest, Destination::Appservice(_))
{
let retry_after = next_interval(min, max, *tries);
let retry_after = min_exp_backoff_duration(min, max, *tries);
debug_warn!("Not retrying destination for another {retry_after:?}");
allow = false;
} else {
@@ -468,8 +446,6 @@ async fn select_edus(&self, server_name: &ServerName) -> Result<(EduVec, u64)> {
events.extend(presence.into_iter().flatten());
events.extend(receipts.into_iter().flatten());
// TODO(nex): some EDUs like typing need flattening
Ok((events, max_edu_count.load(Ordering::Acquire)))
}