@@ -125,7 +125,7 @@ describe("FetchHttpApi", () => {
125125 ) . resolves . toBe ( text ) ;
126126 } ) ;
127127
128- it ( "should send token via query params if useAuthorizationHeader=false" , ( ) => {
128+ it ( "should send token via query params if useAuthorizationHeader=false" , async ( ) => {
129129 const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
130130 const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
131131 baseUrl,
@@ -134,19 +134,19 @@ describe("FetchHttpApi", () => {
134134 accessToken : "token" ,
135135 useAuthorizationHeader : false ,
136136 } ) ;
137- api . authedRequest ( Method . Get , "/path" ) ;
137+ await api . authedRequest ( Method . Get , "/path" ) ;
138138 expect ( fetchFn . mock . calls [ 0 ] [ 0 ] . searchParams . get ( "access_token" ) ) . toBe ( "token" ) ;
139139 } ) ;
140140
141- it ( "should send token via headers by default" , ( ) => {
141+ it ( "should send token via headers by default" , async ( ) => {
142142 const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
143143 const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
144144 baseUrl,
145145 prefix,
146146 fetchFn,
147147 accessToken : "token" ,
148148 } ) ;
149- api . authedRequest ( Method . Get , "/path" ) ;
149+ await api . authedRequest ( Method . Get , "/path" ) ;
150150 expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Authorization" ] ) . toBe ( "Bearer token" ) ;
151151 } ) ;
152152
@@ -163,7 +163,7 @@ describe("FetchHttpApi", () => {
163163 expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Authorization" ] ) . toBeFalsy ( ) ;
164164 } ) ;
165165
166- it ( "should ensure no token is leaked out via query params if sending via headers" , ( ) => {
166+ it ( "should ensure no token is leaked out via query params if sending via headers" , async ( ) => {
167167 const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
168168 const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
169169 baseUrl,
@@ -172,12 +172,12 @@ describe("FetchHttpApi", () => {
172172 accessToken : "token" ,
173173 useAuthorizationHeader : true ,
174174 } ) ;
175- api . authedRequest ( Method . Get , "/path" , { access_token : "123" } ) ;
175+ await api . authedRequest ( Method . Get , "/path" , { access_token : "123" } ) ;
176176 expect ( fetchFn . mock . calls [ 0 ] [ 0 ] . searchParams . get ( "access_token" ) ) . toBeFalsy ( ) ;
177177 expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Authorization" ] ) . toBe ( "Bearer token" ) ;
178178 } ) ;
179179
180- it ( "should not override manually specified access token via query params" , ( ) => {
180+ it ( "should not override manually specified access token via query params" , async ( ) => {
181181 const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
182182 const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
183183 baseUrl,
@@ -186,11 +186,11 @@ describe("FetchHttpApi", () => {
186186 accessToken : "token" ,
187187 useAuthorizationHeader : false ,
188188 } ) ;
189- api . authedRequest ( Method . Get , "/path" , { access_token : "RealToken" } ) ;
189+ await api . authedRequest ( Method . Get , "/path" , { access_token : "RealToken" } ) ;
190190 expect ( fetchFn . mock . calls [ 0 ] [ 0 ] . searchParams . get ( "access_token" ) ) . toBe ( "RealToken" ) ;
191191 } ) ;
192192
193- it ( "should not override manually specified access token via header" , ( ) => {
193+ it ( "should not override manually specified access token via header" , async ( ) => {
194194 const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
195195 const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
196196 baseUrl,
@@ -199,16 +199,16 @@ describe("FetchHttpApi", () => {
199199 accessToken : "token" ,
200200 useAuthorizationHeader : true ,
201201 } ) ;
202- api . authedRequest ( Method . Get , "/path" , undefined , undefined , {
202+ await api . authedRequest ( Method . Get , "/path" , undefined , undefined , {
203203 headers : { Authorization : "Bearer RealToken" } ,
204204 } ) ;
205205 expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Authorization" ] ) . toBe ( "Bearer RealToken" ) ;
206206 } ) ;
207207
208- it ( "should not override Accept header" , ( ) => {
208+ it ( "should not override Accept header" , async ( ) => {
209209 const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
210210 const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , { baseUrl, prefix, fetchFn } ) ;
211- api . authedRequest ( Method . Get , "/path" , undefined , undefined , {
211+ await api . authedRequest ( Method . Get , "/path" , undefined , undefined , {
212212 headers : { Accept : "text/html" } ,
213213 } ) ;
214214 expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Accept" ] ) . toBe ( "text/html" ) ;
@@ -468,4 +468,61 @@ describe("FetchHttpApi", () => {
468468 ]
469469 ` ) ;
470470 } ) ;
471+
472+ it ( "should not make multiple concurrent refresh token requests" , async ( ) => {
473+ const tokenInactiveError = new MatrixError ( { errcode : "M_UNKNOWN_TOKEN" , error : "Token is not active" } , 401 ) ;
474+
475+ const deferredTokenRefresh = defer < { accessToken : string ; refreshToken : string } > ( ) ;
476+ const fetchFn = jest . fn ( ) . mockResolvedValue ( {
477+ ok : false ,
478+ status : tokenInactiveError . httpStatus ,
479+ async text ( ) {
480+ return JSON . stringify ( tokenInactiveError . data ) ;
481+ } ,
482+ async json ( ) {
483+ return tokenInactiveError . data ;
484+ } ,
485+ headers : {
486+ get : jest . fn ( ) . mockReturnValue ( "application/json" ) ,
487+ } ,
488+ } ) ;
489+ const tokenRefreshFunction = jest . fn ( ) . mockReturnValue ( deferredTokenRefresh . promise ) ;
490+
491+ const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
492+ baseUrl,
493+ prefix,
494+ fetchFn,
495+ doNotAttemptTokenRefresh : false ,
496+ tokenRefreshFunction,
497+ accessToken : "ACCESS_TOKEN" ,
498+ refreshToken : "REFRESH_TOKEN" ,
499+ } ) ;
500+
501+ const prom1 = api . authedRequest ( Method . Get , "/path1" ) ;
502+ const prom2 = api . authedRequest ( Method . Get , "/path2" ) ;
503+
504+ await jest . advanceTimersByTimeAsync ( 10 ) ; // wait for requests to fire
505+ expect ( fetchFn ) . toHaveBeenCalledTimes ( 2 ) ;
506+ fetchFn . mockResolvedValue ( {
507+ ok : true ,
508+ status : 200 ,
509+ async text ( ) {
510+ return "{}" ;
511+ } ,
512+ async json ( ) {
513+ return { } ;
514+ } ,
515+ headers : {
516+ get : jest . fn ( ) . mockReturnValue ( "application/json" ) ,
517+ } ,
518+ } ) ;
519+ deferredTokenRefresh . resolve ( { accessToken : "NEW_ACCESS_TOKEN" , refreshToken : "NEW_REFRESH_TOKEN" } ) ;
520+
521+ await prom1 ;
522+ await prom2 ;
523+ expect ( fetchFn ) . toHaveBeenCalledTimes ( 4 ) ; // 2 original calls + 2 retries
524+ expect ( tokenRefreshFunction ) . toHaveBeenCalledTimes ( 1 ) ;
525+ expect ( api . opts . accessToken ) . toBe ( "NEW_ACCESS_TOKEN" ) ;
526+ expect ( api . opts . refreshToken ) . toBe ( "NEW_REFRESH_TOKEN" ) ;
527+ } ) ;
471528} ) ;
0 commit comments