/** * `[[contributes.secrets]]` on the plugin details page: a password field per * declared secret. Values go to the backend's secret store (the macOS * Keychain in the desktop app) and never come back to the UI; plugins use * them only as `{{secret:}}` placeholders in `net` request headers. */ import { useCallback, useEffect, useState } from "react"; import type { PluginInfo, PluginSecretState } from "@/bindings"; import { tx } from "@/platform/i18n"; import { Badge, Button, Input, T } from "@/ui"; import { pluginsBackend } from "./backend"; import type { UiItem } from "./projection"; import styles from "./PluginsPage.module.css"; export interface SecretDecl { name: string; label: string; description?: string | null; } function SecretRow({ plugin, decl, isSet, onChanged, }: { plugin: PluginInfo; decl: SecretDecl; isSet: boolean; onChanged: () => void; }) { const [value, setValue] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const save = async (v: string | null) => { setBusy(true); setError(null); try { await pluginsBackend().setSecret(plugin.id, decl.name, v); setValue(""); onChanged(); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setBusy(false); } }; return (
  • {decl.label} {isSet ? {tx("Stored")} : {tx("Not set")}} setValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && value) void save(value); }} /> {isSet && ( )} {error && ( {error} )}
  • ); } export function SecretsDetails({ plugin, items }: { plugin: PluginInfo; items: UiItem[] }) { const [state, setState] = useState([]); const refresh = useCallback(() => { pluginsBackend() .secrets(plugin.id) .then(setState, () => setState([])); }, [plugin.id]); useEffect(refresh, [refresh]); return ( <>
      {items.map(({ decl }) => ( s.name === decl.name && s.set)} onChanged={refresh} /> ))}
    The plugin can only send these with its network requests; it cannot read them.
    ); }