import { randomBytes, timingSafeEqual } from "node:crypto"; import { z } from "zod"; export type Data = Record; export const token = () => randomBytes(24).toString("base64url"); export const equal = (a: string, b: string) => { const left = Buffer.from(a), right = Buffer.from(b); return left.length === right.length && timingSafeEqual(left, right); }; export class Failure extends Error { constructor( public code: string, public sideEffect = false, public retryable = false, public reason?: string, ) { super(code); } } export function failure(error: unknown): Data { const e = error instanceof Failure ? error : new Failure("INTERNAL_ERROR"); return { code: e.code, retryable: e.retryable, side_effect_possible: e.sideEffect, ...(e.reason ? { reason: e.reason } : {}), }; } export function parse( schema: T, value: unknown, ): z.output { const result = schema.safeParse(value); if (!result.success) throw new Failure("INVALID_ARGUMENT"); return result.data; } export const id = z .string() .min(1) .max(128) .regex(/^[a-zA-Z0-9_-]+$/); export const alias = z .string() .min(1) .max(80) .regex(/^[\p{L}\p{N} _.-]+$/u); export const mode = z.enum(["observe", "control"]); export const unlockPassword = z.string().min(1).max(256) .refine((value) => Buffer.byteLength(value) <= 256 && !/[\0\r\n\t]/.test(value)); export const panel = z.enum(["auto", "apps", "browser", "terminal", "off"]); export type Panel = z.infer; export const videoOptions = z.strictObject({ decode_path: z.enum(["browser", "minirtc"]).default("browser"), decoder: z.enum(["auto", "software"]).default("auto"), }); export type VideoOptions = z.infer; export const host = z .string() .min(1) .max(253) .regex(/^[a-zA-Z0-9.:-]+$/); export const point = z.strictObject({ x: z.number().finite().min(0).max(8192), y: z.number().finite().min(0).max(8192), }); const button = z.enum(["left", "right", "middle"]).optional(); const unique = (keys: string[]) => new Set(keys).size === keys.length; const modifiers = z.array(z.enum(["CTRL", "ALT", "SHIFT", "META"])).max(4).refine(unique) .describe("Modifiers held during this action and released afterwards, including on cancellation.").optional(); const key = z.enum([ ..."ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""), "BACKSPACE", "TAB", "ENTER", "SHIFT", "CTRL", "ALT", "PAUSE", "CAPS_LOCK", "ESC", "SPACE", "PAGE_UP", "PAGE_DOWN", "END", "HOME", "LEFT", "UP", "RIGHT", "DOWN", "INSERT", "DELETE", "META", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "SEMICOLON", "EQUAL", "COMMA", "MINUS", "PERIOD", "SLASH", "BACKQUOTE", "BRACKET_LEFT", "BACKSLASH", "BRACKET_RIGHT", "QUOTE", ]); export const action = z.discriminatedUnion("type", [ point.extend({ type: z.literal("move"), modifiers }), point.extend({ type: z.literal("click"), button, modifiers }), point.extend({ type: z.literal("double_click"), button, modifiers }), point.extend({ type: z.literal("scroll"), direction: z.enum(["up", "down", "left", "right"]), ticks: z.int().min(1).max(10).optional(), modifiers, }), z.strictObject({ type: z.literal("drag"), path: z.array(point).min(2).max(128), duration_ms: z.int().min(100).max(2000).optional(), button, modifiers, }), z.strictObject({ type: z.literal("key_press"), key, modifiers }) .refine((p) => !p.modifiers?.some((modifier) => modifier === p.key)), z.strictObject({ type: z.literal("hotkey"), keys: z.array(key).min(2).max(6).refine(unique), }), z.strictObject({ type: z.literal("type_text"), text: z .string() .min(1) .max(16384) .refine((s) => Buffer.byteLength(s) <= 16384 && !s.includes("\0")), }), ]); export const screenshot = z.strictObject({ session_id: id, region: z .strictObject({ x: z.int().min(0), y: z.int().min(0), width: z.int().min(1).max(8192), height: z.int().min(1).max(8192), }) .optional(), max_edge: z.int().min(64).max(2048).optional(), after_frame_id: z.int().min(0).optional(), generation: z.int().min(0).optional(), timeout_ms: z.int().min(0).max(5000).optional(), }); export const uiConnect = z.strictObject({ device_alias: alias.optional(), device_id: z .string() .regex(/^\d{9}$/) .optional(), label: alias.optional(), password: z .string() .max(256) .refine((s) => Buffer.byteLength(s) <= 256 && !/[\0\r\n]/.test(s)) .optional(), mode: mode.default("observe"), display_index: z.int().min(0).max(63).default(0), server: host.optional(), port: z.int().min(1).max(65535).optional(), save_device: z.boolean().default(false), remember_password: z.boolean().default(false), unlock_password: unlockPassword.optional(), remember_unlock_password: z.boolean().default(false), persistent_authorization: z.boolean().default(false), });