@@ -4,6 +4,7 @@ import {get, omit, isPlainObject, isString, defaults } from 'lodash';
44import { currentUserToken , getAPIURL , logoutUser } from '../common/utils' ;
55
66const APIServiceProvider = { } ;
7+ const throttlingListeners = new Set ( ) ;
78const RESOURCES = [
89 { name : 'concepts' , relations : [ ] } ,
910 { name : 'mappings' , relations : [ ] } ,
@@ -19,6 +20,60 @@ const RESOURCES = [
1920 { name : 'new' , relations : [ ] } ,
2021] ;
2122
23+ const getHeaderValue = ( headers = { } , key ) => {
24+ if ( headers && typeof headers . get === 'function' ) {
25+ const headerValue = headers . get ( key ) ?? headers . get ( key . toLowerCase ( ) ) ?? headers . get ( key . toUpperCase ( ) ) ;
26+ if ( headerValue !== undefined && headerValue !== null )
27+ return headerValue ;
28+ }
29+
30+ return headers ?. [ key ] ?? headers ?. [ key . toLowerCase ( ) ] ?? headers ?. [ key . toUpperCase ( ) ] ;
31+ } ;
32+
33+ export const getThrottlingDetails = response => {
34+ const retryAfter = Number ( getHeaderValue ( response ?. headers , 'retry-after' ) ) ;
35+ const minuteRemaining = Number ( getHeaderValue ( response ?. headers , 'x-limitremaining-minute' ) ) ;
36+ const dayRemaining = Number ( getHeaderValue ( response ?. headers , 'x-limitremaining-day' ) ) ;
37+ const hasMinuteRemaining = Number . isFinite ( minuteRemaining ) ;
38+ const hasDayRemaining = Number . isFinite ( dayRemaining ) ;
39+ let limitType = 'unknown' ;
40+ if ( hasMinuteRemaining && minuteRemaining <= 0 && hasDayRemaining && dayRemaining <= 0 )
41+ limitType = 'minute_and_day' ;
42+ else if ( hasMinuteRemaining && minuteRemaining <= 0 )
43+ limitType = 'minute' ;
44+ else if ( hasDayRemaining && dayRemaining <= 0 )
45+ limitType = 'day' ;
46+
47+ return {
48+ retryAfter : Number . isFinite ( retryAfter ) && retryAfter > 0 ? retryAfter : 0 ,
49+ limitType,
50+ minuteRemaining : hasMinuteRemaining ? minuteRemaining : null ,
51+ dayRemaining : hasDayRemaining ? dayRemaining : null ,
52+ response,
53+ } ;
54+ }
55+
56+ const notifyThrottlingListeners = response => {
57+ const details = getThrottlingDetails ( response ) ;
58+ throttlingListeners . forEach ( listener => listener ( details ) ) ;
59+ }
60+
61+ const createPendingRequest = ( ) => new Promise ( ( ) => { } )
62+
63+ const handleAPIError = ( error , raw = false ) => {
64+ if ( error ?. response ?. status === 401 && error ?. response ?. config ?. url ?. startsWith ( getAPIURL ( ) ) ) {
65+ logoutUser ( true )
66+ return { handled : true , result : undefined } ;
67+ }
68+
69+ if ( error ?. response ?. status === 429 ) {
70+ notifyThrottlingListeners ( error . response ) ;
71+ return { handled : true , result : raw ? error : createPendingRequest ( ) } ;
72+ }
73+
74+ return { handled : false } ;
75+ }
76+
2277class APIService {
2378 constructor ( name , id , relations ) {
2479 const apiURL = getAPIURL ( ) ;
@@ -77,14 +132,14 @@ class APIService {
77132 return axios ( request )
78133 . then ( response => response || null )
79134 . catch ( error => {
80- if ( error ?. response ?. status === 401 && error ?. response ?. config ?. url ?. startsWith ( getAPIURL ( ) ) ) {
81- logoutUser ( true )
82- } else {
83- if ( raw )
84- return error ;
135+ const { handled, result } = handleAPIError ( error , raw ) ;
136+ if ( handled )
137+ return result ;
85138
86- return error . response ? error . response . data : error . message ;
87- }
139+ if ( raw )
140+ return error ;
141+
142+ return error . response ? error . response . data : error . message ;
88143
89144 } ) ;
90145 }
@@ -108,7 +163,10 @@ class APIService {
108163 }
109164 let request = this . getRequest ( method , data , token , headers , query ) ;
110165 request = { ...request , ...omit ( config , [ 'headers' , 'query' ] ) } ;
111- return axios ( request ) ;
166+ return axios ( request ) . catch ( error => {
167+ handleAPIError ( error ) ;
168+ return Promise . reject ( error ) ;
169+ } ) ;
112170 } ;
113171
114172 getHeaders ( token , headers ) {
@@ -144,4 +202,9 @@ RESOURCES.forEach(resource => {
144202 APIServiceProvider [ resource . name ] = ( id , query ) => new APIService ( resource . name , id , resource . relations , query ) ;
145203} ) ;
146204
205+ APIServiceProvider . onThrottle = listener => {
206+ throttlingListeners . add ( listener ) ;
207+ return ( ) => throttlingListeners . delete ( listener ) ;
208+ } ;
209+
147210export default APIServiceProvider ;
0 commit comments