import type { HttpRequestHeader } from "@yaakapp-internal/models"; import type { GenericCompletionOption } from "@yaakapp-internal/plugins"; import { charsets } from "../lib/data/charsets"; import { connections } from "../lib/data/connections"; import { encodings } from "../lib/data/encodings"; import { headerNames } from "../lib/data/headerNames"; import { mimeTypes } from "../lib/data/mimetypes"; import { CountBadge } from "./core/CountBadge"; import { DetailsBanner } from "./core/DetailsBanner"; import type { GenericCompletionConfig } from "./core/Editor/genericCompletion"; import type { InputProps } from "./core/Input"; import type { Pair, PairEditorProps } from "./core/PairEditor"; import { PairEditorRow } from "./core/PairEditor"; import { ensurePairId } from "./core/PairEditor.util"; import { PairOrBulkEditor } from "./core/PairOrBulkEditor"; import { HStack } from "./core/Stacks"; type Props = { forceUpdateKey: string; headers: HttpRequestHeader[]; inheritedHeaders?: HttpRequestHeader[]; inheritedHeadersLabel?: string; stateKey: string; onChange: (headers: HttpRequestHeader[]) => void; label?: string; }; export function HeadersEditor({ stateKey, headers, inheritedHeaders, inheritedHeadersLabel = "Inherited", onChange, forceUpdateKey, }: Props) { // Get header names defined at current level (case-insensitive) const currentHeaderNames = new Set( headers.filter((h) => h.name).map((h) => h.name.toLowerCase()), ); // Filter inherited headers: must be enabled, have content, and not be overridden by current level const validInheritedHeaders = inheritedHeaders?.filter( (pair) => pair.enabled && (pair.name || pair.value) && !currentHeaderNames.has(pair.name.toLowerCase()), ) ?? []; const hasInheritedHeaders = validInheritedHeaders.length > 0; return (
{hasInheritedHeaders && ( {inheritedHeadersLabel} } >
{validInheritedHeaders?.map((pair, i) => ( ))}
)}
); } const MIN_MATCH = 3; const headerOptionsMap: Record = { "content-type": mimeTypes, accept: ["*/*", ...mimeTypes], "accept-encoding": encodings, connection: connections, "accept-charset": charsets, }; const valueType = (pair: Pair): InputProps["type"] => { const name = pair.name.toLowerCase().trim(); if ( name.includes("authorization") || name.includes("api-key") || name.includes("access-token") || name.includes("auth") || name.includes("secret") || name.includes("token") || name === "cookie" || name === "set-cookie" ) { return "password"; } return "text"; }; const valueAutocomplete = (headerName: string): GenericCompletionConfig | undefined => { const name = headerName.toLowerCase().trim(); const options: GenericCompletionOption[] = headerOptionsMap[name]?.map((o) => ({ label: o, type: "constant", boost: 1, // Put above other completions })) ?? []; return { minMatch: MIN_MATCH, options }; }; const nameAutocomplete: PairEditorProps["nameAutocomplete"] = { minMatch: MIN_MATCH, options: headerNames.map((t) => typeof t === "string" ? { label: t, type: "constant", boost: 1, // Put above other completions } : { ...t, boost: 1, // Put above other completions }, ), }; const validateHttpHeader = (v: string) => { if (v === "") { return true; } // Template strings are not allowed so we replace them with a valid example string const withoutTemplateStrings = v.replace(/\$\{\[\s*[^\]\s]+\s*]}/gi, "123"); return withoutTemplateStrings.match(/^[a-zA-Z0-9-_]+$/) !== null; };