mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 09:38:29 +02:00
Show sent/received cookie counts in Cookies tab
- Add getCookieCounts function to parse cookie headers and count individual cookies (not just headers) - Deduplicates by cookie name using Sets - Display as sent/received format like Headers tab - Add showZero to CountBadge so 0/3 displays properly - Add tests for getCookieCounts
This commit is contained in:
@@ -9,7 +9,7 @@ import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
|||||||
import { useResponseBodyBytes, useResponseBodyText } from '../hooks/useResponseBodyText';
|
import { useResponseBodyBytes, useResponseBodyText } from '../hooks/useResponseBodyText';
|
||||||
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
||||||
import { getMimeTypeFromContentType } from '../lib/contentType';
|
import { getMimeTypeFromContentType } from '../lib/contentType';
|
||||||
import { getContentTypeFromHeaders } from '../lib/model_util';
|
import { getCookieCounts, getContentTypeFromHeaders } from '../lib/model_util';
|
||||||
import { ConfirmLargeResponse } from './ConfirmLargeResponse';
|
import { ConfirmLargeResponse } from './ConfirmLargeResponse';
|
||||||
import { ConfirmLargeResponseRequest } from './ConfirmLargeResponseRequest';
|
import { ConfirmLargeResponseRequest } from './ConfirmLargeResponseRequest';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
@@ -67,20 +67,10 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
|||||||
|
|
||||||
const responseEvents = useHttpResponseEvents(activeResponse);
|
const responseEvents = useHttpResponseEvents(activeResponse);
|
||||||
|
|
||||||
const cookieCount = useMemo(() => {
|
const cookieCounts = useMemo(
|
||||||
if (!responseEvents.data) return 0;
|
() => getCookieCounts(responseEvents.data),
|
||||||
let count = 0;
|
[responseEvents.data],
|
||||||
for (const event of responseEvents.data) {
|
);
|
||||||
const e = event.event;
|
|
||||||
if (
|
|
||||||
(e.type === 'header_up' && e.name.toLowerCase() === 'cookie') ||
|
|
||||||
(e.type === 'header_down' && e.name.toLowerCase() === 'set-cookie')
|
|
||||||
) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}, [responseEvents.data]);
|
|
||||||
|
|
||||||
const tabs = useMemo<TabItem[]>(
|
const tabs = useMemo<TabItem[]>(
|
||||||
() => [
|
() => [
|
||||||
@@ -107,15 +97,19 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
|||||||
label: 'Headers',
|
label: 'Headers',
|
||||||
rightSlot: (
|
rightSlot: (
|
||||||
<CountBadge
|
<CountBadge
|
||||||
count2={activeResponse?.headers.length ?? 0}
|
|
||||||
count={activeResponse?.requestHeaders.length ?? 0}
|
count={activeResponse?.requestHeaders.length ?? 0}
|
||||||
|
count2={activeResponse?.headers.length ?? 0}
|
||||||
|
showZero
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: TAB_COOKIES,
|
value: TAB_COOKIES,
|
||||||
label: 'Cookies',
|
label: 'Cookies',
|
||||||
rightSlot: cookieCount > 0 ? <CountBadge count={cookieCount} /> : null,
|
rightSlot:
|
||||||
|
cookieCounts.sent > 0 || cookieCounts.received > 0 ? (
|
||||||
|
<CountBadge count={cookieCounts.sent} count2={cookieCounts.received} showZero />
|
||||||
|
) : null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: TAB_TIMELINE,
|
value: TAB_TIMELINE,
|
||||||
@@ -127,7 +121,8 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
|||||||
activeResponse?.headers,
|
activeResponse?.headers,
|
||||||
activeResponse?.requestContentLength,
|
activeResponse?.requestContentLength,
|
||||||
activeResponse?.requestHeaders.length,
|
activeResponse?.requestHeaders.length,
|
||||||
cookieCount,
|
cookieCounts.sent,
|
||||||
|
cookieCounts.received,
|
||||||
mimeType,
|
mimeType,
|
||||||
responseEvents.data?.length,
|
responseEvents.data?.length,
|
||||||
setViewMode,
|
setViewMode,
|
||||||
|
|||||||
93
src-web/lib/model_util.test.ts
Normal file
93
src-web/lib/model_util.test.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import type { HttpResponseEvent } from '@yaakapp-internal/models';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { getCookieCounts } from './model_util';
|
||||||
|
|
||||||
|
function makeEvent(
|
||||||
|
type: string,
|
||||||
|
name: string,
|
||||||
|
value: string,
|
||||||
|
): HttpResponseEvent {
|
||||||
|
return {
|
||||||
|
id: 'test',
|
||||||
|
model: 'http_response_event',
|
||||||
|
responseId: 'resp',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
event: { type, name, value } as HttpResponseEvent['event'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('getCookieCounts', () => {
|
||||||
|
test('returns zeros for undefined events', () => {
|
||||||
|
expect(getCookieCounts(undefined)).toEqual({ sent: 0, received: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns zeros for empty events', () => {
|
||||||
|
expect(getCookieCounts([])).toEqual({ sent: 0, received: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('counts single sent cookie', () => {
|
||||||
|
const events = [makeEvent('header_up', 'Cookie', 'session=abc123')];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 1, received: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('counts multiple sent cookies in one header', () => {
|
||||||
|
const events = [makeEvent('header_up', 'Cookie', 'a=1; b=2; c=3')];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 3, received: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('counts single received cookie', () => {
|
||||||
|
const events = [makeEvent('header_down', 'Set-Cookie', 'session=abc123; Path=/')];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 0, received: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('counts multiple received cookies from multiple headers', () => {
|
||||||
|
const events = [
|
||||||
|
makeEvent('header_down', 'Set-Cookie', 'a=1; Path=/'),
|
||||||
|
makeEvent('header_down', 'Set-Cookie', 'b=2; HttpOnly'),
|
||||||
|
makeEvent('header_down', 'Set-Cookie', 'c=3; Secure'),
|
||||||
|
];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 0, received: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deduplicates sent cookies by name', () => {
|
||||||
|
const events = [
|
||||||
|
makeEvent('header_up', 'Cookie', 'session=old'),
|
||||||
|
makeEvent('header_up', 'Cookie', 'session=new'),
|
||||||
|
];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 1, received: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deduplicates received cookies by name', () => {
|
||||||
|
const events = [
|
||||||
|
makeEvent('header_down', 'Set-Cookie', 'token=abc; Path=/'),
|
||||||
|
makeEvent('header_down', 'Set-Cookie', 'token=xyz; Path=/'),
|
||||||
|
];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 0, received: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('counts both sent and received cookies', () => {
|
||||||
|
const events = [
|
||||||
|
makeEvent('header_up', 'Cookie', 'a=1; b=2; c=3'),
|
||||||
|
makeEvent('header_down', 'Set-Cookie', 'x=10; Path=/'),
|
||||||
|
makeEvent('header_down', 'Set-Cookie', 'y=20; Path=/'),
|
||||||
|
makeEvent('header_down', 'Set-Cookie', 'z=30; Path=/'),
|
||||||
|
];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 3, received: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ignores non-cookie headers', () => {
|
||||||
|
const events = [
|
||||||
|
makeEvent('header_up', 'Content-Type', 'application/json'),
|
||||||
|
makeEvent('header_down', 'Content-Length', '123'),
|
||||||
|
];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 0, received: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles case-insensitive header names', () => {
|
||||||
|
const events = [
|
||||||
|
makeEvent('header_up', 'COOKIE', 'a=1'),
|
||||||
|
makeEvent('header_down', 'SET-COOKIE', 'b=2; Path=/'),
|
||||||
|
];
|
||||||
|
expect(getCookieCounts(events)).toEqual({ sent: 1, received: 1 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import type { AnyModel, Cookie, Environment, HttpResponseHeader } from '@yaakapp-internal/models';
|
import type {
|
||||||
|
AnyModel,
|
||||||
|
Cookie,
|
||||||
|
Environment,
|
||||||
|
HttpResponseEvent,
|
||||||
|
HttpResponseHeader,
|
||||||
|
} from '@yaakapp-internal/models';
|
||||||
import { getMimeTypeFromContentType } from './contentType';
|
import { getMimeTypeFromContentType } from './contentType';
|
||||||
|
|
||||||
export const BODY_TYPE_NONE = null;
|
export const BODY_TYPE_NONE = null;
|
||||||
@@ -59,3 +65,30 @@ export function isSubEnvironment(environment: Environment): boolean {
|
|||||||
export function isFolderEnvironment(environment: Environment): boolean {
|
export function isFolderEnvironment(environment: Environment): boolean {
|
||||||
return environment.parentModel === 'folder';
|
return environment.parentModel === 'folder';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCookieCounts(
|
||||||
|
events: HttpResponseEvent[] | undefined,
|
||||||
|
): { sent: number; received: number } {
|
||||||
|
if (!events) return { sent: 0, received: 0 };
|
||||||
|
|
||||||
|
// Use Sets to deduplicate by cookie name
|
||||||
|
const sentNames = new Set<string>();
|
||||||
|
const receivedNames = new Set<string>();
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
const e = event.event;
|
||||||
|
if (e.type === 'header_up' && e.name.toLowerCase() === 'cookie') {
|
||||||
|
// Parse "Cookie: name=value; name2=value2" format
|
||||||
|
for (const pair of e.value.split(';')) {
|
||||||
|
const name = pair.split('=')[0]?.trim();
|
||||||
|
if (name) sentNames.add(name);
|
||||||
|
}
|
||||||
|
} else if (e.type === 'header_down' && e.name.toLowerCase() === 'set-cookie') {
|
||||||
|
// Parse "Set-Cookie: name=value; ..." - first part before ; is name=value
|
||||||
|
const name = e.value.split(';')[0]?.split('=')[0]?.trim();
|
||||||
|
if (name) receivedNames.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sent: sentNames.size, received: receivedNames.size };
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user