|
|
|
|
@@ -1,7 +1,8 @@
|
|
|
|
|
import type { Cookie } from "@yaakapp-internal/models";
|
|
|
|
|
import { cookieJarsAtom, patchModel } from "@yaakapp-internal/models";
|
|
|
|
|
import type { Cookie, CookieDomain, CookieJar } from "@yaakapp-internal/models";
|
|
|
|
|
import { cookieJarsAtom, patchModelById } from "@yaakapp-internal/models";
|
|
|
|
|
import { useAtomValue } from "jotai";
|
|
|
|
|
import { cookieDomain } from "../lib/model_util";
|
|
|
|
|
import { showPromptForm } from "../lib/prompt-form";
|
|
|
|
|
import { Banner, InlineCode } from "@yaakapp-internal/ui";
|
|
|
|
|
import { IconButton } from "./core/IconButton";
|
|
|
|
|
|
|
|
|
|
@@ -9,6 +10,109 @@ interface Props {
|
|
|
|
|
cookieJarId: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function showAddCookieForm(cookieJarId: string): Promise<void> {
|
|
|
|
|
const result = await showPromptForm({
|
|
|
|
|
id: "add-cookie",
|
|
|
|
|
title: "Add Cookie",
|
|
|
|
|
size: "md",
|
|
|
|
|
inputs: [
|
|
|
|
|
{
|
|
|
|
|
name: "cookie_pairs",
|
|
|
|
|
label: "Cookie Attributes",
|
|
|
|
|
type: "key_value",
|
|
|
|
|
description:
|
|
|
|
|
"Add key-value pairs for the cookie. These will be combined into the cookie string.",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "domain_value",
|
|
|
|
|
label: "Domain",
|
|
|
|
|
type: "text",
|
|
|
|
|
placeholder: "example.com",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "hostOnly",
|
|
|
|
|
label: "Host Only",
|
|
|
|
|
type: "checkbox",
|
|
|
|
|
defaultValue: "true",
|
|
|
|
|
description:
|
|
|
|
|
"If enabled, cookie is restricted to the exact host. Otherwise, it applies to the domain and its subdomains.",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "path",
|
|
|
|
|
label: "Path",
|
|
|
|
|
type: "text",
|
|
|
|
|
placeholder: "/",
|
|
|
|
|
defaultValue: "/",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "secure",
|
|
|
|
|
label: "Secure",
|
|
|
|
|
type: "checkbox",
|
|
|
|
|
defaultValue: "true",
|
|
|
|
|
description: "If enabled, cookie will only be sent over HTTPS connections.",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result == null) return;
|
|
|
|
|
|
|
|
|
|
// Parse the form results
|
|
|
|
|
const cookie_pairs_raw = result.cookie_pairs;
|
|
|
|
|
const domain_value = (result.domain_value as string) ?? "";
|
|
|
|
|
const path = (result.path as string) ?? "/";
|
|
|
|
|
const hostOnly = (result.hostOnly as string) === "true";
|
|
|
|
|
const secure = (result.secure as string) === "true";
|
|
|
|
|
|
|
|
|
|
// Convert key-value pairs to raw_cookie string format: key1=value1;key2=value2
|
|
|
|
|
// Parse cookie_pairs - it comes as a JSON string from the key_value input
|
|
|
|
|
let parsedPairs: Array<{ name: string; value: string }> = [];
|
|
|
|
|
try {
|
|
|
|
|
// Handle null, undefined, or string value
|
|
|
|
|
const pairsStr =
|
|
|
|
|
typeof cookie_pairs_raw === "string"
|
|
|
|
|
? cookie_pairs_raw
|
|
|
|
|
: cookie_pairs_raw != null
|
|
|
|
|
? JSON.stringify(cookie_pairs_raw)
|
|
|
|
|
: "[]";
|
|
|
|
|
if (pairsStr && pairsStr !== "") {
|
|
|
|
|
parsedPairs = JSON.parse(pairsStr);
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
parsedPairs = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const validPairs = parsedPairs.filter((p) => p?.name?.trim());
|
|
|
|
|
// Ensure at least one valid pair exists
|
|
|
|
|
if (validPairs.length === 0) {
|
|
|
|
|
console.log("No valid cookie pairs provided");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const raw_cookie = validPairs.map((p) => `${p.name}=${p.value}`).join(";");
|
|
|
|
|
|
|
|
|
|
const domain: CookieDomain = hostOnly
|
|
|
|
|
? { HostOnly: domain_value ?? "" }
|
|
|
|
|
: { Suffix: domain_value ?? "" };
|
|
|
|
|
|
|
|
|
|
// Build the new cookie with explicit tuple type for path
|
|
|
|
|
const newCookie: Cookie = {
|
|
|
|
|
raw_cookie,
|
|
|
|
|
domain,
|
|
|
|
|
expires: "SessionEnd",
|
|
|
|
|
path: [path, secure] as [string, boolean],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await patchModelById<"cookie_jar", CookieJar>("cookie_jar", cookieJarId, (prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
cookies: [...prev.cookies, newCookie],
|
|
|
|
|
}));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Failed to add cookie:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const CookieDialog = ({ cookieJarId }: Props) => {
|
|
|
|
|
const cookieJars = useAtomValue(cookieJarsAtom);
|
|
|
|
|
const cookieJar = cookieJars?.find((c) => c.id === cookieJarId);
|
|
|
|
|
@@ -17,12 +121,47 @@ export const CookieDialog = ({ cookieJarId }: Props) => {
|
|
|
|
|
return <div>No cookie jar selected</div>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onAddCookie = () => showAddCookieForm(cookieJar.id);
|
|
|
|
|
|
|
|
|
|
let tableBody;
|
|
|
|
|
if (cookieJar.cookies.length === 0) {
|
|
|
|
|
return (
|
|
|
|
|
<Banner>
|
|
|
|
|
Cookies will appear when a response contains the <InlineCode>Set-Cookie</InlineCode> header
|
|
|
|
|
</Banner>
|
|
|
|
|
tableBody = (
|
|
|
|
|
<tr>
|
|
|
|
|
<td colSpan={3}>
|
|
|
|
|
<Banner>
|
|
|
|
|
Cookies will appear when a response contains the <InlineCode>Set-Cookie</InlineCode>{" "}
|
|
|
|
|
header
|
|
|
|
|
</Banner>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
// );
|
|
|
|
|
} else {
|
|
|
|
|
tableBody = cookieJar?.cookies.map((c: Cookie) => (
|
|
|
|
|
<tr key={JSON.stringify(c)}>
|
|
|
|
|
<td className="py-2 select-text cursor-text font-mono font-semibold max-w-0">
|
|
|
|
|
{cookieDomain(c)}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="py-2 pl-4 select-text cursor-text font-mono text-text-subtle whitespace-nowrap overflow-x-auto max-w-[200px] hide-scrollbars">
|
|
|
|
|
{c.raw_cookie}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="max-w-0 w-10">
|
|
|
|
|
<IconButton
|
|
|
|
|
icon="trash"
|
|
|
|
|
size="xs"
|
|
|
|
|
iconSize="sm"
|
|
|
|
|
title="Delete"
|
|
|
|
|
className="ml-auto"
|
|
|
|
|
onClick={async () =>
|
|
|
|
|
await patchModelById<"cookie_jar", CookieJar>("cookie_jar", cookieJar.id, (prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
cookies: prev.cookies.filter((c2: Cookie) => c2 !== c),
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
@@ -32,35 +171,19 @@ export const CookieDialog = ({ cookieJarId }: Props) => {
|
|
|
|
|
<tr>
|
|
|
|
|
<th className="py-2 text-left">Domain</th>
|
|
|
|
|
<th className="py-2 text-left pl-4">Cookie</th>
|
|
|
|
|
<th className="py-2 pl-4" />
|
|
|
|
|
<th className="py-2 pl-4 w-10">
|
|
|
|
|
<IconButton
|
|
|
|
|
icon="plus"
|
|
|
|
|
size="xs"
|
|
|
|
|
iconSize="sm"
|
|
|
|
|
title="Add Cookie"
|
|
|
|
|
className="ml-auto"
|
|
|
|
|
onClick={onAddCookie}
|
|
|
|
|
/>
|
|
|
|
|
</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody className="divide-y divide-surface-highlight">
|
|
|
|
|
{cookieJar?.cookies.map((c: Cookie) => (
|
|
|
|
|
<tr key={JSON.stringify(c)}>
|
|
|
|
|
<td className="py-2 select-text cursor-text font-mono font-semibold max-w-0">
|
|
|
|
|
{cookieDomain(c)}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="py-2 pl-4 select-text cursor-text font-mono text-text-subtle whitespace-nowrap overflow-x-auto max-w-[200px] hide-scrollbars">
|
|
|
|
|
{c.raw_cookie}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="max-w-0 w-10">
|
|
|
|
|
<IconButton
|
|
|
|
|
icon="trash"
|
|
|
|
|
size="xs"
|
|
|
|
|
iconSize="sm"
|
|
|
|
|
title="Delete"
|
|
|
|
|
className="ml-auto"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
patchModel(cookieJar, {
|
|
|
|
|
cookies: cookieJar.cookies.filter((c2: Cookie) => c2 !== c),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
))}
|
|
|
|
|
</tbody>
|
|
|
|
|
<tbody className="divide-y divide-surface-highlight">{tableBody}</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|