import { toByteArray } from "base64-js"; import { type ZodType, z } from "zod/v4"; export function makePskHelpers(allowedByteLengths: readonly number[]) { const bitsLabel = allowedByteLengths.map((b) => b * 8).join(" | "); const msgs = { format: "formValidation.invalidFormat.key", required: "formValidation.required.key", length: `formValidation.pskLength.${bitsLabel.replace(/ \| /g, "_")}bit`, } as const; function tryParse(str: string): Uint8Array | null { try { return toByteArray(str); } catch { return null; } } function isValidString(str: string): boolean { const arr = tryParse(str); return arr !== null && allowedByteLengths.includes(arr.byteLength); } function isValidKey(v: unknown): boolean { if (typeof v === "string") { return isValidString(v); } if (v instanceof Uint8Array) { return allowedByteLengths.includes(v.byteLength); } return false; } const stringSchema = (optional = false) => z .string() .refine( (s) => optional || s !== "" || (s === "" && allowedByteLengths.includes(0)), { message: msgs.required, }, ) .refine((s) => s === "" || tryParse(s) !== null, { message: msgs.format }) .refine((s) => s === "" || isValidString(s), { message: msgs.length, params: { bits: bitsLabel }, }); const bytesSchema = (optional = false): ZodType => z .instanceof(Uint8Array) .refine( (arr) => optional || arr.byteLength !== 0 || allowedByteLengths.includes(0), { message: msgs.required }, ) .refine( (arr) => optional || allowedByteLengths.includes(arr.byteLength), { message: msgs.length, params: { bits: bitsLabel } }, ); return { allowedByteLengths, msgs, tryParseStringKey: tryParse, isValidKey, stringSchema, bytesSchema, }; }