| 1 | import { readFileSync } from "node:fs"; |
| 2 | import type { ContractInfo } from "../shared/ipc.js"; |
| 3 | |
| 4 | export interface LoadedContract extends ContractInfo { |
| 5 | readonly commandSet: ReadonlySet<string>; |
| 6 | } |
| 7 | |
| 8 | function commandNames(value: unknown): string[] { |
| 9 | if (Array.isArray(value)) { |
| 10 | return value.flatMap((entry) => { |
| 11 | if (typeof entry === "string") return [entry]; |
| 12 | if (entry && typeof entry === "object" && typeof (entry as { name?: unknown }).name === "string") { |
| 13 | return [(entry as { name: string }).name]; |
| 14 | } |
| 15 | return []; |
| 16 | }); |
| 17 | } |
| 18 | if (value && typeof value === "object") return Object.keys(value as Record<string, unknown>); |
| 19 | return []; |
| 20 | } |
| 21 | |
| 22 | export function parseContract(json: unknown): LoadedContract { |
| 23 | if (!json || typeof json !== "object") throw new Error("contract must be a JSON object"); |
| 24 | const record = json as Record<string, unknown>; |
| 25 | const digest = typeof record.digest === "string" ? record.digest : ""; |
| 26 | if (digest === "") throw new Error("contract has no digest"); |
| 27 | const commands = commandNames(record.commands).filter((name) => name !== ""); |
| 28 | if (commands.length === 0) throw new Error("contract lists no commands"); |
| 29 | const protocolVersion = typeof record.protocolVersion === "number" ? record.protocolVersion : 1; |
| 30 | return { protocolVersion, digest, commands: Object.freeze([...commands]), commandSet: new Set(commands) }; |
| 31 | } |
| 32 | |
| 33 | export function emptyContract(): LoadedContract { |
| 34 | return { protocolVersion: 1, digest: "", commands: Object.freeze([]), commandSet: new Set() }; |
| 35 | } |
| 36 | |
| 37 | export function loadContract(path: string): LoadedContract { |
| 38 | return parseContract(JSON.parse(readFileSync(path, "utf8")) as unknown); |
| 39 | } |
| 40 | |
| 41 | export function isAllowedCommand(contract: LoadedContract, method: unknown): method is string { |
| 42 | return typeof method === "string" && method !== "" && contract.commandSet.has(method); |
| 43 | } |
| 44 |