Compare commits
4 Commits
60c2c718eb
...
3bd4815ee2
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bd4815ee2 | |||
| 2aaa327853 | |||
| 806bd8ae6a | |||
| 52abbc5643 |
@@ -20,7 +20,7 @@ function RDB_OpBridgeStatus.register()
|
|||||||
local now = getTimestampMs()
|
local now = getTimestampMs()
|
||||||
|
|
||||||
return true, {
|
return true, {
|
||||||
version = RDB_Constants.PROTOCOL_VERSION,
|
version = RDB_Config.get("ProtocolVersion"),
|
||||||
bootedAt = bootedAt,
|
bootedAt = bootedAt,
|
||||||
uptimeSeconds = (now - bootedAt) / 1000,
|
uptimeSeconds = (now - bootedAt) / 1000,
|
||||||
paused = isPaused(),
|
paused = isPaused(),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function RDB_Bootstrap.getBootTimestamp()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function runSelfTests()
|
local function runSelfTests()
|
||||||
if not RDB_Constants.DEBUG then
|
if not RDB_Config.get("DebugSelfTest") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -45,11 +45,12 @@ end
|
|||||||
|
|
||||||
local function registerBaseOptions()
|
local function registerBaseOptions()
|
||||||
local OPT = RDB_Constants.OPT
|
local OPT = RDB_Constants.OPT
|
||||||
RDB_OptionsRegistry.register(OPT.PROTOCOL_VERSION, "{}", RDB_Constants.OPTION_MAX_LENGTH)
|
local optionMaxLength = RDB_Config.get("OptionMaxLength")
|
||||||
RDB_OptionsRegistry.register(OPT.WORLD_STATS, "{}", RDB_Constants.OPTION_MAX_LENGTH)
|
RDB_OptionsRegistry.register(OPT.PROTOCOL_VERSION, "{}", optionMaxLength)
|
||||||
RDB_OptionsRegistry.register(OPT.REQUEST, "{}", RDB_Constants.OPTION_MAX_LENGTH)
|
RDB_OptionsRegistry.register(OPT.WORLD_STATS, "{}", optionMaxLength)
|
||||||
RDB_OptionsRegistry.register(OPT.RESPONSE, "{}", RDB_Constants.OPTION_MAX_LENGTH)
|
RDB_OptionsRegistry.register(OPT.REQUEST, "{}", optionMaxLength)
|
||||||
RDB_OptionsRegistry.register(OPT.LAST_RESPONSE, "0", RDB_Constants.OPTION_MAX_LENGTH)
|
RDB_OptionsRegistry.register(OPT.RESPONSE, "{}", optionMaxLength)
|
||||||
|
RDB_OptionsRegistry.register(OPT.LAST_RESPONSE, "0", optionMaxLength)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function registerOps()
|
local function registerOps()
|
||||||
@@ -63,7 +64,7 @@ end
|
|||||||
|
|
||||||
local function publishProtocolVersion()
|
local function publishProtocolVersion()
|
||||||
local json = RDB_Json.encode({
|
local json = RDB_Json.encode({
|
||||||
version = RDB_Constants.PROTOCOL_VERSION,
|
version = RDB_Config.get("ProtocolVersion"),
|
||||||
ops = RDB_OpRegistry.listNames(),
|
ops = RDB_OpRegistry.listNames(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -75,7 +76,6 @@ local function onServerStarted()
|
|||||||
bootTimestamp = getTimestampMs()
|
bootTimestamp = getTimestampMs()
|
||||||
end
|
end
|
||||||
runSelfTests()
|
runSelfTests()
|
||||||
RDB_Config.init()
|
|
||||||
registerBaseOptions()
|
registerBaseOptions()
|
||||||
registerOps()
|
registerOps()
|
||||||
publishProtocolVersion()
|
publishProtocolVersion()
|
||||||
|
|||||||
@@ -1,58 +1,45 @@
|
|||||||
---@diagnostic disable: need-check-nil
|
---@diagnostic disable: need-check-nil
|
||||||
|
|
||||||
-- ModData-backed bridge configuration: rate limits, payload cap, sensitive
|
-- Facade over ALL scalar constants/settings -- the single point of access,
|
||||||
-- field policy. Defaults are baked in and only written to the ModData table
|
-- so no other file should index RDB_Constants directly for a scalar value.
|
||||||
-- the first time it's created.
|
-- Sandbox-tunable values (rate limits, poll cadence, idempotency TTL, debug
|
||||||
|
-- self-tests) live in
|
||||||
|
-- Contents/mods/RconDataBridge/common/media/sandbox-options.txt and are read
|
||||||
|
-- live from SandboxVars.RconDataBridge.*. Anything NOT sandbox-tunable (e.g.
|
||||||
|
-- MaxPayloadBytes/OptionMaxLength, sized against the fixed TextServerOption
|
||||||
|
-- capacity -- base64 expansion of the payload must still fit, so an
|
||||||
|
-- admin-set value with no matching engine-side capacity change could
|
||||||
|
-- silently truncate a request ServerOptions can't hold; or ProtocolVersion/
|
||||||
|
-- PlayerStatsPrefix, protocol identifiers, not settings) instead falls back
|
||||||
|
-- to a same-named RDB_Constants field. Adding either kind of value never
|
||||||
|
-- needs a new branch here -- just a sandbox-options.txt entry or a
|
||||||
|
-- RDB_Constants field. (Unit conversions, like IdempotencyTtlSeconds ->
|
||||||
|
-- milliseconds, belong in the one caller that needs the converted unit, not
|
||||||
|
-- here -- see RDB_Idempotency.)
|
||||||
|
|
||||||
require("RconDataBridge.RDB_Constants")
|
require("RconDataBridge.RDB_Constants")
|
||||||
|
|
||||||
RDB_Config = {}
|
RDB_Config = {}
|
||||||
|
|
||||||
local TAG = "RconDataBridge_Config"
|
-- PascalCase (sandbox-var convention, e.g. "MaxPayloadBytes") ->
|
||||||
|
-- SCREAMING_SNAKE_CASE (RDB_Constants convention, "MAX_PAYLOAD_BYTES").
|
||||||
local DEFAULTS = {
|
local function toConstantKey(key)
|
||||||
maxPayloadBytes = RDB_Constants.MAX_PAYLOAD_BYTES,
|
return (key:gsub("(%l)(%u)", "%1_%2")):upper()
|
||||||
rateLimitMaxRequests = 10,
|
|
||||||
rateLimitWindowSeconds = 10,
|
|
||||||
includeSensitiveFields = false,
|
|
||||||
}
|
|
||||||
|
|
||||||
local store
|
|
||||||
|
|
||||||
function RDB_Config.init()
|
|
||||||
store = ModData.getOrCreate(TAG)
|
|
||||||
for key, value in pairs(DEFAULTS) do
|
|
||||||
if store[key] == nil then
|
|
||||||
store[key] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function RDB_Config.get(key)
|
function RDB_Config.get(key)
|
||||||
if not store then
|
local v = SandboxVars.RconDataBridge[key]
|
||||||
RDB_Config.init()
|
if v ~= nil then
|
||||||
|
return v
|
||||||
end
|
end
|
||||||
|
|
||||||
return store[key]
|
return RDB_Constants[toConstantKey(key)]
|
||||||
end
|
end
|
||||||
|
|
||||||
function RDB_Config.set(key, value)
|
|
||||||
if not store then
|
|
||||||
RDB_Config.init()
|
|
||||||
end
|
|
||||||
|
|
||||||
store[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Shallow copy, so a caller can't mutate live config through the returned table.
|
|
||||||
function RDB_Config.getAll()
|
function RDB_Config.getAll()
|
||||||
if not store then
|
return {
|
||||||
RDB_Config.init()
|
maxPayloadBytes = RDB_Config.get("MaxPayloadBytes"),
|
||||||
end
|
rateLimitMaxRequests = RDB_Config.get("RateLimitMaxRequests"),
|
||||||
|
rateLimitWindowSeconds = RDB_Config.get("RateLimitWindowSeconds"),
|
||||||
local copy = {}
|
}
|
||||||
for key, value in pairs(store) do
|
|
||||||
copy[key] = value
|
|
||||||
end
|
|
||||||
return copy
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
-- the original response instead of re-executing the op.
|
-- the original response instead of re-executing the op.
|
||||||
-- Persisted via ModData so it survives restarts.
|
-- Persisted via ModData so it survives restarts.
|
||||||
--
|
--
|
||||||
-- Entries expire after RDB_Constants.IDEMPOTENCY_TTL_MS (60s by default):
|
-- Entries expire after RDB_Config.get("IdempotencyTtlSeconds") (60s by
|
||||||
-- a request id is only idempotent for retries within that window, not
|
-- default, admin-tunable via sandbox-options.txt): a request id is only
|
||||||
-- forever. Without a TTL, a client that reuses a fixed id for routine
|
-- idempotent for retries within that window, not forever. Without a TTL, a
|
||||||
-- polling would get the first-ever response back on every call, since a
|
-- client that reuses a fixed id for routine polling would get the
|
||||||
-- low-traffic server would rarely reach MAX_ENTRIES and trigger the
|
-- first-ever response back on every call, since a low-traffic server would
|
||||||
-- count-based eviction below.
|
-- rarely reach MAX_ENTRIES and trigger the count-based eviction below.
|
||||||
|
|
||||||
require("RconDataBridge.RDB_Constants")
|
require("RconDataBridge.RDB_Constants")
|
||||||
|
require("RconDataBridge.RDB_Config")
|
||||||
require("RconDataBridge.RDB_Log")
|
require("RconDataBridge.RDB_Log")
|
||||||
|
|
||||||
RDB_Idempotency = {}
|
RDB_Idempotency = {}
|
||||||
@@ -27,7 +28,8 @@ local function ensureStore()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function isExpired(entry)
|
local function isExpired(entry)
|
||||||
return getTimestampMs() - (entry.ts or 0) >= RDB_Constants.IDEMPOTENCY_TTL_MS
|
local ttlMs = RDB_Config.get("IdempotencyTtlSeconds") * 1000
|
||||||
|
return getTimestampMs() - (entry.ts or 0) >= ttlMs
|
||||||
end
|
end
|
||||||
|
|
||||||
function RDB_Idempotency.has(id)
|
function RDB_Idempotency.has(id)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ require("RconDataBridge.RDB_Constants")
|
|||||||
require("RconDataBridge.RDB_Log")
|
require("RconDataBridge.RDB_Log")
|
||||||
require("RconDataBridge.RDB_Json")
|
require("RconDataBridge.RDB_Json")
|
||||||
require("RconDataBridge.RDB_OptionsRegistry")
|
require("RconDataBridge.RDB_OptionsRegistry")
|
||||||
|
require("RconDataBridge.RDB_Config")
|
||||||
|
|
||||||
RDB_PlayerSnapshot = {}
|
RDB_PlayerSnapshot = {}
|
||||||
|
|
||||||
@@ -78,8 +79,8 @@ function RDB_PlayerSnapshot.publishStatsOption(id)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local optionName = RDB_Constants.PLAYER_STATS_PREFIX .. id:gsub("[^%w_]", "_")
|
local optionName = RDB_Config.get("PlayerStatsPrefix") .. id:gsub("[^%w_]", "_")
|
||||||
RDB_OptionsRegistry.register(optionName, "{}", RDB_Constants.OPTION_MAX_LENGTH)
|
RDB_OptionsRegistry.register(optionName, "{}", RDB_Config.get("OptionMaxLength"))
|
||||||
RDB_OptionsRegistry.set(optionName, RDB_Json.encode(snapshot))
|
RDB_OptionsRegistry.set(optionName, RDB_Json.encode(snapshot))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ local initialized = false
|
|||||||
|
|
||||||
local function onTick()
|
local function onTick()
|
||||||
tickCounter = tickCounter + 1
|
tickCounter = tickCounter + 1
|
||||||
if tickCounter < RDB_Constants.POLL_EVERY_TICKS then
|
if tickCounter < RDB_Config.get("PollEveryTicks") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ require("RconDataBridge.RDB_Json")
|
|||||||
require("RconDataBridge.RDB_Base64")
|
require("RconDataBridge.RDB_Base64")
|
||||||
require("RconDataBridge.RDB_Log")
|
require("RconDataBridge.RDB_Log")
|
||||||
require("RconDataBridge.RDB_OptionsRegistry")
|
require("RconDataBridge.RDB_OptionsRegistry")
|
||||||
|
require("RconDataBridge.RDB_Config")
|
||||||
require("RconDataBridge.RDB_Security")
|
require("RconDataBridge.RDB_Security")
|
||||||
require("RconDataBridge.RDB_Idempotency")
|
require("RconDataBridge.RDB_Idempotency")
|
||||||
require("RconDataBridge.RDB_AuditLog")
|
require("RconDataBridge.RDB_AuditLog")
|
||||||
@@ -19,7 +20,7 @@ local tickCounter = 0
|
|||||||
|
|
||||||
local function buildOkResponse(id, data)
|
local function buildOkResponse(id, data)
|
||||||
return {
|
return {
|
||||||
v = RDB_Constants.PROTOCOL_VERSION,
|
v = RDB_Config.get("ProtocolVersion"),
|
||||||
id = id,
|
id = id,
|
||||||
ok = true,
|
ok = true,
|
||||||
data = data or {}
|
data = data or {}
|
||||||
@@ -28,7 +29,7 @@ end
|
|||||||
|
|
||||||
local function buildErrorResponse(id, code, message)
|
local function buildErrorResponse(id, code, message)
|
||||||
return {
|
return {
|
||||||
v = RDB_Constants.PROTOCOL_VERSION,
|
v = RDB_Config.get("ProtocolVersion"),
|
||||||
id = id,
|
id = id,
|
||||||
ok = false,
|
ok = false,
|
||||||
error = { code = code, message = message },
|
error = { code = code, message = message },
|
||||||
@@ -142,7 +143,7 @@ end
|
|||||||
|
|
||||||
local function onTick()
|
local function onTick()
|
||||||
tickCounter = tickCounter + 1
|
tickCounter = tickCounter + 1
|
||||||
if tickCounter < RDB_Constants.POLL_EVERY_TICKS then
|
if tickCounter < RDB_Config.get("PollEveryTicks") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
tickCounter = 0
|
tickCounter = 0
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ require("RconDataBridge.RDB_Config")
|
|||||||
RDB_Security = {}
|
RDB_Security = {}
|
||||||
|
|
||||||
function RDB_Security.checkPayloadSize(raw)
|
function RDB_Security.checkPayloadSize(raw)
|
||||||
if #raw > RDB_Config.get("maxPayloadBytes") then
|
if #raw > RDB_Config.get("MaxPayloadBytes") then
|
||||||
return false, RDB_Constants.ERROR_CODES.PAYLOAD_TOO_LARGE, "Request payload exceeds size limit"
|
return false, RDB_Constants.ERROR_CODES.PAYLOAD_TOO_LARGE, "Request payload exceeds size limit"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ function RDB_Security.validateEnvelope(obj)
|
|||||||
if type(obj) ~= "table" then
|
if type(obj) ~= "table" then
|
||||||
return false, RDB_Constants.ERROR_CODES.SCHEMA_ERROR, "Request must be a JSON object"
|
return false, RDB_Constants.ERROR_CODES.SCHEMA_ERROR, "Request must be a JSON object"
|
||||||
end
|
end
|
||||||
if obj.v ~= RDB_Constants.PROTOCOL_VERSION then
|
if obj.v ~= RDB_Config.get("ProtocolVersion") then
|
||||||
return false, RDB_Constants.ERROR_CODES.SCHEMA_ERROR, "Unsupported or missing protocol version"
|
return false, RDB_Constants.ERROR_CODES.SCHEMA_ERROR, "Unsupported or missing protocol version"
|
||||||
end
|
end
|
||||||
if type(obj.id) ~= "string" or #obj.id == 0 or #obj.id > 128 then
|
if type(obj.id) ~= "string" or #obj.id == 0 or #obj.id > 128 then
|
||||||
@@ -46,7 +46,7 @@ local requestTimestamps = {}
|
|||||||
-- and getRateLimitStatus (which only observes).
|
-- and getRateLimitStatus (which only observes).
|
||||||
local function pruneAndCount()
|
local function pruneAndCount()
|
||||||
local now = getTimestampMs()
|
local now = getTimestampMs()
|
||||||
local windowMs = RDB_Config.get("rateLimitWindowSeconds") * 1000
|
local windowMs = RDB_Config.get("RateLimitWindowSeconds") * 1000
|
||||||
|
|
||||||
local kept = {}
|
local kept = {}
|
||||||
for _, ts in ipairs(requestTimestamps) do
|
for _, ts in ipairs(requestTimestamps) do
|
||||||
@@ -60,7 +60,7 @@ local function pruneAndCount()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function RDB_Security.checkRateLimit()
|
function RDB_Security.checkRateLimit()
|
||||||
local maxRequests = RDB_Config.get("rateLimitMaxRequests")
|
local maxRequests = RDB_Config.get("RateLimitMaxRequests")
|
||||||
|
|
||||||
if pruneAndCount() >= maxRequests then
|
if pruneAndCount() >= maxRequests then
|
||||||
return false, RDB_Constants.ERROR_CODES.RATE_LIMITED, "Too many requests, slow down"
|
return false, RDB_Constants.ERROR_CODES.RATE_LIMITED, "Too many requests, slow down"
|
||||||
@@ -73,7 +73,7 @@ end
|
|||||||
function RDB_Security.getRateLimitStatus()
|
function RDB_Security.getRateLimitStatus()
|
||||||
return {
|
return {
|
||||||
currentWindowRequests = pruneAndCount(),
|
currentWindowRequests = pruneAndCount(),
|
||||||
maxRequests = RDB_Config.get("rateLimitMaxRequests"),
|
maxRequests = RDB_Config.get("RateLimitMaxRequests"),
|
||||||
windowSeconds = RDB_Config.get("rateLimitWindowSeconds"),
|
windowSeconds = RDB_Config.get("RateLimitWindowSeconds"),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ local function refresh()
|
|||||||
minute = gt:getMinutes(),
|
minute = gt:getMinutes(),
|
||||||
},
|
},
|
||||||
isNight = gt:isNight(),
|
isNight = gt:isNight(),
|
||||||
isRaining = gt:isRainingToday(),
|
isRaining = RainManager.isRaining(), -- GameTime:isRainingToday() is a dead stub in B42.20
|
||||||
timeMultiplier = gt:getMultiplier(),
|
timeMultiplier = gt:getMultiplier(),
|
||||||
generatedAt = getTimestampMs(),
|
generatedAt = getTimestampMs(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
RDB_Constants = {}
|
RDB_Constants = {}
|
||||||
|
|
||||||
RDB_Constants.DEBUG = true
|
|
||||||
|
|
||||||
RDB_Constants.PROTOCOL_VERSION = 1
|
RDB_Constants.PROTOCOL_VERSION = 1
|
||||||
|
|
||||||
RDB_Constants.OPT = {
|
RDB_Constants.OPT = {
|
||||||
@@ -22,16 +20,6 @@ RDB_Constants.PLAYER_STATS_PREFIX = "RconDataBridge_PlayerStats_"
|
|||||||
|
|
||||||
RDB_Constants.MAX_PAYLOAD_BYTES = 4096
|
RDB_Constants.MAX_PAYLOAD_BYTES = 4096
|
||||||
RDB_Constants.OPTION_MAX_LENGTH = 8192
|
RDB_Constants.OPTION_MAX_LENGTH = 8192
|
||||||
RDB_Constants.POLL_EVERY_TICKS = 30
|
|
||||||
|
|
||||||
-- How long a request id's cached response is replayed for on retry before
|
|
||||||
-- it's treated as expired and the op runs again. Deliberately short: a
|
|
||||||
-- client that reuses a fixed id for routine polling (e.g. "ws-poll" every
|
|
||||||
-- few minutes) should get a fresh answer each time, not the first-ever
|
|
||||||
-- response forever. A count-based cap alone (MAX_ENTRIES in
|
|
||||||
-- RDB_Idempotency) doesn't help here, it only evicts under high request
|
|
||||||
-- volume, never under low-and-slow polling, which is exactly this case.
|
|
||||||
RDB_Constants.IDEMPOTENCY_TTL_MS = 60 * 1000
|
|
||||||
|
|
||||||
RDB_Constants.ERROR_CODES = {
|
RDB_Constants.ERROR_CODES = {
|
||||||
SCHEMA_ERROR = "SCHEMA_ERROR",
|
SCHEMA_ERROR = "SCHEMA_ERROR",
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Sandbox_RconDataBridge": "RCON Data Bridge",
|
||||||
|
"Sandbox_RconDataBridge_DebugSelfTest": "Debug Self-Tests",
|
||||||
|
"Sandbox_RconDataBridge_DebugSelfTest_tooltip": "Run RDB_Json/RDB_Base64 self-tests and log the result on every server boot.",
|
||||||
|
"Sandbox_RconDataBridge_PollEveryTicks": "Poll Interval (ticks)",
|
||||||
|
"Sandbox_RconDataBridge_PollEveryTicks_tooltip": "How many server ticks between checks of the request mailbox and the online-player roster. Lower is more responsive, higher is less overhead.",
|
||||||
|
"Sandbox_RconDataBridge_IdempotencyTtlSeconds": "Idempotency Window (seconds)",
|
||||||
|
"Sandbox_RconDataBridge_IdempotencyTtlSeconds_tooltip": "How long a repeated request id replays its cached response instead of running again.",
|
||||||
|
"Sandbox_RconDataBridge_RateLimitMaxRequests": "Rate Limit: Max Requests",
|
||||||
|
"Sandbox_RconDataBridge_RateLimitMaxRequests_tooltip": "Maximum RCON Data Bridge requests allowed per rate-limit window.",
|
||||||
|
"Sandbox_RconDataBridge_RateLimitWindowSeconds": "Rate Limit: Window (seconds)",
|
||||||
|
"Sandbox_RconDataBridge_RateLimitWindowSeconds_tooltip": "Length of the rolling rate-limit window, in seconds."
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
VERSION = 1,
|
||||||
|
|
||||||
|
option RconDataBridge.DebugSelfTest
|
||||||
|
{
|
||||||
|
type = boolean,
|
||||||
|
default = true,
|
||||||
|
page = RconDataBridge,
|
||||||
|
translation = RconDataBridge_DebugSelfTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
option RconDataBridge.PollEveryTicks
|
||||||
|
{
|
||||||
|
type = integer,
|
||||||
|
min = 1,
|
||||||
|
max = 300,
|
||||||
|
default = 30,
|
||||||
|
page = RconDataBridge,
|
||||||
|
translation = RconDataBridge_PollEveryTicks,
|
||||||
|
}
|
||||||
|
|
||||||
|
option RconDataBridge.IdempotencyTtlSeconds
|
||||||
|
{
|
||||||
|
type = integer,
|
||||||
|
min = 1,
|
||||||
|
max = 3600,
|
||||||
|
default = 60,
|
||||||
|
page = RconDataBridge,
|
||||||
|
translation = RconDataBridge_IdempotencyTtlSeconds,
|
||||||
|
}
|
||||||
|
|
||||||
|
option RconDataBridge.RateLimitMaxRequests
|
||||||
|
{
|
||||||
|
type = integer,
|
||||||
|
min = 1,
|
||||||
|
max = 1000,
|
||||||
|
default = 10,
|
||||||
|
page = RconDataBridge,
|
||||||
|
translation = RconDataBridge_RateLimitMaxRequests,
|
||||||
|
}
|
||||||
|
|
||||||
|
option RconDataBridge.RateLimitWindowSeconds
|
||||||
|
{
|
||||||
|
type = integer,
|
||||||
|
min = 1,
|
||||||
|
max = 3600,
|
||||||
|
default = 10,
|
||||||
|
page = RconDataBridge,
|
||||||
|
translation = RconDataBridge_RateLimitWindowSeconds,
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user