@@ -17,6 +17,14 @@ class MotApiSdk extends EventEmitter {
17
17
private token : string | null = null ;
18
18
private tokenExpiry : number = 0 ;
19
19
20
+ // Rate limiting properties
21
+ private dailyQuota : number = 500000 ;
22
+ private dailyQuotaReset : number = 0 ;
23
+ private burstLimit : number = 10 ;
24
+ private burstTokens : number = 10 ;
25
+ private rpsLimit : number = 15 ;
26
+ private requestTimestamps : number [ ] = [ ] ;
27
+
20
28
private static readonly BASE_URL =
21
29
"https://history.mot.api.gov.uk/v1/trade/vehicles" ;
22
30
private static readonly TOKEN_URL =
@@ -31,76 +39,134 @@ class MotApiSdk extends EventEmitter {
31
39
[ 404 , "Not Found - The requested data is not found" ] ,
32
40
[
33
41
405 ,
34
- "Method Not Allowed - The HTTP method is not supported for this endpoint" ,
42
+ "Method Not Allowed - The HTTP method is not supported for this endpoint"
35
43
] ,
36
44
[ 406 , "Not Acceptable - The requested media type is not supported" ] ,
37
45
[
38
46
409 ,
39
- "Conflict - The request could not be completed due to a conflict with the current state of the target resource" ,
47
+ "Conflict - The request could not be completed due to a conflict with the current state of the target resource"
40
48
] ,
41
49
[
42
50
412 ,
43
- "Precondition Failed - Could not complete request because a constraint was not met" ,
51
+ "Precondition Failed - Could not complete request because a constraint was not met"
44
52
] ,
45
53
[
46
54
415 ,
47
- "Unsupported Media Type - The media type of the request is not supported" ,
55
+ "Unsupported Media Type - The media type of the request is not supported"
48
56
] ,
49
57
[
50
58
422 ,
51
- "Unprocessable Entity - The request was well-formed but contains semantic errors" ,
59
+ "Unprocessable Entity - The request was well-formed but contains semantic errors"
52
60
] ,
53
61
[
54
62
429 ,
55
- "Too Many Requests - The user has sent too many requests in a given amount of time" ,
63
+ "Too Many Requests - The user has sent too many requests in a given amount of time"
56
64
] ,
57
65
[ 500 , "Internal Server Error - An unexpected error has occurred" ] ,
58
66
[
59
67
502 ,
60
- "Bad Gateway - The server received an invalid response from an upstream server" ,
68
+ "Bad Gateway - The server received an invalid response from an upstream server"
61
69
] ,
62
70
[
63
71
503 ,
64
- "Service Unavailable - The server is currently unable to handle the request" ,
72
+ "Service Unavailable - The server is currently unable to handle the request"
65
73
] ,
66
74
[
67
75
504 ,
68
- "Gateway Timeout - The upstream server failed to send a request in the time allowed by the server" ,
69
- ] ,
70
- ] ,
76
+ "Gateway Timeout - The upstream server failed to send a request in the time allowed by the server"
77
+ ]
78
+ ]
71
79
) ;
72
80
73
81
constructor (
74
82
private readonly clientId : string ,
75
83
private readonly clientSecret : string ,
76
- private readonly apiKey : string ,
84
+ private readonly apiKey : string
77
85
) {
78
86
super ( ) ;
79
87
this . axiosInstance = axios . create ( {
80
88
baseURL : MotApiSdk . BASE_URL ,
81
89
headers : {
82
- "X-API-Key" : this . apiKey ,
83
- } ,
90
+ "X-API-Key" : this . apiKey
91
+ }
84
92
} ) ;
85
93
86
94
this . setupInterceptors ( ) ;
95
+ this . resetDailyQuota ( ) ;
87
96
}
88
97
89
98
private setupInterceptors ( ) : void {
90
99
this . axiosInstance . interceptors . request . use (
91
- async ( config ) => {
100
+ async config => {
101
+ await this . waitForRateLimit ( ) ;
92
102
config . headers [ "Authorization" ] = `Bearer ${ await this . getToken ( ) } ` ;
93
103
return config ;
94
104
} ,
95
- ( error ) => Promise . reject ( error ) ,
105
+ error => Promise . reject ( error )
96
106
) ;
97
107
98
108
this . axiosInstance . interceptors . response . use (
99
- ( response ) => response ,
100
- ( error ) => this . handleApiError ( error ) ,
109
+ response => response ,
110
+ error => this . handleApiError ( error )
101
111
) ;
102
112
}
103
113
114
+ private async waitForRateLimit ( ) : Promise < void > {
115
+ while ( ! this . checkRateLimits ( ) ) {
116
+ await new Promise ( resolve => setTimeout ( resolve , 100 ) ) ;
117
+ }
118
+ this . updateRateLimits ( ) ;
119
+ }
120
+
121
+ private checkRateLimits ( ) : boolean {
122
+ const now = Date . now ( ) ;
123
+
124
+ // Check daily quota
125
+ if ( this . dailyQuota <= 0 && now < this . dailyQuotaReset ) {
126
+ return false ;
127
+ }
128
+
129
+ // Check burst limit
130
+ if ( this . burstTokens <= 0 ) {
131
+ return false ;
132
+ }
133
+
134
+ // Check RPS limit
135
+ const oneSecondAgo = now - 1000 ;
136
+ const requestsLastSecond = this . requestTimestamps . filter (
137
+ t => t > oneSecondAgo
138
+ ) . length ;
139
+ if ( requestsLastSecond >= this . rpsLimit ) {
140
+ return false ;
141
+ }
142
+
143
+ return true ;
144
+ }
145
+
146
+ private updateRateLimits ( ) : void {
147
+ const now = Date . now ( ) ;
148
+
149
+ // Update daily quota
150
+ if ( now >= this . dailyQuotaReset ) {
151
+ this . resetDailyQuota ( ) ;
152
+ }
153
+ this . dailyQuota -- ;
154
+
155
+ // Update burst tokens
156
+ this . burstTokens = Math . min ( this . burstTokens + 1 , this . burstLimit ) ;
157
+ this . burstTokens -- ;
158
+
159
+ // Update RPS tracking
160
+ this . requestTimestamps . push ( now ) ;
161
+ this . requestTimestamps = this . requestTimestamps . filter ( t => t > now - 1000 ) ;
162
+ }
163
+
164
+ private resetDailyQuota ( ) : void {
165
+ const now = Date . now ( ) ;
166
+ this . dailyQuota = 500000 ;
167
+ this . dailyQuotaReset = now + 24 * 60 * 60 * 1000 ; // Reset after 24 hours
168
+ }
169
+
104
170
private async getToken ( ) : Promise < string > {
105
171
if ( this . token && this . tokenExpiry > Date . now ( ) ) {
106
172
return this . token ;
@@ -111,17 +177,17 @@ class MotApiSdk extends EventEmitter {
111
177
grant_type : "client_credentials" ,
112
178
client_id : this . clientId ,
113
179
client_secret : this . clientSecret ,
114
- scope : MotApiSdk . SCOPE_URL ,
180
+ scope : MotApiSdk . SCOPE_URL
115
181
} ) ;
116
182
117
183
const tokenResponse = await axios . post < TokenResponse > (
118
184
MotApiSdk . TOKEN_URL ,
119
185
params ,
120
186
{
121
187
headers : {
122
- "Content-Type" : "application/x-www-form-urlencoded" ,
123
- } ,
124
- } ,
188
+ "Content-Type" : "application/x-www-form-urlencoded"
189
+ }
190
+ }
125
191
) ;
126
192
127
193
this . token = tokenResponse . data . access_token ;
@@ -151,7 +217,7 @@ class MotApiSdk extends EventEmitter {
151
217
private async makeRequest < T > (
152
218
endpoint : string ,
153
219
method : "GET" | "PUT" = "GET" ,
154
- data ?: any ,
220
+ data ?: any
155
221
) : Promise < T > {
156
222
try {
157
223
const response : AxiosResponse < T > = await this . axiosInstance . request ( {
@@ -161,7 +227,7 @@ class MotApiSdk extends EventEmitter {
161
227
headers :
162
228
method === "PUT"
163
229
? { "Content-Type" : "application/x-www-form-urlencoded" }
164
- : { } ,
230
+ : { }
165
231
} ) ;
166
232
this . emit ( "requestSuccess" , { endpoint, method } ) ;
167
233
return response . data ;
0 commit comments