feat(platforms): Add 8 professional platforms — Behance, Dribbble, Medium, Substack, Dev.to, Hashnode, CodePen, HackerNews (#574) - #593
Conversation
…, Medium, Substack, Dev.to, Hashnode, CodePen, HackerNews - lib/platforms.ts: URL pattern + placeholder + route for all 8 platforms - lib/platformIcons.ts: react-icons/si mappings (SiBehance, SiDribbble, SiMedium, SiSubstack, SiDevdotto, SiHashnode, SiCodepen, SiYcombinator) - README.md: updated Supported Platforms table with all new entries - Auto-detection works for all 8 when user pastes a URL (existing detection logic uses urlPattern) Closes vishnukothakapu#574
|
@prince-pokharna is attempting to deploy a commit to the vishnukothakapu's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThe PR adds Behance, Substack, CodePen, and Hacker News to platform types, validation, and icon metadata. It also replaces Redis-backed sliding-window rate limiting with a process-local fixed-window counter and adds client IP extraction. ChangesPlatform support
Rate limiting
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Warning |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
lib/rateLimit.ts (1)
59-66: 🔒 Security & Privacy | 🔵 Trivial
getIptrusts client-controllable headers without proxy-trust validation.
getIpreadsx-forwarded-forandx-real-ipdirectly and uses the result to key IP-based rate limits. If the deployment is not guaranteed to sit behind a proxy layer that overwrites (rather than appends to) these headers, a client can spoofx-forwarded-forwith an arbitrary or rotating value to obtain a fresh rate-limit bucket on every request, bypassing the limiter entirely.Separately, if
x-forwarded-foris present but empty (e.g.,""),''.split(',')[0].trim()yields'', which is not nullish, so the??fallback tox-real-ipor'unknown'never triggers. Multiple such clients would then collide in the same:-suffixed bucket.Confirm the deployment topology (e.g., Vercel's edge network) guarantees these headers are proxy-set and not attacker-controlled, and consider treating a falsy/empty first segment the same as a missing header.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/rateLimit.ts` around lines 59 - 66, Update getIp to rely only on headers guaranteed to be proxy-controlled by the deployment topology, or otherwise avoid using client-supplied x-forwarded-for/x-real-ip values for rate-limit keys. Also treat an empty trimmed first x-forwarded-for segment as missing so fallback resolution reaches x-real-ip or 'unknown' rather than returning an empty string.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/platforms.ts`:
- Around line 50-53: Update the profile URL regular expressions for hashnode,
devto, medium, and dribbble in the platform definitions to allow an optional
query string after the profile path, while preserving existing host, username,
and trailing-slash validation so URLs such as dev.to profiles with tracking
parameters remain detected correctly.
- Around line 56-59: Update the Behance and CodePen handling in
PLATFORM_PATTERNS or PLATFORM_BLOCKLIST to reject reserved routes such as
Behance search and CodePen pen while continuing to accept valid profile URLs.
Add regression tests for these non-profile URLs and preserve existing
valid-profile coverage.
In `@lib/rateLimit.ts`:
- Around line 27-39: Update all remaining checkRateLimit callers in the listed
API route handlers and rateLimit.test.ts to use the exported rateLimit(req, key,
limit, windowMs) API, awaiting its Promise result and preserving each caller’s
existing allow/block behavior. Alternatively, add a compatible checkRateLimit
wrapper in lib/rateLimit, but ensure every caller builds against the current
asynchronous rate limiter contract.
- Around line 41-53: Update the rate-limit response in the record.count >= limit
branch to calculate Retry-After from the remaining time until the current window
resets, rather than the full windowMs duration. Use the existing record/window
timing values available in lib/rateLimit.ts, round up to whole seconds, and
preserve a minimum of one second for the header.
- Around line 3-18: Update the RateLimitRecord structure and rateLimit record
creation to store each key’s windowMs, then change the setInterval cleanup sweep
to compare elapsed time against record.windowMs instead of the fixed five-minute
threshold. Preserve the existing cleanup behavior while ensuring long-window
limits remain active for their configured duration.
---
Nitpick comments:
In `@lib/rateLimit.ts`:
- Around line 59-66: Update getIp to rely only on headers guaranteed to be
proxy-controlled by the deployment topology, or otherwise avoid using
client-supplied x-forwarded-for/x-real-ip values for rate-limit keys. Also treat
an empty trimmed first x-forwarded-for segment as missing so fallback resolution
reaches x-real-ip or 'unknown' rather than returning an empty string.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 726ce2fb-6673-47ad-95eb-dd0b11539bcd
📒 Files selected for processing (3)
lib/platformIcons.tslib/platforms.tslib/rateLimit.ts
| hashnode: /^https?:\/\/[\w-]+\.hashnode\.dev\/?$|^https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?$/i, | ||
| devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?$/i, | ||
| medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?$/i, | ||
| dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?$/i, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Keep query strings valid for profile URLs.
These patterns now reject otherwise valid profile URLs with tracking parameters. For example, https://dev.to/alice?ref=share fails validation and detectPlatform falls back to website.
Allow an optional query string after the profile path.
Proposed fix
- hashnode: /^https?:\/\/[\w-]+\.hashnode\.dev\/?$|^https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?$/i,
- devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?$/i,
- medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?$/i,
- dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?$/i,
+ hashnode: /^(?:https?:\/\/[\w-]+\.hashnode\.dev\/?|https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?)(?:\?[^#]*)?$/i,
+ devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?(?:\?[^#]*)?$/i,
+ medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?(?:\?[^#]*)?$/i,
+ dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?(?:\?[^#]*)?$/i,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| hashnode: /^https?:\/\/[\w-]+\.hashnode\.dev\/?$|^https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?$/i, | |
| devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?$/i, | |
| medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?$/i, | |
| dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?$/i, | |
| hashnode: /^(?:https?:\/\/[\w-]+\.hashnode\.dev\/?|https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?)(?:\?[^#]*)?$/i, | |
| devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?(?:\?[^#]*)?$/i, | |
| medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?(?:\?[^#]*)?$/i, | |
| dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?(?:\?[^#]*)?$/i, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/platforms.ts` around lines 50 - 53, Update the profile URL regular
expressions for hashnode, devto, medium, and dribbble in the platform
definitions to allow an optional query string after the profile path, while
preserving existing host, username, and trailing-slash validation so URLs such
as dev.to profiles with tracking parameters remain detected correctly.
| behance: /^https?:\/\/(www\.)?behance\.net\/[\w.-]+\/?$/i, | ||
| substack: /^https?:\/\/[\w-]+\.substack\.com\/?$/i, | ||
| codepen: /^https?:\/\/(www\.)?codepen\.io\/[\w.-]+\/?$/i, | ||
| hackernews: /^https?:\/\/news\.ycombinator\.com\/user\?id=[\w.-]+$/i, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 6 'PLATFORM_BLOCKLIST|behance|codepen|search|pen|popular|explore' libRepository: vishnukothakapu/linkid
Length of output: 14872
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant platforms.ts section =="
sed -n '1,160p' lib/platforms.ts
echo
echo "== validatePlatformUrl tests =="
rg -n -C 4 'validatePlatformUrl|PLATFORM_BLOCKLIST|behance|codepen|results' lib/platforms.test.ts lib
echo
echo "== source mentions of reserved terms =="
rg -n 'search|pen|popular|explore|results|messaging|feed|groups|events' lib/platforms.ts lib/platforms.test.tsRepository: vishnukothakapu/linkid
Length of output: 44459
Reject Behance and CodePen reserved routes before accepting them as profiles.
The Behance pattern accepts https://www.behance.net/search, while the CodePen pattern accepts https://codepen.io/pen. Add Behance/CodePen entries to PLATFORM_BLOCKLIST or tighten the PLATFORM_PATTERNS to require the user segment, and add regression tests covering these non-profile URLs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/platforms.ts` around lines 56 - 59, Update the Behance and CodePen
handling in PLATFORM_PATTERNS or PLATFORM_BLOCKLIST to reject reserved routes
such as Behance search and CodePen pen while continuing to accept valid profile
URLs. Add regression tests for these non-profile URLs and preserve existing
valid-profile coverage.
| interface RateLimitRecord { | ||
| count: number; | ||
| ts: number; | ||
| } | ||
|
|
||
| function checkRateLimitMemory( | ||
| key: string, | ||
| limit: number, | ||
| windowMs: number, | ||
| ): boolean { | ||
| const now = Date.now(); | ||
| const cutoff = now - windowMs; | ||
|
|
||
| requestsSinceCleanup++; | ||
| if (requestsSinceCleanup >= CLEANUP_INTERVAL) { | ||
| requestsSinceCleanup = 0; | ||
| sweepExpiredKeys(); | ||
| } | ||
|
|
||
| let entry = store.get(key); | ||
| if (!entry) { | ||
| entry = { timestamps: [], windowMs }; | ||
| store.set(key, entry); | ||
| } else { | ||
| entry.windowMs = windowMs; | ||
| } | ||
| // In-process store — works for single-instance deployments. | ||
| // For multi-instance Vercel Edge, swap to @upstash/ratelimit (see .env.example). | ||
| const rateLimitMap = new Map<string, RateLimitRecord>(); | ||
|
|
||
| // Evict timestamps outside the current window. | ||
| entry.timestamps = entry.timestamps.filter((t) => t > cutoff); | ||
|
|
||
| if (entry.timestamps.length >= limit) { | ||
| return false; | ||
| } | ||
|
|
||
| entry.timestamps.push(now); | ||
| return true; | ||
| } | ||
|
|
||
| // ─── Redis Backend ──────────────────────────────────────────────────────────── | ||
| // Clean up stale entries every 5 minutes to avoid memory leaks | ||
| setInterval(() => { | ||
| const now = Date.now(); | ||
| for (const [key, record] of rateLimitMap.entries()) { | ||
| if (now - record.ts > 5 * 60_000) rateLimitMap.delete(key); | ||
| } | ||
| }, 5 * 60_000); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Fix cleanup sweep: it ignores each record's actual window.
The interval at Line 13-18 deletes a record when now - record.ts > 5 * 60_000, using a fixed 5-minute threshold for every key. record.ts is set once at window start (Line 37) and never refreshed on subsequent hits (Line 55). Any key configured with windowMs greater than 5 minutes loses its counter after 5-10 minutes of wall-clock time, regardless of request volume, and the next request silently starts a fresh window with count = 1.
This reintroduces the exact scenario the removed checkRateLimit test guarded against ("Global Rate Limit Reset Vulnerability check" in the provided lib/rateLimit.test.ts snippet), but worse: the old sweep only triggered after 500 calls to any key, while this sweep fires automatically on a wall-clock timer independent of traffic. A caller using a long window (e.g., 24 hours) effectively gets a 5-10 minute rate limit instead.
Store the window length in the record and use it for the cleanup decision instead of a fixed constant.
🐛 Proposed fix
interface RateLimitRecord {
count: number;
ts: number;
+ windowMs: number;
}
// In-process store — works for single-instance deployments.
// For multi-instance Vercel Edge, swap to `@upstash/ratelimit` (see .env.example).
const rateLimitMap = new Map<string, RateLimitRecord>();
// Clean up stale entries every 5 minutes to avoid memory leaks
setInterval(() => {
const now = Date.now();
for (const [key, record] of rateLimitMap.entries()) {
- if (now - record.ts > 5 * 60_000) rateLimitMap.delete(key);
+ if (now - record.ts > record.windowMs) rateLimitMap.delete(key);
}
}, 5 * 60_000);And in rateLimit(), update the record creation to include windowMs:
if (!record || now - record.ts > windowMs) {
- rateLimitMap.set(key, { count: 1, ts: now });
+ rateLimitMap.set(key, { count: 1, ts: now, windowMs });
return null; // allowed
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| interface RateLimitRecord { | |
| count: number; | |
| ts: number; | |
| } | |
| function checkRateLimitMemory( | |
| key: string, | |
| limit: number, | |
| windowMs: number, | |
| ): boolean { | |
| const now = Date.now(); | |
| const cutoff = now - windowMs; | |
| requestsSinceCleanup++; | |
| if (requestsSinceCleanup >= CLEANUP_INTERVAL) { | |
| requestsSinceCleanup = 0; | |
| sweepExpiredKeys(); | |
| } | |
| let entry = store.get(key); | |
| if (!entry) { | |
| entry = { timestamps: [], windowMs }; | |
| store.set(key, entry); | |
| } else { | |
| entry.windowMs = windowMs; | |
| } | |
| // In-process store — works for single-instance deployments. | |
| // For multi-instance Vercel Edge, swap to @upstash/ratelimit (see .env.example). | |
| const rateLimitMap = new Map<string, RateLimitRecord>(); | |
| // Evict timestamps outside the current window. | |
| entry.timestamps = entry.timestamps.filter((t) => t > cutoff); | |
| if (entry.timestamps.length >= limit) { | |
| return false; | |
| } | |
| entry.timestamps.push(now); | |
| return true; | |
| } | |
| // ─── Redis Backend ──────────────────────────────────────────────────────────── | |
| // Clean up stale entries every 5 minutes to avoid memory leaks | |
| setInterval(() => { | |
| const now = Date.now(); | |
| for (const [key, record] of rateLimitMap.entries()) { | |
| if (now - record.ts > 5 * 60_000) rateLimitMap.delete(key); | |
| } | |
| }, 5 * 60_000); | |
| interface RateLimitRecord { | |
| count: number; | |
| ts: number; | |
| windowMs: number; | |
| } | |
| // In-process store — works for single-instance deployments. | |
| // For multi-instance Vercel Edge, swap to `@upstash/ratelimit` (see .env.example). | |
| const rateLimitMap = new Map<string, RateLimitRecord>(); | |
| // Clean up stale entries every 5 minutes to avoid memory leaks | |
| setInterval(() => { | |
| const now = Date.now(); | |
| for (const [key, record] of rateLimitMap.entries()) { | |
| if (now - record.ts > record.windowMs) rateLimitMap.delete(key); | |
| } | |
| }, 5 * 60_000); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/rateLimit.ts` around lines 3 - 18, Update the RateLimitRecord structure
and rateLimit record creation to store each key’s windowMs, then change the
setInterval cleanup sweep to compare elapsed time against record.windowMs
instead of the fixed five-minute threshold. Preserve the existing cleanup
behavior while ensuring long-window limits remain active for their configured
duration.
| export function rateLimit( | ||
| req: NextRequest, | ||
| key: string, | ||
| limit: number, | ||
| windowMs: number | ||
| ): NextResponse | null { | ||
| const now = Date.now(); | ||
| const record = rateLimitMap.get(key); | ||
|
|
||
| if (!record || now - record.ts > windowMs) { | ||
| rateLimitMap.set(key, { count: 1, ts: now }); | ||
| return null; // allowed | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Confirm whether old rate-limit API callers were updated elsewhere in the PR.
set -euo pipefail
echo "== checkRateLimit usages =="
rg -n 'checkRateLimit' --type=ts
echo "== linksLimiter usages =="
rg -n 'linksLimiter' --type=ts
echo "== rateLimit / getIp new usages =="
rg -n '\brateLimit\s*\(|\bgetIp\s*\(' --type=tsRepository: vishnukothakapu/linkid
Length of output: 188
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== tracked TypeScript files =="
git ls-files '*.ts' | sed -n '1,200p'
echo "== exact rate limiter filenames =="
fd -a 'rateLimit|rate-limit' . 2>/dev/null || true
echo "== possible exports/usages =="
rg -n 'checkRateLimit|linksLimiter|rateLimit|getIp|export function rateLimit|export const linksLimiter|export async function checkRateLimit' . --glob '*.ts' || true
echo "== route imports for lib/rateLimit =="
rg -n 'from .*rateLimit|from ["'\''].*rateLimit|["'\'']lib/rateLimit' . --glob '*.ts' || trueRepository: vishnukothakapu/linkid
Length of output: 8385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== lib/rateLimit.ts =="
cat -n lib/rateLimit.ts
echo "== updated caller snippets =="
for f in app/api/contact-us/route.ts app/api/auth/register/route.ts app/api/links/click/route.ts app/api/links/[id]/route.ts app/api/jobs/enqueue/route.ts app/api/user/delete/route.ts app/api/links/route.ts lib/rateLimit.test.ts; do
echo "--- $f ---"
sed -n '1,80p' "$f" | cat -n
doneRepository: vishnukothakapu/linkid
Length of output: 25533
Update the remaining checkRateLimit callers to the new rate-limiter API.
@/lib/rateLimit now exports rateLimit(req, key, limit, windowMs): Promise<NextResponse | null>, but app/api/contact-us/route.ts, app/api/auth/register/route.ts, app/api/links/click/route.ts, app/api/links/[id]/route.ts, app/api/jobs/enqueue/route.ts, app/api/user/delete/route.ts, and lib/rateLimit.test.ts still call the removed checkRateLimit, so the build will fail. Update these call sites or add a backward-compatible wrapper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/rateLimit.ts` around lines 27 - 39, Update all remaining checkRateLimit
callers in the listed API route handlers and rateLimit.test.ts to use the
exported rateLimit(req, key, limit, windowMs) API, awaiting its Promise result
and preserving each caller’s existing allow/block behavior. Alternatively, add a
compatible checkRateLimit wrapper in lib/rateLimit, but ensure every caller
builds against the current asynchronous rate limiter contract.
| if (record.count >= limit) { | ||
| return NextResponse.json( | ||
| { error: 'Too many requests. Please slow down.' }, | ||
| { | ||
| status: 429, | ||
| headers: { | ||
| 'Retry-After': String(Math.ceil(windowMs / 1000)), | ||
| 'X-RateLimit-Limit': String(limit), | ||
| 'X-RateLimit-Remaining': '0', | ||
| }, | ||
| } | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Retry-After reports the full window, not the remaining time.
Math.ceil(windowMs / 1000) always returns the entire window length. A client blocked near the end of the window is told to wait the full window again instead of the actual remaining time until reset.
🐛 Proposed fix
if (record.count >= limit) {
+ const retryAfterMs = windowMs - (now - record.ts);
return NextResponse.json(
{ error: 'Too many requests. Please slow down.' },
{
status: 429,
headers: {
- 'Retry-After': String(Math.ceil(windowMs / 1000)),
+ 'Retry-After': String(Math.max(1, Math.ceil(retryAfterMs / 1000))),
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
},
}
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (record.count >= limit) { | |
| return NextResponse.json( | |
| { error: 'Too many requests. Please slow down.' }, | |
| { | |
| status: 429, | |
| headers: { | |
| 'Retry-After': String(Math.ceil(windowMs / 1000)), | |
| 'X-RateLimit-Limit': String(limit), | |
| 'X-RateLimit-Remaining': '0', | |
| }, | |
| } | |
| ); | |
| } | |
| if (record.count >= limit) { | |
| const retryAfterMs = windowMs - (now - record.ts); | |
| return NextResponse.json( | |
| { error: 'Too many requests. Please slow down.' }, | |
| { | |
| status: 429, | |
| headers: { | |
| 'Retry-After': String(Math.max(1, Math.ceil(retryAfterMs / 1000))), | |
| 'X-RateLimit-Limit': String(limit), | |
| 'X-RateLimit-Remaining': '0', | |
| }, | |
| } | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/rateLimit.ts` around lines 41 - 53, Update the rate-limit response in the
record.count >= limit branch to calculate Retry-After from the remaining time
until the current window resets, rather than the full windowMs duration. Use the
existing record/window timing values available in lib/rateLimit.ts, round up to
whole seconds, and preserve a minimum of one second for the header.
|
Thanks for the PR, @prince-pokharna! Could you please address the CodeRabbit review comments? Once those are resolved, we'll take another look. |
Closes #574
Summary by CodeRabbit
New Features
Bug Fixes