Skip to content

Commit

Permalink
feat(auth): Handle refreshAuth rejections gracefully (#3307)
Browse files Browse the repository at this point in the history
  • Loading branch information
kitten authored Jul 12, 2023
1 parent 1cf2e2f commit 657bd0c
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/light-kangaroos-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/exchange-auth': patch
---

Handle `refreshAuth` rejections and pass the resulting error on to `OperationResult`s on the authentication queue.
34 changes: 34 additions & 0 deletions exchanges/auth/src/authExchange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,3 +442,37 @@ it('does not infinitely retry authentication when an operation did error', async
'final-token'
);
});

it('passes on failing refreshAuth() errors to results', async () => {
const { exchangeArgs, result } = makeExchangeArgs();

const didAuthError = vi.fn().mockReturnValue(true);
const willAuthError = vi.fn().mockReturnValue(true);

const res = await pipe(
fromValue(queryOperation),
authExchange(async utils => {
const token = 'initial-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
didAuthError,
willAuthError,
async refreshAuth() {
throw new Error('test');
},
};
})(exchangeArgs),
take(1),
toPromise
);

expect(result).toHaveBeenCalledTimes(0);
expect(didAuthError).toHaveBeenCalledTimes(0);
expect(willAuthError).toHaveBeenCalledTimes(1);

expect(res.error).toMatchInlineSnapshot('[CombinedError: [Network] test]');
});
60 changes: 38 additions & 22 deletions exchanges/auth/src/authExchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import {
createRequest,
makeOperation,
makeErrorResult,
Operation,
OperationContext,
OperationResult,
Expand Down Expand Up @@ -202,17 +203,26 @@ export function authExchange(
return ({ client, forward }) => {
const bypassQueue = new Set<OperationInstance | undefined>();
const retries = makeSubject<Operation>();
const errors = makeSubject<OperationResult>();

let retryQueue = new Map<number, Operation>();

function flushQueue(_config?: AuthConfig | undefined) {
if (_config) config = _config;
function flushQueue() {
authPromise = undefined;
const queue = retryQueue;
retryQueue = new Map();
queue.forEach(retries.next);
}

function errorQueue(error: Error) {
authPromise = undefined;
const queue = retryQueue;
retryQueue = new Map();
queue.forEach(operation => {
errors.next(makeErrorResult(operation, error));
});
}

let authPromise: Promise<void> | void;
let config: AuthConfig | null = null;

Expand Down Expand Up @@ -270,7 +280,10 @@ export function authExchange(
},
})
)
.then(flushQueue);
.then((_config: AuthConfig) => {
if (_config) config = _config;
flushQueue();
});

function refreshAuth(operation: Operation) {
// add to retry queue to try again later
Expand All @@ -281,7 +294,7 @@ export function authExchange(

// check that another operation isn't already doing refresh
if (config && !authPromise) {
authPromise = config.refreshAuth().finally(flushQueue);
authPromise = config.refreshAuth().then(flushQueue).catch(errorQueue);
}
}

Expand Down Expand Up @@ -341,26 +354,29 @@ export function authExchange(

const result$ = pipe(opsWithAuth$, forward);

return pipe(
result$,
filter(result => {
if (
!bypassQueue.has(result.operation.context._instance) &&
result.error &&
didAuthError(result) &&
!result.operation.context.authAttempt
) {
refreshAuth(result.operation);
return false;
}
return merge([
errors.source,
pipe(
result$,
filter(result => {
if (
!bypassQueue.has(result.operation.context._instance) &&
result.error &&
didAuthError(result) &&
!result.operation.context.authAttempt
) {
refreshAuth(result.operation);
return false;
}

if (bypassQueue.has(result.operation.context._instance)) {
bypassQueue.delete(result.operation.context._instance);
}
if (bypassQueue.has(result.operation.context._instance)) {
bypassQueue.delete(result.operation.context._instance);
}

return true;
})
);
return true;
})
),
]);
};
};
}

0 comments on commit 657bd0c

Please sign in to comment.