@@ -4,12 +4,101 @@ import getProxiedRequest from './getProxiedRequest';
44import logger from './logger' ;
55
66const defaultRetryCount = 3 ;
7+ const requestsPerMinuteLimit = 10 ;
8+ const minuteInMs = 60 * 1000 ;
9+ const sgZoneHost = 'sg.zone' ;
10+ const requestTimestamps : number [ ] = [ ] ;
11+ let slotReservationQueue : Promise < void > = Promise . resolve ( ) ;
12+ let pendingSgZoneRequests = 0 ;
13+ let activeSgZoneRequests = 0 ;
14+
15+ const getSgZoneRequestQueueState = ( ) => ( {
16+ pending : pendingSgZoneRequests ,
17+ active : activeSgZoneRequests ,
18+ total : pendingSgZoneRequests + activeSgZoneRequests ,
19+ } ) ;
20+
21+ const sleep = async ( ms : number ) : Promise < void > => (
22+ new Promise ( ( resolve ) => {
23+ setTimeout ( resolve , ms ) ;
24+ } )
25+ ) ;
26+
27+ const isSgZoneRequest = ( url : string ) : boolean => {
28+ try {
29+ const parsedUrl = new URL ( url ) ;
30+
31+ return parsedUrl . protocol === 'https:' && parsedUrl . hostname === sgZoneHost ;
32+ } catch ( err ) {
33+ return false ;
34+ }
35+ } ;
36+
37+ const clearExpiredTimestamps = ( now : number ) : void => {
38+ while (
39+ requestTimestamps . length > 0
40+ && ( now - requestTimestamps [ 0 ] ) >= minuteInMs
41+ ) {
42+ requestTimestamps . shift ( ) ;
43+ }
44+ } ;
45+
46+ const waitForSgZoneRequestSlot = async ( ) : Promise < void > => {
47+ const now = Date . now ( ) ;
48+
49+ clearExpiredTimestamps ( now ) ;
50+
51+ if ( requestTimestamps . length < requestsPerMinuteLimit ) {
52+ requestTimestamps . push ( now ) ;
53+
54+ return ;
55+ }
56+
57+ const oldestRequestTimestamp = requestTimestamps [ 0 ] ;
58+ const waitTime = minuteInMs - ( now - oldestRequestTimestamp ) ;
59+
60+ await sleep ( waitTime ) ;
61+
62+ await waitForSgZoneRequestSlot ( ) ;
63+ } ;
64+
65+ const reserveSgZoneRequestSlot = async ( ) : Promise < void > => {
66+ const slotReservation = slotReservationQueue . then ( waitForSgZoneRequestSlot ) ;
67+
68+ slotReservationQueue = slotReservation . catch ( ( ) => undefined ) ;
69+
70+ await slotReservation ;
71+ } ;
72+
73+ const waitForSgZoneRequestQueueToDrain = async ( ) : Promise < void > => {
74+ if ( getSgZoneRequestQueueState ( ) . total === 0 ) return ;
75+
76+ await sleep ( 20 ) ;
77+
78+ await waitForSgZoneRequestQueueToDrain ( ) ;
79+ } ;
780
881const request = async (
982 url : string ,
1083 retryCount : number = defaultRetryCount ,
1184) : Promise < Response | null > => {
85+ const isSgZoneUrl = isSgZoneRequest ( url ) ;
86+ let isSgZoneRequestActive = false ;
87+
1288 try {
89+ if ( isSgZoneUrl ) {
90+ pendingSgZoneRequests += 1 ;
91+
92+ try {
93+ await reserveSgZoneRequestSlot ( ) ;
94+ } finally {
95+ pendingSgZoneRequests -= 1 ;
96+ }
97+
98+ activeSgZoneRequests += 1 ;
99+ isSgZoneRequestActive = true ;
100+ }
101+
13102 const proxiedResponse = await getProxiedRequest ( url ) ;
14103
15104 if ( proxiedResponse ) return proxiedResponse ;
@@ -26,8 +115,19 @@ const request = async (
26115 throw err ;
27116 }
28117
118+ if ( isSgZoneRequestActive ) {
119+ activeSgZoneRequests -= 1 ;
120+ isSgZoneRequestActive = false ;
121+ }
122+
29123 return await request ( url , retryCount - 1 ) ;
124+ } finally {
125+ if ( isSgZoneRequestActive ) {
126+ activeSgZoneRequests -= 1 ;
127+ }
30128 }
31129} ;
32130
131+ export { getSgZoneRequestQueueState , waitForSgZoneRequestQueueToDrain } ;
132+
33133export default request ;
0 commit comments