Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(auth): Handle refreshAuth rejections gracefully (#3307)

authored by

Phil Pluckthun and committed by
GitHub
657bd0c6 1cf2e2ff

+77 -22
+5
.changeset/light-kangaroos-smash.md
··· 1 + --- 2 + '@urql/exchange-auth': patch 3 + --- 4 + 5 + Handle `refreshAuth` rejections and pass the resulting error on to `OperationResult`s on the authentication queue.
+34
exchanges/auth/src/authExchange.test.ts
··· 442 442 'final-token' 443 443 ); 444 444 }); 445 + 446 + it('passes on failing refreshAuth() errors to results', async () => { 447 + const { exchangeArgs, result } = makeExchangeArgs(); 448 + 449 + const didAuthError = vi.fn().mockReturnValue(true); 450 + const willAuthError = vi.fn().mockReturnValue(true); 451 + 452 + const res = await pipe( 453 + fromValue(queryOperation), 454 + authExchange(async utils => { 455 + const token = 'initial-token'; 456 + return { 457 + addAuthToOperation(operation) { 458 + return utils.appendHeaders(operation, { 459 + Authorization: token, 460 + }); 461 + }, 462 + didAuthError, 463 + willAuthError, 464 + async refreshAuth() { 465 + throw new Error('test'); 466 + }, 467 + }; 468 + })(exchangeArgs), 469 + take(1), 470 + toPromise 471 + ); 472 + 473 + expect(result).toHaveBeenCalledTimes(0); 474 + expect(didAuthError).toHaveBeenCalledTimes(0); 475 + expect(willAuthError).toHaveBeenCalledTimes(1); 476 + 477 + expect(res.error).toMatchInlineSnapshot('[CombinedError: [Network] test]'); 478 + });
+38 -22
exchanges/auth/src/authExchange.ts
··· 13 13 import { 14 14 createRequest, 15 15 makeOperation, 16 + makeErrorResult, 16 17 Operation, 17 18 OperationContext, 18 19 OperationResult, ··· 202 203 return ({ client, forward }) => { 203 204 const bypassQueue = new Set<OperationInstance | undefined>(); 204 205 const retries = makeSubject<Operation>(); 206 + const errors = makeSubject<OperationResult>(); 205 207 206 208 let retryQueue = new Map<number, Operation>(); 207 209 208 - function flushQueue(_config?: AuthConfig | undefined) { 209 - if (_config) config = _config; 210 + function flushQueue() { 210 211 authPromise = undefined; 211 212 const queue = retryQueue; 212 213 retryQueue = new Map(); 213 214 queue.forEach(retries.next); 215 + } 216 + 217 + function errorQueue(error: Error) { 218 + authPromise = undefined; 219 + const queue = retryQueue; 220 + retryQueue = new Map(); 221 + queue.forEach(operation => { 222 + errors.next(makeErrorResult(operation, error)); 223 + }); 214 224 } 215 225 216 226 let authPromise: Promise<void> | void; ··· 270 280 }, 271 281 }) 272 282 ) 273 - .then(flushQueue); 283 + .then((_config: AuthConfig) => { 284 + if (_config) config = _config; 285 + flushQueue(); 286 + }); 274 287 275 288 function refreshAuth(operation: Operation) { 276 289 // add to retry queue to try again later ··· 281 294 282 295 // check that another operation isn't already doing refresh 283 296 if (config && !authPromise) { 284 - authPromise = config.refreshAuth().finally(flushQueue); 297 + authPromise = config.refreshAuth().then(flushQueue).catch(errorQueue); 285 298 } 286 299 } 287 300 ··· 341 354 342 355 const result$ = pipe(opsWithAuth$, forward); 343 356 344 - return pipe( 345 - result$, 346 - filter(result => { 347 - if ( 348 - !bypassQueue.has(result.operation.context._instance) && 349 - result.error && 350 - didAuthError(result) && 351 - !result.operation.context.authAttempt 352 - ) { 353 - refreshAuth(result.operation); 354 - return false; 355 - } 357 + return merge([ 358 + errors.source, 359 + pipe( 360 + result$, 361 + filter(result => { 362 + if ( 363 + !bypassQueue.has(result.operation.context._instance) && 364 + result.error && 365 + didAuthError(result) && 366 + !result.operation.context.authAttempt 367 + ) { 368 + refreshAuth(result.operation); 369 + return false; 370 + } 356 371 357 - if (bypassQueue.has(result.operation.context._instance)) { 358 - bypassQueue.delete(result.operation.context._instance); 359 - } 372 + if (bypassQueue.has(result.operation.context._instance)) { 373 + bypassQueue.delete(result.operation.context._instance); 374 + } 360 375 361 - return true; 362 - }) 363 - ); 376 + return true; 377 + }) 378 + ), 379 + ]); 364 380 }; 365 381 }; 366 382 }