@@ -3,6 +3,32 @@ import path from 'path';
33
44import type { VercelRequest , VercelResponse } from '@vercel/node' ;
55
6+ const RATE_LIMIT_WINDOW_MS = 60 * 1000 ; // 1 minute
7+ const RATE_LIMIT_MAX = 60 ;
8+ const rateLimitMap = new Map < string , { count : number ; resetTime : number } > ( ) ;
9+
10+ function pruneExpiredRateLimitEntries ( now : number ) : void {
11+ for ( const [ ip , entry ] of rateLimitMap . entries ( ) ) {
12+ if ( now > entry . resetTime ) {
13+ rateLimitMap . delete ( ip ) ;
14+ }
15+ }
16+ }
17+
18+ function isRateLimited ( ip : string ) : boolean {
19+ const now = Date . now ( ) ;
20+ pruneExpiredRateLimitEntries ( now ) ;
21+ const entry = rateLimitMap . get ( ip ) ;
22+
23+ if ( ! entry || now > entry . resetTime ) {
24+ rateLimitMap . set ( ip , { count : 1 , resetTime : now + RATE_LIMIT_WINDOW_MS } ) ;
25+ return false ;
26+ }
27+
28+ entry . count ++ ;
29+ return entry . count > RATE_LIMIT_MAX ;
30+ }
31+
632interface VersionInfo {
733 latest : string ;
834 majorVersions ?: Record < string , { latest : string } > ;
@@ -54,6 +80,11 @@ function sendJson(res: VercelResponse, content: string): void {
5480}
5581
5682export default function handler ( req : VercelRequest , res : VercelResponse ) {
83+ const clientIp = ( req . headers [ 'x-forwarded-for' ] as string ) ?. split ( ',' ) [ 0 ] ?. trim ( ) || 'unknown' ;
84+ if ( isRateLimited ( clientIp ) ) {
85+ return res . status ( 429 ) . json ( { error : 'Too Many Requests' , message : 'Rate limit exceeded' } ) ;
86+ }
87+
5788 const { file } = req . query ;
5889 const fileName = Array . isArray ( file ) ? file . join ( '/' ) : file || '' ;
5990
@@ -68,14 +99,15 @@ export default function handler(req: VercelRequest, res: VercelResponse) {
6899
69100 const basePath = getBasePath ( ) ;
70101 const versionInfo = getVersionInfo ( basePath ) ;
71- const versionParam = req . query . version as string | undefined ;
102+ const rawVersionParam = req . query . version ;
103+ const versionParam = typeof rawVersionParam === 'string' ? rawVersionParam : undefined ;
72104
73105 const rootFilePath = path . join ( basePath , normalizedFileName ) ;
74106 if ( ! versionParam && fs . existsSync ( rootFilePath ) ) {
75107 try {
76108 sendJson ( res , fs . readFileSync ( rootFilePath , 'utf-8' ) ) ;
77109 } catch ( error ) {
78- console . error ( ` Failed to read registry file ${ rootFilePath } :` , error ) ;
110+ console . error ( ' Failed to read registry file: %s' , rootFilePath ) ;
79111 res
80112 . status ( 500 )
81113 . json ( { error : 'Internal Server Error' , message : 'Failed to read registry file' } ) ;
@@ -121,7 +153,7 @@ export default function handler(req: VercelRequest, res: VercelResponse) {
121153 try {
122154 sendJson ( res , fs . readFileSync ( versionedPath , 'utf-8' ) ) ;
123155 } catch ( error ) {
124- console . error ( ` Failed to read registry file ${ versionedPath } :` , error ) ;
156+ console . error ( ' Failed to read registry file: %s' , versionedPath ) ;
125157 res
126158 . status ( 500 )
127159 . json ( { error : 'Internal Server Error' , message : 'Failed to read registry file' } ) ;
0 commit comments