import type { Cookie } from "@yaakapp-internal/models"; import { cookieJarsAtom, patchModel } from "@yaakapp-internal/models"; import { formatDate } from "date-fns/format"; import { useAtomValue } from "jotai"; import { type ComponentProps, useMemo, useState } from "react"; import { showDialog } from "../lib/dialog"; import { jotaiStore } from "../lib/jotai"; import { cookieDomain } from "../lib/model_util"; import { Icon, SplitLayout, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, TruncatedWideTableCell, } from "@yaakapp-internal/ui"; import { IconButton } from "./core/IconButton"; import { Checkbox } from "./core/Checkbox"; import classNames from "classnames"; import { EventDetailHeader } from "./core/EventViewer"; import { KeyValueRow, KeyValueRows } from "./core/KeyValueRow"; import { EmptyStateText } from "./EmptyStateText"; import { PlainInput } from "./core/PlainInput"; import { showAlert } from "../lib/alert"; interface Props { cookieJarId: string | null; } export const CookieDialog = ({ cookieJarId }: Props) => { const cookieJars = useAtomValue(cookieJarsAtom); const cookieJar = cookieJars?.find((c) => c.id === cookieJarId); const [filter, setFilter] = useState(""); const [filterUpdateKey, setFilterUpdateKey] = useState(0); const [selectedCookieKey, setSelectedCookieKey] = useState(null); const [editingCookieKey, setEditingCookieKey] = useState(null); const [draftCookie, setDraftCookie] = useState(null); const filteredCookies = useMemo(() => { return cookieJar?.cookies.filter((cookie) => cookieMatchesFilter(cookie, filter)) ?? []; }, [cookieJar?.cookies, filter]); const selectedCookie = useMemo( () => selectedCookieKey == null ? null : (filteredCookies.find((cookie) => cookieKey(cookie) === selectedCookieKey) ?? null), [filteredCookies, selectedCookieKey], ); const detailCookie = draftCookie ?? selectedCookie; const isCreatingCookie = editingCookieKey === NEW_COOKIE_KEY; const isEditingCookie = draftCookie != null; const handleAddCookie = () => { setSelectedCookieKey(null); setEditingCookieKey(NEW_COOKIE_KEY); setDraftCookie(newCookieDraft()); }; const handleEditCookie = () => { if (selectedCookie == null) { return; } setEditingCookieKey(cookieKey(selectedCookie)); setDraftCookie(selectedCookie); }; const handleCancelEdit = () => { if (isCreatingCookie) { setSelectedCookieKey(null); } setEditingCookieKey(null); setDraftCookie(null); }; const handleCloseDetails = () => { if (isEditingCookie) { handleCancelEdit(); return; } setSelectedCookieKey(null); }; const handleSaveCookie = () => { if (cookieJar == null || draftCookie == null) { return; } const nextCookie = normalizeCookie(draftCookie); if (nextCookie.name.trim().length === 0) { showAlert({ id: "invalid-cookie-name", title: "Invalid Cookie", body: "Cookie name is required.", }); return; } const nextCookieKey = cookieKey(nextCookie); const nextCookies = cookieJar.cookies.filter((cookie) => { const key = cookieKey(cookie); if (editingCookieKey != null && key === editingCookieKey) { return false; } return key !== nextCookieKey; }); patchModel(cookieJar, { cookies: [...nextCookies, nextCookie] }); setSelectedCookieKey(nextCookieKey); setEditingCookieKey(null); setDraftCookie(null); }; if (cookieJar == null) { return
No cookie jar selected
; } return (
0 && ( { setFilter(""); setFilterUpdateKey((key) => key + 1); }} /> ) } />
{cookieJar.cookies.length === 0 && detailCookie == null ? ( Cookies will appear when a response includes a Set-Cookie header. ) : filteredCookies.length === 0 && detailCookie == null ? ( No cookies match the current filter. ) : ( filteredCookies.length === 0 ? (
No cookies match the current filter.
) : ( Name Value Domain Path Expires Size HTTP Only Secure Same Site { setSelectedCookieKey(null); setEditingCookieKey(null); setDraftCookie(null); patchModel(cookieJar, { cookies: [] }); }} /> {filteredCookies.map((c: Cookie) => { const key = cookieKey(c); const isSelected = key === selectedCookieKey; return ( { setSelectedCookieKey(key); setEditingCookieKey(null); setDraftCookie(null); }} > {c.name} {c.value} {cookieDomain(c)} {c.path} {cookieExpires(c)} {cookieSize(c)} {c.sameSite} { event.stopPropagation(); if (isSelected) { setSelectedCookieKey(null); } if (editingCookieKey === key) { setEditingCookieKey(null); setDraftCookie(null); } patchModel(cookieJar, { cookies: cookieJar.cookies.filter( (c2: Cookie) => cookieKey(c2) !== key, ), }); }} /> ); })}
) } secondSlot={ detailCookie == null ? null : ({ style }) => (
{isEditingCookie ? ( ) : ( )}
) } /> )}
); }; CookieDialog.show = (cookieJarId: string | null) => { const cookieJar = jotaiStore.get(cookieJarsAtom)?.find((jar) => jar.id === cookieJarId); if (cookieJar == null) { showAlert({ id: "invalid-jar", body: `Failed to find cookie jar for ID: ${cookieJarId}`, title: "Invalid Cookie Jar", }); return; } showDialog({ id: "cookies", title: `${cookieJar.name} Cookies`, size: "full", render: () => , }); }; function CookieDetails({ cookie }: { cookie: Cookie }) { return (
{cookie.name}
{cookie.value}
{cookieDomain(cookie)} {cookie.path} {cookieExpires(cookie)} {cookieSize(cookie)} {cookie.httpOnly ? "Yes" : "No"} {cookie.secure ? "Yes" : "No"} {cookie.sameSite && ( {cookie.sameSite} )}
); } function CookieEditor({ cookie, onChange, }: { cookie: Cookie; onChange: (cookie: Cookie) => void; }) { const sessionCookie = cookie.expires === "SessionEnd"; return (
onChange({ ...cookie, name })} /> onChange({ ...cookie, value })} /> onChange(cookieWithDomain(cookie, domain))} /> onChange({ ...cookie, path })} />
onChange({ ...cookie, expires: checked ? "SessionEnd" : cookieExpiresFromInput(defaultCookieExpiresInputValue()), }) } /> onChange({ ...cookie, expires: cookieExpiresFromInput(value) })} />
{cookieSize(cookie)} onChange({ ...cookie, httpOnly })} /> onChange({ ...cookie, secure })} />
); } function CookieKeyValueRow({ labelClassName, ...props }: ComponentProps) { return ( ); } function CookieTextInput({ autoFocus, disabled, onChange, placeholder, required, value, }: { autoFocus?: boolean; disabled?: boolean; onChange: (value: string) => void; placeholder?: string; required?: boolean; value: string; }) { return ( onChange(event.target.value)} placeholder={placeholder} required={required} type="text" value={value} /> ); } function CookieTextarea({ onChange, value }: { onChange: (value: string) => void; value: string }) { return (