This commit is contained in:
@@ -157,6 +157,24 @@ describe('apiClient', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('does not refresh or emit auth changes for 401 responses without auth context', async () => {
|
||||
fetchMock.mockResolvedValueOnce(createResponseMock({ status: 401 }));
|
||||
|
||||
const response = await fetchWithApiAuth('/api/runtime/protected', {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'/api/runtime/protected',
|
||||
expect.objectContaining({
|
||||
credentials: 'same-origin',
|
||||
}),
|
||||
);
|
||||
expect(window.dispatchEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('retries transient get requests before unwrapping the response envelope', async () => {
|
||||
fetchMock
|
||||
.mockRejectedValueOnce(new TypeError('network unavailable'))
|
||||
|
||||
@@ -351,12 +351,15 @@ export function setStoredAccessToken(
|
||||
}
|
||||
|
||||
const nextToken = token.trim();
|
||||
const previousToken = getStoredAccessToken();
|
||||
if (nextToken) {
|
||||
window.localStorage.setItem(ACCESS_TOKEN_KEY, nextToken);
|
||||
} else {
|
||||
window.localStorage.removeItem(ACCESS_TOKEN_KEY);
|
||||
}
|
||||
if (options.emit !== false) {
|
||||
|
||||
// 只有登录态令牌真的发生变化时才广播事件,避免无意义的鉴权重算。
|
||||
if (options.emit !== false && previousToken !== nextToken) {
|
||||
emitAuthStateChange();
|
||||
}
|
||||
}
|
||||
@@ -370,8 +373,11 @@ export function clearStoredAccessToken(
|
||||
return;
|
||||
}
|
||||
|
||||
const previousToken = getStoredAccessToken();
|
||||
window.localStorage.removeItem(ACCESS_TOKEN_KEY);
|
||||
if (options.emit !== false) {
|
||||
|
||||
// 未登录态下重复清空 token 不应触发状态刷新,否则会放大 401 循环。
|
||||
if (options.emit !== false && previousToken) {
|
||||
emitAuthStateChange();
|
||||
}
|
||||
}
|
||||
@@ -487,14 +493,20 @@ export async function fetchWithApiAuth(
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
const requestHeaders = withAuthorizationHeaders(init.headers, options);
|
||||
const hasAuthHeader = Boolean(
|
||||
requestHeaders.Authorization?.trim() ||
|
||||
requestHeaders.authorization?.trim(),
|
||||
);
|
||||
const response = await fetch(input, {
|
||||
credentials: 'same-origin',
|
||||
...init,
|
||||
headers: withAuthorizationHeaders(init.headers, options),
|
||||
headers: requestHeaders,
|
||||
});
|
||||
|
||||
if (
|
||||
response.status === 401 &&
|
||||
hasAuthHeader &&
|
||||
!options.skipAuth &&
|
||||
!options.skipRefresh &&
|
||||
!refreshAttempted
|
||||
|
||||
Reference in New Issue
Block a user