@@ -24,6 +24,7 @@ import type {
2424 MobileDeviceSettings ,
2525 PushNotificationRegistration ,
2626 PushNotificationRegistrationData ,
27+ RateLimit ,
2728 RunningTimeAggregation ,
2829 RunningTimes ,
2930 RunningTimesSummaryOnly ,
@@ -35,7 +36,7 @@ import type {
3536} from "./types" ;
3637
3738import { Agent } from "https" ;
38- import axios , { Method } from "axios" ;
39+ import axios , { AxiosHeaders , Method } from "axios" ;
3940import { Semaphore } from "./semaphore" ;
4041import { AuthTimeout , InvalidRefreshToken , NotAuthenticated , TadoError } from "./types" ;
4142
@@ -49,6 +50,7 @@ export class BaseTado {
4950 #token?: Token | undefined ;
5051 #tokenCallback?: ( token : Token ) => void ;
5152 #semaphore: Semaphore ;
53+ #ratelimit: RateLimit | undefined = undefined ;
5254
5355 constructor ( ) {
5456 this . #httpsAgent = new Agent ( { keepAlive : true } ) ;
@@ -165,6 +167,15 @@ export class BaseTado {
165167 this . #tokenCallback = cb ;
166168 }
167169
170+ /**
171+ * Get the Tado API rate limit information
172+ *
173+ * @returns `RateLimit` if an API call has occured
174+ */
175+ get ratelimit ( ) : RateLimit | undefined {
176+ return this . #ratelimit;
177+ }
178+
168179 /**
169180 * Authenticate with the Oauth server. A refresh token may be supplied to bypass the device auth
170181 * flow if it is still valid, otherwise the device flow is initiaited.
@@ -248,6 +259,52 @@ export class BaseTado {
248259 request . data = data ;
249260 }
250261 const response = await axios < R > ( request ) ;
262+
263+ const headers = response . headers ;
264+ if ( headers instanceof AxiosHeaders ) {
265+ let ratelimit : RateLimit = {
266+ policy : {
267+ quota : 0 ,
268+ window : 0 ,
269+ } ,
270+ current : {
271+ remaining : 0 ,
272+ resetsIn : undefined ,
273+ } ,
274+ } ;
275+
276+ headers
277+ . get ( "ratelimit-policy" )
278+ ?. toString ( )
279+ . split ( ";" )
280+ . forEach ( ( v ) => {
281+ if ( v . includes ( "=" ) ) {
282+ const parts = v . split ( "=" ) ;
283+ if ( parts [ 0 ] == "q" ) {
284+ ratelimit . policy . quota = Number . parseInt ( parts [ 1 ] ) ;
285+ } else if ( parts [ 0 ] == "w" ) {
286+ ratelimit . policy . window = Number . parseInt ( parts [ 1 ] ) ;
287+ }
288+ }
289+ } ) ;
290+ headers
291+ . get ( "ratelimit" )
292+ ?. toString ( )
293+ . split ( ";" )
294+ . forEach ( ( v ) => {
295+ if ( v . includes ( "=" ) ) {
296+ const parts = v . split ( "=" ) ;
297+ if ( parts [ 0 ] == "r" ) {
298+ ratelimit . current . remaining = Number . parseInt ( parts [ 1 ] ) ;
299+ } else if ( parts [ 0 ] == "t" ) {
300+ ratelimit . current . resetsIn = Number . parseInt ( parts [ 1 ] ) ;
301+ }
302+ }
303+ } ) ;
304+
305+ this . #ratelimit = ratelimit ;
306+ }
307+
251308 return response . data ;
252309 }
253310
0 commit comments