openvibers/openvibe.media Commits

/limits.json lists the per-actor rate limits as rate_limits (roadmap WS-R task 4): every named limit the routes declared (object upload, parts, complete, delete, jobs, files, me), with its numbers per caller per minute and hour and 429 rate_limited past it, so Codes' limits page shows what is enforced. test/limits.test.js checks the upload and part numbers. npm test passes.

OP OpenVibers committed 28 Sept 2026 · 62483631db · 3 files, +26 −5


Changes

ddiff --git a/server/actor-limits.js b/server/actor-limits.js
iindex b6192dc..422b834 100644
---- a/server/actor-limits.js
++++ b/server/actor-limits.js
@@@ -36,13 +36,22 @@ function onLimited(e) {
if (refused) refused.inc({ limit: e.name, window: e.window });
}
--const limits = createActorLimiter({
++const limiter = createActorLimiter({
limits: { minute: config.limits.minute, hour: config.limits.hour },
actor,
now: () => clock(),
onLimited,
});
++/** Every named limit with its numbers, as the routes declare them (published in /limits.json). */
++const registered = new Map();
++function limits(name, own = {}) {
++ registered.set(name, { minute: own.minute != null ? own.minute : config.limits.minute, hour: own.hour != null ? own.hour : config.limits.hour });
++ return limiter(name, own);
++}
++limits.stats = () => limiter.stats();
++limits.reset = () => limiter.reset();
++
/** Count refusals in /metrics (server/index.js, after observability.instrument). */
function bindMetrics(registry) {
if (refused || !registry) return;
@@@ -51,6 +60,8 @@ function bindMetrics(registry) {
module.exports = {
limits,
++ /** [{ id, minute, hour }] for every limit the routes have declared, sorted by id. */
++ registered: () => [...registered].map(([id, n]) => ({ id, ...n })).sort((x, y) => x.id.localeCompare(y.id)),
actor,
bindMetrics,
_setClockForTests(fn) { clock = fn || (() => Date.now()); },
ddiff --git a/server/limits.js b/server/limits.js
iindex 7a3a11b..0c1f1f0 100644
---- a/server/limits.js
++++ b/server/limits.js
@@@ -39,9 +39,10 @@ function limitsOf(config) {
}
function mountLimits(app, config) {
-- const body = limitsOf(config);
app.get('/limits.json', (_req, res) => {
-- res.set('Cache-Control', 'public, max-age=300').set('Access-Control-Allow-Origin', '*').json(body);
++ // Per-actor rate limits (server/actor-limits.js), as the routes declared them: per caller, per minute and hour.
++ const rate_limits = require('./actor-limits').registered().map((l) => ({ ...l, per: 'caller (principal, person, else address)', exceeded: '429 rate_limited' }));
++ res.set('Cache-Control', 'public, max-age=300').set('Access-Control-Allow-Origin', '*').json({ ...limitsOf(config), rate_limits });
});
}
ddiff --git a/test/limits.test.js b/test/limits.test.js
iindex 03a619e..5e3162a 100644
---- a/test/limits.test.js
++++ b/test/limits.test.js
@@@ -1,6 +1,7 @@
'use strict';
// GET /limits.json (WS-N task 7): the developer limits Media enforces, read from the running config
--// (an env override is what it answers), public with CORS *; Codes' /docs/limits renders it.
++// (an env override is what it answers), public with CORS *; Codes' /docs/limits renders it. rate_limits lists the
++// per-actor rate limits the routes declare (WS-R task 4).
process.env.MEDIA_APP_SANDBOX_QUOTA_MB = '50';
const assert = require('assert');
const http = require('http');
@@@ -23,7 +24,15 @@ const { limitsOf, mountLimits } = require('../server/limits');
const res = await fetch(`http://127.0.0.1:${server.address().port}/limits.json`);
assert.strictEqual(res.status, 200);
assert.strictEqual(res.headers.get('access-control-allow-origin'), '*');
-- assert.strictEqual((await res.json()).service, 'media');
++ const body = await res.json();
++ assert.strictEqual(body.service, 'media');
++ // The per-actor rate limits, as the routes declare them (loading a router declares its limits).
++ require('../server/objects/routes');
++ const again = await (await fetch(`http://127.0.0.1:${server.address().port}/limits.json`)).json();
++ const rate = Object.fromEntries(again.rate_limits.map((l) => [l.id, l]));
++ assert.deepStrictEqual([rate['media.object.upload'].minute, rate['media.object.upload'].hour], [30, 600]);
++ assert.deepStrictEqual([rate['media.object.part'].minute, rate['media.object.part'].hour], [600, 20000]);
++ assert.strictEqual(rate['media.object.upload'].exceeded, '429 rate_limited');
server.close();
console.log('limits: all checks passed');
})().catch((e) => { console.error(e); process.exit(1); });