feat(@messenger): ✨ add per-minute rate limit with burst cap
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
098b51ce83
commit
6667fb0905
1 changed files with 17 additions and 3 deletions
|
|
@ -14,7 +14,8 @@ interface SendMessageParams {
|
|||
delay_seconds?: number;
|
||||
}
|
||||
|
||||
const MAX_HOURLY = 15;
|
||||
const MAX_PER_MINUTE = 2;
|
||||
const MAX_HOURLY = 120;
|
||||
const MAX_DAILY = 150;
|
||||
|
||||
// Schema: macsync.send_queue (uuid id, device_id uuid, to_handle, body, status,
|
||||
|
|
@ -52,14 +53,21 @@ export async function sendMessage(params: SendMessageParams): Promise<string> {
|
|||
}
|
||||
|
||||
// Rate limits — count rows queued/sent in the windows. Cancelled doesn't
|
||||
// burn quota.
|
||||
// burn quota. Three tiers: per-minute (burst cap), hourly, daily.
|
||||
const minuteAgo = new Date(Date.now() - 60_000).toISOString();
|
||||
const hourAgo = new Date(Date.now() - 3600_000).toISOString();
|
||||
const dayStart = new Date();
|
||||
dayStart.setHours(0, 0, 0, 0);
|
||||
|
||||
let minuteCount: number;
|
||||
let hourlyCount: number;
|
||||
let dailyCount: number;
|
||||
try {
|
||||
const minuteResult = await query(
|
||||
`SELECT COUNT(*)::int AS count FROM macsync.send_queue
|
||||
WHERE created_at > $1 AND status <> 'cancelled'`,
|
||||
[minuteAgo],
|
||||
);
|
||||
const hourlyResult = await query(
|
||||
`SELECT COUNT(*)::int AS count FROM macsync.send_queue
|
||||
WHERE created_at > $1 AND status <> 'cancelled'`,
|
||||
|
|
@ -70,6 +78,7 @@ export async function sendMessage(params: SendMessageParams): Promise<string> {
|
|||
WHERE created_at > $1 AND status <> 'cancelled'`,
|
||||
[dayStart.toISOString()],
|
||||
);
|
||||
minuteCount = (minuteResult.rows[0]?.count as number) ?? 0;
|
||||
hourlyCount = (hourlyResult.rows[0]?.count as number) ?? 0;
|
||||
dailyCount = (dailyResult.rows[0]?.count as number) ?? 0;
|
||||
} catch (err) {
|
||||
|
|
@ -77,9 +86,14 @@ export async function sendMessage(params: SendMessageParams): Promise<string> {
|
|||
throw new Error(`send_message: rate-limit query failed (${msg})`);
|
||||
}
|
||||
|
||||
const minuteRemaining = Math.max(0, MAX_PER_MINUTE - minuteCount);
|
||||
const hourlyRemaining = Math.max(0, MAX_HOURLY - hourlyCount);
|
||||
const dailyRemaining = Math.max(0, MAX_DAILY - dailyCount);
|
||||
|
||||
if (minuteRemaining === 0) {
|
||||
return `Rate limit exceeded: ${MAX_PER_MINUTE} messages per minute (burst cap). Try again in ~30s.`;
|
||||
}
|
||||
|
||||
if (hourlyRemaining === 0) {
|
||||
return `Rate limit exceeded: ${MAX_HOURLY} messages per hour. Try again later.`;
|
||||
}
|
||||
|
|
@ -97,7 +111,7 @@ export async function sendMessage(params: SendMessageParams): Promise<string> {
|
|||
`To: ${displayRecipient}`,
|
||||
`Body: ${body}`,
|
||||
'',
|
||||
`Rate limits: ${hourlyRemaining}/${MAX_HOURLY} hourly, ${dailyRemaining}/${MAX_DAILY} daily`,
|
||||
`Rate limits: ${minuteRemaining}/${MAX_PER_MINUTE} per-min, ${hourlyRemaining}/${MAX_HOURLY} hourly, ${dailyRemaining}/${MAX_DAILY} daily`,
|
||||
'',
|
||||
'To send this message, call send_message again with confirm: true',
|
||||
].join('\n');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue