mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-05-15 12:17:18 +02:00
feat(cookies): Allow manually creating cookies
This commit is contained in:
@@ -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 } from "./core/Banner";
|
||||
import { IconButton } from "./core/IconButton";
|
||||
import { InlineCode } from "./core/InlineCode";
|
||||
@@ -10,6 +11,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);
|
||||
@@ -18,12 +122,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 (
|
||||
@@ -33,35 +172,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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user