/** * The install / permission dialog: every * capability with its risk; optional ones can be refused; high-risk ones are * called out as running code on this machine. */ import { useState } from "react"; import { create } from "zustand"; import type { CapabilityInfo, Consent } from "@/bindings"; import { tx } from "@/platform/i18n"; import { Badge, Checkbox, Dialog, T } from "@/ui"; import { setConsentPrompt, type ConsentRequest } from "./store"; import styles from "./PluginsPage.module.css"; interface Pending { req: ConsentRequest; resolve: (c: Consent | null) => void; } const usePending = create<{ queue: Pending[] }>(() => ({ queue: [] })); export const RISK_LABEL: Record = { low: { label: "Low", tone: "neutral" }, medium: { label: "Medium", tone: "warning" }, high: { label: "High", tone: "danger" }, }; const RISK_ORDER = { high: 0, medium: 1, low: 2 } as const; export function sortByRisk(caps: readonly CapabilityInfo[]): CapabilityInfo[] { return [...caps].sort((a, b) => RISK_ORDER[a.risk] - RISK_ORDER[b.risk] || a.id.localeCompare(b.id)); } function ask(req: ConsentRequest): Promise { return new Promise((resolve) => usePending.setState((s) => ({ queue: [...s.queue, { req, resolve }] }))); } function ConsentDialog({ pending, onDone }: { pending: Pending; onDone: () => void }) { const { req } = pending; const caps = sortByRisk(req.capabilities); const optional = caps.filter((c) => c.optional); const [accepted, setAccepted] = useState>( () => new Set(optional.filter((c) => c.granted || c.risk === "low").map((c) => c.id)), ); const high = caps.some((c) => c.risk === "high" && (!c.optional || accepted.has(c.id))); const finish = (consent: Consent | null) => { pending.resolve(consent); onDone(); }; const title = req.kind === "install" ? tx`Install ${req.name} ${req.version}?` : tx`Allow ${req.name} to run?`; return ( finish(null)} buttons={[ { label: tx("Cancel"), role: "cancel", onClick: () => finish(null) }, { label: req.kind === "install" ? tx("Install") : tx("Allow"), role: "default", onClick: () => finish({ optional: [...accepted], signerChange: !!req.signerChanged }), }, ]} >
{req.unsigned && (

This package is not signed. Only continue if you trust where it came from.

)} {req.signerChanged && (

This version is signed with a different key than the installed one, and the publisher's earlier key doesn't vouch for it. Only continue if you know the publisher changed keys.

)} {caps.length === 0 ? (

This plugin needs no special permissions.

) : (
    {caps.map((c) => (
  • {c.optional ? ( setAccepted((s) => { const next = new Set(s); if (on) next.add(c.id); else next.delete(c.id); return next; }) } aria-label={tx`Allow ${c.id}`} /> ) : ( • )} {c.description} {c.id} {RISK_LABEL[c.risk].label}
  • ))}
)} {high && (

High-risk permissions are equivalent to running arbitrary code on this machine.

)} {req.warnings.map((w) => (

{w}

))}
); } /** Overlay rendering the queued consent dialogs one at a time. */ export function ConsentDialogHost() { const first = usePending((s) => s.queue[0]); if (!first) return null; return ( usePending.setState((s) => ({ queue: s.queue.slice(1) }))} /> ); } /** Routes the store's consent requests to the dialog. */ export function provideConsentDialog(): () => void { setConsentPrompt(ask); return () => { setConsentPrompt(null); for (const p of usePending.getState().queue) p.resolve(null); usePending.setState({ queue: [] }); }; }