@@ -125,7 +125,7 @@ describe("FetchHttpApi", () => {
125
125
) . resolves . toBe ( text ) ;
126
126
} ) ;
127
127
128
- it ( "should send token via query params if useAuthorizationHeader=false" , ( ) => {
128
+ it ( "should send token via query params if useAuthorizationHeader=false" , async ( ) => {
129
129
const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
130
130
const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
131
131
baseUrl,
@@ -134,19 +134,19 @@ describe("FetchHttpApi", () => {
134
134
accessToken : "token" ,
135
135
useAuthorizationHeader : false ,
136
136
} ) ;
137
- api . authedRequest ( Method . Get , "/path" ) ;
137
+ await api . authedRequest ( Method . Get , "/path" ) ;
138
138
expect ( fetchFn . mock . calls [ 0 ] [ 0 ] . searchParams . get ( "access_token" ) ) . toBe ( "token" ) ;
139
139
} ) ;
140
140
141
- it ( "should send token via headers by default" , ( ) => {
141
+ it ( "should send token via headers by default" , async ( ) => {
142
142
const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
143
143
const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
144
144
baseUrl,
145
145
prefix,
146
146
fetchFn,
147
147
accessToken : "token" ,
148
148
} ) ;
149
- api . authedRequest ( Method . Get , "/path" ) ;
149
+ await api . authedRequest ( Method . Get , "/path" ) ;
150
150
expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Authorization" ] ) . toBe ( "Bearer token" ) ;
151
151
} ) ;
152
152
@@ -163,7 +163,7 @@ describe("FetchHttpApi", () => {
163
163
expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Authorization" ] ) . toBeFalsy ( ) ;
164
164
} ) ;
165
165
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 ( ) => {
167
167
const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
168
168
const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
169
169
baseUrl,
@@ -172,12 +172,12 @@ describe("FetchHttpApi", () => {
172
172
accessToken : "token" ,
173
173
useAuthorizationHeader : true ,
174
174
} ) ;
175
- api . authedRequest ( Method . Get , "/path" , { access_token : "123" } ) ;
175
+ await api . authedRequest ( Method . Get , "/path" , { access_token : "123" } ) ;
176
176
expect ( fetchFn . mock . calls [ 0 ] [ 0 ] . searchParams . get ( "access_token" ) ) . toBeFalsy ( ) ;
177
177
expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Authorization" ] ) . toBe ( "Bearer token" ) ;
178
178
} ) ;
179
179
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 ( ) => {
181
181
const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
182
182
const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
183
183
baseUrl,
@@ -186,11 +186,11 @@ describe("FetchHttpApi", () => {
186
186
accessToken : "token" ,
187
187
useAuthorizationHeader : false ,
188
188
} ) ;
189
- api . authedRequest ( Method . Get , "/path" , { access_token : "RealToken" } ) ;
189
+ await api . authedRequest ( Method . Get , "/path" , { access_token : "RealToken" } ) ;
190
190
expect ( fetchFn . mock . calls [ 0 ] [ 0 ] . searchParams . get ( "access_token" ) ) . toBe ( "RealToken" ) ;
191
191
} ) ;
192
192
193
- it ( "should not override manually specified access token via header" , ( ) => {
193
+ it ( "should not override manually specified access token via header" , async ( ) => {
194
194
const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
195
195
const api = new FetchHttpApi ( new TypedEventEmitter < any , any > ( ) , {
196
196
baseUrl,
@@ -199,16 +199,16 @@ describe("FetchHttpApi", () => {
199
199
accessToken : "token" ,
200
200
useAuthorizationHeader : true ,
201
201
} ) ;
202
- api . authedRequest ( Method . Get , "/path" , undefined , undefined , {
202
+ await api . authedRequest ( Method . Get , "/path" , undefined , undefined , {
203
203
headers : { Authorization : "Bearer RealToken" } ,
204
204
} ) ;
205
205
expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Authorization" ] ) . toBe ( "Bearer RealToken" ) ;
206
206
} ) ;
207
207
208
- it ( "should not override Accept header" , ( ) => {
208
+ it ( "should not override Accept header" , async ( ) => {
209
209
const fetchFn = jest . fn ( ) . mockResolvedValue ( { ok : true } ) ;
210
210
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 , {
212
212
headers : { Accept : "text/html" } ,
213
213
} ) ;
214
214
expect ( fetchFn . mock . calls [ 0 ] [ 1 ] . headers [ "Accept" ] ) . toBe ( "text/html" ) ;
@@ -468,4 +468,61 @@ describe("FetchHttpApi", () => {
468
468
]
469
469
` ) ;
470
470
} ) ;
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
+ } ) ;
471
528
} ) ;
0 commit comments