Skip to content

Commit ba5d393

Browse files
authored
fix(chat): handle EasyAuth 302 redirect and improve auth error detection in useAgentCard (#8801)
* fix: handle EasyAuth 302 redirect in useAgentCard fetch * test: add tests for EasyAuth redirect and CORS error handling in useAgentCard * fix: use status codes for auth detection, remove dead opaqueredirect check, update tests * refactor: remove unused redirect manual option * Remove console log
1 parent 38a85ba commit ba5d393

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

apps/iframe-app/src/hooks/__tests__/useAgentCard.test.tsx

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ describe('useAgentCard', () => {
168168
const onUnauthorized = vi.fn();
169169
mockFetch.mockResolvedValueOnce({
170170
ok: false,
171+
status: 401,
171172
statusText: 'Unauthorized',
172173
});
173174

@@ -186,9 +187,68 @@ describe('useAgentCard', () => {
186187
expect(result.current.error?.message).toBe('Unauthorized');
187188
});
188189

190+
it('should call onUnauthorized on network/CORS error (e.g., EasyAuth 302 redirect)', async () => {
191+
const onUnauthorized = vi.fn();
192+
mockFetch.mockRejectedValueOnce(new TypeError('Failed to fetch'));
193+
194+
const config: UseAgentCardConfig = {
195+
apiUrl: 'https://api.example.com',
196+
onUnauthorized,
197+
};
198+
199+
const { result } = renderHook(() => useAgentCard(config), { wrapper });
200+
201+
await waitFor(() => {
202+
expect(result.current.isError).toBe(true);
203+
});
204+
205+
expect(onUnauthorized).toHaveBeenCalled();
206+
expect(result.current.error?.message).toBe('Unauthorized');
207+
});
208+
209+
it('should error with Unauthorized on network failure even without onUnauthorized callback', async () => {
210+
mockFetch.mockRejectedValueOnce(new TypeError('Failed to fetch'));
211+
212+
const config: UseAgentCardConfig = {
213+
apiUrl: 'https://api.example.com',
214+
};
215+
216+
const { result } = renderHook(() => useAgentCard(config), { wrapper });
217+
218+
await waitFor(() => {
219+
expect(result.current.isError).toBe(true);
220+
});
221+
222+
expect(result.current.error?.message).toBe('Unauthorized');
223+
});
224+
225+
it('should handle 403 forbidden response and call onUnauthorized', async () => {
226+
const onUnauthorized = vi.fn();
227+
mockFetch.mockResolvedValueOnce({
228+
ok: false,
229+
status: 403,
230+
statusText: 'Forbidden',
231+
});
232+
233+
const config: UseAgentCardConfig = {
234+
apiUrl: 'https://api.example.com',
235+
onUnauthorized,
236+
};
237+
238+
const { result } = renderHook(() => useAgentCard(config), { wrapper });
239+
240+
await waitFor(() => {
241+
expect(result.current.isError).toBe(true);
242+
});
243+
244+
expect(onUnauthorized).toHaveBeenCalled();
245+
expect(result.current.error?.message).toBe('Unauthorized');
246+
});
247+
189248
it('should handle non-unauthorized error responses', async () => {
190249
mockFetch.mockResolvedValueOnce({
191250
ok: false,
251+
status: 500,
192252
statusText: 'Internal Server Error',
193253
});
194254

@@ -202,7 +262,7 @@ describe('useAgentCard', () => {
202262
expect(result.current.isError).toBe(true);
203263
});
204264

205-
expect(result.current.error?.message).toBe('Failed to fetch agent card: Internal Server Error');
265+
expect(result.current.error?.message).toBe('Failed to fetch agent card: 500 Internal Server Error');
206266
});
207267

208268
it('should not fetch when disabled', async () => {

apps/iframe-app/src/hooks/useAgentCard.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ export interface UseAgentCardConfig {
99
onUnauthorized?: () => void | Promise<void>;
1010
}
1111

12+
async function handleUnauthorized(config: UseAgentCardConfig): Promise<never> {
13+
if (config.onUnauthorized) {
14+
await config.onUnauthorized();
15+
}
16+
throw new Error('Unauthorized');
17+
}
18+
1219
async function fetchAgentCard(config: UseAgentCardConfig): Promise<AgentCard> {
1320
const url = isDirectAgentCardUrl(config.apiUrl) ? config.apiUrl : `${config.apiUrl}/.well-known/agent-card.json`;
1421

@@ -28,16 +35,19 @@ async function fetchAgentCard(config: UseAgentCardConfig): Promise<AgentCard> {
2835
requestInit.credentials = 'include';
2936
}
3037

31-
const response = await fetch(url, requestInit);
38+
let response: Response;
39+
try {
40+
response = await fetch(url, requestInit);
41+
} catch {
42+
// Network error or CORS failure (e.g., EasyAuth 302 redirect to login page)
43+
return handleUnauthorized(config);
44+
}
3245

3346
if (!response.ok) {
34-
if (response.statusText === 'Unauthorized') {
35-
if (config.onUnauthorized) {
36-
await config.onUnauthorized();
37-
}
38-
throw new Error('Unauthorized');
47+
if (response.status === 401 || response.status === 403) {
48+
return handleUnauthorized(config);
3949
}
40-
throw new Error(`Failed to fetch agent card: ${response.statusText}`);
50+
throw new Error(`Failed to fetch agent card: ${response.status} ${response.statusText}`.trim());
4151
}
4252

4353
return (await response.json()) as AgentCard;

0 commit comments

Comments
 (0)