@braedonsaunders/appkit-desk 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +233 -0
- package/backend.d.ts +139 -0
- package/backend.d.ts.map +1 -0
- package/backend.js +583 -0
- package/backend.js.map +1 -0
- package/conformance.d.ts +19 -0
- package/conformance.d.ts.map +1 -0
- package/conformance.js +46 -0
- package/conformance.js.map +1 -0
- package/events.d.ts +190 -0
- package/events.d.ts.map +1 -0
- package/events.js +12 -0
- package/events.js.map +1 -0
- package/frames.d.ts +14 -0
- package/frames.d.ts.map +1 -0
- package/frames.js +24 -0
- package/frames.js.map +1 -0
- package/guest-agent.d.ts +129 -0
- package/guest-agent.d.ts.map +1 -0
- package/guest-agent.js +165 -0
- package/guest-agent.js.map +1 -0
- package/host.d.ts +325 -0
- package/host.d.ts.map +1 -0
- package/host.js +1038 -0
- package/host.js.map +1 -0
- package/index.d.ts +10 -0
- package/index.d.ts.map +1 -0
- package/index.js +10 -0
- package/index.js.map +1 -0
- package/package.json +41 -0
- package/plan.d.ts +117 -0
- package/plan.d.ts.map +1 -0
- package/plan.js +187 -0
- package/plan.js.map +1 -0
- package/portable-home.d.ts +79 -0
- package/portable-home.d.ts.map +1 -0
- package/portable-home.js +193 -0
- package/portable-home.js.map +1 -0
- package/protocol.d.ts +224 -0
- package/protocol.d.ts.map +1 -0
- package/protocol.js +498 -0
- package/protocol.js.map +1 -0
package/plan.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from 'node:fs'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\n\nexport const DEFAULT_VMM_PATH = '/usr/bin/cloud-hypervisor'\nexport const DEFAULT_KVM_PATH = '/dev/kvm'\nexport const DEFAULT_QEMU_IMG_PATH = '/usr/bin/qemu-img'\nexport const DEFAULT_RUNTIME_DIR = '/run/appkit-desk'\nexport const DEFAULT_MEMORY_MB = 384\nexport const DEFAULT_VCPUS = 2\nexport const DEFAULT_KERNEL_CMDLINE = 'root=/dev/vda rw quiet'\n\nconst DESK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/\nconst TAP_DEVICE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,14}$/\nconst MAC_ADDRESS_PATTERN = /^[0-9a-f]{2}(?::[0-9a-f]{2}){5}$/i\n/** vsock CIDs 0–2 are reserved (hypervisor, loopback, host). */\nconst MIN_GUEST_CID = 3\nconst MAX_GUEST_CID = 0xffff_fffe\n\n/**\n * Every failure this package raises. The `name` is annotated as `string`\n * rather than left to infer its literal so a subclass can narrow the meaning\n * of a failure — see `DeskRequestFateUnknownError` — without callers losing\n * the one `instanceof DeskError` they already check.\n */\nexport class DeskError extends Error {\n override readonly name: string = 'DeskError'\n}\n\nexport interface DeskLauncherIdentity {\n /** Host/container UID used to invoke the VMM and image tooling. */\n uid: number\n /** Host/container GID used to invoke the VMM and image tooling. */\n gid: number\n}\n\nexport interface DeskLaunchPlanOptions {\n deskId: string\n vmmPath?: string\n kvmPath?: string\n qemuImgPath?: string\n /** Absolute path of the kernel image the microVM boots. */\n kernelPath: string\n /**\n * Absolute path of an initramfs. Required whenever the kernel loads its\n * root-filesystem or virtio drivers as modules — a stock distro cloud kernel\n * panics with \"VFS: Unable to mount root fs\" if it boots without one. Omit\n * only for a kernel with virtio_blk/ext4 built in.\n */\n initramfsPath?: string\n /** Absolute path of the golden raw base image every desk clones. */\n baseImagePath: string\n /** Absolute path of this desk's copy-on-write overlay (a raw reflink clone). */\n overlayPath: string\n memoryMb?: number\n vcpus?: number\n /** Guest vsock context ID; must be unique among resident desks. */\n vsockCid: number\n /** Directory holding the vsock and API control sockets. */\n runtimeDir?: string\n /** Host TAP device name; derived from the CID when omitted. */\n tapDevice?: string\n /** Guest MAC address; derived from the CID when omitted. */\n macAddress?: string\n kernelCmdline?: string\n launcherIdentity?: DeskLauncherIdentity\n}\n\n/**\n * The complete Cloud Hypervisor invocation as inspectable data. Nothing here\n * has been spawned: tests, audit logs, and readiness screens can assert on the\n * exact argv a desk would boot with.\n */\nexport interface DeskLaunchPlan {\n deskId: string\n vmm: { command: string; args: string[] }\n overlay: {\n path: string\n basePath: string\n /** The overlay-creation step, or `null` when the overlay already exists. */\n create: { command: string; args: string[] } | null\n }\n vsock: { cid: number; socketPath: string }\n api: { socketPath: string }\n tap: { device: string; mac: string }\n memoryMb: number\n vcpus: number\n kernelPath: string\n /** Resolved initramfs path, or `null` when the kernel needs none. */\n initramfsPath: string | null\n kernelCmdline: string\n launcherIdentity?: DeskLauncherIdentity\n}\n\n/**\n * Whether this host can run desks at all: Linux, a usable `/dev/kvm`, and the\n * Cloud Hypervisor binary. All three checks are injectable and the answer is\n * fail-closed — a host missing any of them gets no desk capability rather\n * than a degraded one.\n */\nexport function isDeskSupported(options: {\n platform?: NodeJS.Platform\n kvmPath?: string\n vmmPath?: string\n pathExists?: (path: string) => boolean\n} = {}): boolean {\n const platform = options.platform ?? process.platform\n const pathExists = options.pathExists ?? existsSync\n return (\n platform === 'linux'\n && pathExists(options.kvmPath ?? DEFAULT_KVM_PATH)\n && pathExists(options.vmmPath ?? DEFAULT_VMM_PATH)\n )\n}\n\n/** Validate a desk identifier, which is used in socket paths and device names. */\nexport function cleanDeskId(value: string): string {\n if (!DESK_ID_PATTERN.test(value)) {\n throw new DeskError(\n 'deskId must be 1–64 letters, numbers, dots, underscores, or hyphens.',\n )\n }\n return value\n}\n\n/**\n * Build the deterministic Cloud Hypervisor launch plan without spawning\n * anything. Fail closed: a missing `/dev/kvm`, VMM binary, kernel, base image,\n * or overlay directory throws instead of producing a plan that cannot boot.\n */\nexport function buildDeskLaunchPlan(\n options: DeskLaunchPlanOptions,\n dependencies: {\n pathExists?: (path: string) => boolean\n deviceExists?: (path: string) => boolean\n } = {},\n): DeskLaunchPlan {\n const pathExists = dependencies.pathExists ?? existsSync\n const deviceExists = dependencies.deviceExists ?? existsSync\n const deskId = cleanDeskId(options.deskId)\n const kvmPath = absolutePath(options.kvmPath ?? DEFAULT_KVM_PATH, 'kvmPath')\n if (!deviceExists(kvmPath)) {\n throw new DeskError(\n `KVM is not available at ${kvmPath}. Desks require hardware virtualization; `\n + 'there is no software-emulation fallback.',\n )\n }\n const vmmPath = absolutePath(options.vmmPath ?? DEFAULT_VMM_PATH, 'vmmPath')\n if (!pathExists(vmmPath)) {\n throw new DeskError(\n `Cloud Hypervisor was not found at ${vmmPath}. Install cloud-hypervisor or configure vmmPath.`,\n )\n }\n const kernelPath = absolutePath(options.kernelPath, 'kernelPath')\n if (!pathExists(kernelPath)) {\n throw new DeskError(`Guest kernel does not exist: ${kernelPath}`)\n }\n const initramfsPath =\n options.initramfsPath === undefined\n ? null\n : absolutePath(options.initramfsPath, 'initramfsPath')\n if (initramfsPath !== null && !pathExists(initramfsPath)) {\n throw new DeskError(`Initramfs does not exist: ${initramfsPath}`)\n }\n const baseImagePath = absolutePath(options.baseImagePath, 'baseImagePath')\n if (!pathExists(baseImagePath)) {\n throw new DeskError(`Base image does not exist: ${baseImagePath}`)\n }\n const overlayPath = absolutePath(options.overlayPath, 'overlayPath')\n const overlayDirectory = dirname(overlayPath)\n if (!pathExists(overlayDirectory)) {\n throw new DeskError(`Overlay directory does not exist: ${overlayDirectory}`)\n }\n const memoryMb = positiveInteger(options.memoryMb ?? DEFAULT_MEMORY_MB, 'memoryMb')\n const vcpus = positiveInteger(options.vcpus ?? DEFAULT_VCPUS, 'vcpus')\n const vsockCid = cleanCid(options.vsockCid)\n const runtimeDir = absolutePath(options.runtimeDir ?? DEFAULT_RUNTIME_DIR, 'runtimeDir')\n const tapDevice = cleanTapDevice(options.tapDevice ?? defaultTapDevice(vsockCid))\n const macAddress = cleanMacAddress(options.macAddress ?? defaultMacAddress(vsockCid))\n const kernelCmdline = options.kernelCmdline ?? DEFAULT_KERNEL_CMDLINE\n const launcherIdentity = cleanLauncherIdentity(options.launcherIdentity)\n const vsockSocketPath = join(runtimeDir, `desk-${deskId}.vsock`)\n const apiSocketPath = join(runtimeDir, `desk-${deskId}.api.sock`)\n\n const create = pathExists(overlayPath)\n ? null\n : {\n // Cloud Hypervisor cannot follow disk backing chains: a qcow2 overlay\n // over a qcow2/raw base is rejected with UnsupportedFeature /\n // MaxNestingDepthExceeded. So the overlay is a plain raw file, cloned\n // from the raw base with `cp --reflink=auto` — an instant, block-sharing\n // copy-on-write clone on XFS/Btrfs and a graceful full-copy fallback on\n // ext4. Either way CH boots the resulting raw file with no backing chain.\n command: 'cp',\n args: ['--reflink=auto', baseImagePath, overlayPath],\n }\n\n const args = [\n '--api-socket', apiSocketPath,\n '--kernel', kernelPath,\n // A modular distro kernel needs its initramfs to bring up virtio_blk and\n // mount root; omitted only for a kernel with those drivers built in.\n ...(initramfsPath === null ? [] : ['--initramfs', initramfsPath]),\n '--cmdline', kernelCmdline,\n // Be explicit that the disk is raw: CH deprecated image-format auto-detection\n // and warns without image_type; there is never a qcow2 overlay here.\n '--disk', `path=${overlayPath},image_type=raw`,\n '--memory', `size=${memoryMb}M`,\n '--cpus', `boot=${vcpus}`,\n '--vsock', `cid=${vsockCid},socket=${vsockSocketPath}`,\n '--net', `tap=${tapDevice},mac=${macAddress}`,\n '--serial', 'off',\n '--console', 'off',\n ]\n\n return {\n deskId,\n vmm: { command: vmmPath, args },\n overlay: { path: overlayPath, basePath: baseImagePath, create },\n vsock: { cid: vsockCid, socketPath: vsockSocketPath },\n api: { socketPath: apiSocketPath },\n tap: { device: tapDevice, mac: macAddress },\n memoryMb,\n vcpus,\n kernelPath,\n initramfsPath,\n kernelCmdline,\n launcherIdentity,\n }\n}\n\nfunction defaultTapDevice(cid: number): string {\n return `dsk${cid}`\n}\n\nfunction defaultMacAddress(cid: number): string {\n // Locally administered, unicast prefix; the low four octets carry the CID so\n // the address is stable per desk and never collides across resident desks.\n const octets = [0x06, 0x00, (cid >>> 24) & 0xff, (cid >>> 16) & 0xff, (cid >>> 8) & 0xff, cid & 0xff]\n return octets.map((octet) => octet.toString(16).padStart(2, '0')).join(':')\n}\n\nfunction cleanCid(value: number): number {\n if (!Number.isInteger(value) || value < MIN_GUEST_CID || value > MAX_GUEST_CID) {\n throw new DeskError(\n `vsockCid must be an integer between ${MIN_GUEST_CID} and ${MAX_GUEST_CID}.`,\n )\n }\n return value\n}\n\nfunction cleanTapDevice(value: string): string {\n if (!TAP_DEVICE_PATTERN.test(value)) {\n throw new DeskError('tapDevice must be 1–15 characters of letters, numbers, underscores, or hyphens.')\n }\n return value\n}\n\nfunction cleanMacAddress(value: string): string {\n if (!MAC_ADDRESS_PATTERN.test(value)) {\n throw new DeskError(`macAddress is not a valid MAC address: ${value}`)\n }\n return value.toLowerCase()\n}\n\nexport function cleanLauncherIdentity(\n identity: DeskLauncherIdentity | undefined,\n): DeskLauncherIdentity | undefined {\n if (!identity) return undefined\n const max = 2_147_483_647\n if (!Number.isInteger(identity.uid) || identity.uid < 1 || identity.uid > max) {\n throw new DeskError(`launcherIdentity.uid must be an integer between 1 and ${max}.`)\n }\n if (!Number.isInteger(identity.gid) || identity.gid < 0 || identity.gid > max) {\n throw new DeskError(`launcherIdentity.gid must be an integer between 0 and ${max}.`)\n }\n return { uid: identity.uid, gid: identity.gid }\n}\n\nfunction absolutePath(value: string, field: string): string {\n if (!value || !isAbsolute(value)) {\n throw new DeskError(`${field} must be an absolute path.`)\n }\n return resolve(value)\n}\n\nfunction positiveInteger(value: number, field: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new DeskError(`${field} must be a positive integer.`)\n }\n return value\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export declare const PORTABLE_DESK_HOME_VERSION: 1;
|
|
2
|
+
export type PortableDeskHomeEntry = {
|
|
3
|
+
path: string;
|
|
4
|
+
kind: 'directory';
|
|
5
|
+
mode: number;
|
|
6
|
+
modifiedAt: string | null;
|
|
7
|
+
} | {
|
|
8
|
+
path: string;
|
|
9
|
+
kind: 'file';
|
|
10
|
+
mode: number;
|
|
11
|
+
modifiedAt: string | null;
|
|
12
|
+
size: number;
|
|
13
|
+
digest: string;
|
|
14
|
+
} | {
|
|
15
|
+
path: string;
|
|
16
|
+
kind: 'symlink';
|
|
17
|
+
mode: number;
|
|
18
|
+
modifiedAt: string | null;
|
|
19
|
+
target: string;
|
|
20
|
+
};
|
|
21
|
+
export type PortableDeskHomeManifest = {
|
|
22
|
+
version: typeof PORTABLE_DESK_HOME_VERSION;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
entries: readonly PortableDeskHomeEntry[];
|
|
25
|
+
totalBytes: number;
|
|
26
|
+
};
|
|
27
|
+
export type PortableDeskHomeArchive = {
|
|
28
|
+
manifest: PortableDeskHomeManifest;
|
|
29
|
+
/** Content-addressed file bodies. Identical files occupy one blob. */
|
|
30
|
+
blobs: ReadonlyMap<string, Uint8Array>;
|
|
31
|
+
};
|
|
32
|
+
export type DeskHomeSourceEntry = Omit<Extract<PortableDeskHomeEntry, {
|
|
33
|
+
kind: 'directory';
|
|
34
|
+
}>, 'path'> & {
|
|
35
|
+
path: string;
|
|
36
|
+
} | Omit<Extract<PortableDeskHomeEntry, {
|
|
37
|
+
kind: 'file';
|
|
38
|
+
}>, 'path' | 'size' | 'digest'> & {
|
|
39
|
+
path: string;
|
|
40
|
+
} | Omit<Extract<PortableDeskHomeEntry, {
|
|
41
|
+
kind: 'symlink';
|
|
42
|
+
}>, 'path'> & {
|
|
43
|
+
path: string;
|
|
44
|
+
};
|
|
45
|
+
export interface PortableDeskHomeSource {
|
|
46
|
+
entries(signal: AbortSignal): AsyncIterable<DeskHomeSourceEntry>;
|
|
47
|
+
readFile(path: string, signal: AbortSignal): Promise<Uint8Array>;
|
|
48
|
+
}
|
|
49
|
+
export interface PortableDeskHomeImport {
|
|
50
|
+
writeDirectory(entry: Extract<PortableDeskHomeEntry, {
|
|
51
|
+
kind: 'directory';
|
|
52
|
+
}>, signal: AbortSignal): Promise<void>;
|
|
53
|
+
writeFile(entry: Extract<PortableDeskHomeEntry, {
|
|
54
|
+
kind: 'file';
|
|
55
|
+
}>, bytes: Uint8Array, signal: AbortSignal): Promise<void>;
|
|
56
|
+
writeSymlink(entry: Extract<PortableDeskHomeEntry, {
|
|
57
|
+
kind: 'symlink';
|
|
58
|
+
}>, signal: AbortSignal): Promise<void>;
|
|
59
|
+
/** Atomically make the staged home authoritative. */
|
|
60
|
+
commit(signal: AbortSignal): Promise<void>;
|
|
61
|
+
/** Best-effort cleanup of an uncommitted staging area. */
|
|
62
|
+
rollback(): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
export interface PortableDeskHomeSink {
|
|
65
|
+
begin(manifest: PortableDeskHomeManifest, signal: AbortSignal): Promise<PortableDeskHomeImport>;
|
|
66
|
+
}
|
|
67
|
+
export type PortableDeskHomeLimits = {
|
|
68
|
+
maxEntries?: number;
|
|
69
|
+
maxFileBytes?: number;
|
|
70
|
+
maxTotalBytes?: number;
|
|
71
|
+
};
|
|
72
|
+
export declare function exportPortableDeskHome(source: PortableDeskHomeSource, options?: PortableDeskHomeLimits & {
|
|
73
|
+
signal?: AbortSignal;
|
|
74
|
+
now?: () => Date;
|
|
75
|
+
}): Promise<PortableDeskHomeArchive>;
|
|
76
|
+
export declare function importPortableDeskHome(archive: PortableDeskHomeArchive, sink: PortableDeskHomeSink, options?: PortableDeskHomeLimits & {
|
|
77
|
+
signal?: AbortSignal;
|
|
78
|
+
}): Promise<void>;
|
|
79
|
+
//# sourceMappingURL=portable-home.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-home.d.ts","sourceRoot":"","sources":["../src/portable-home.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,0BAA0B,EAAG,CAAU,CAAA;AAEpD,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC5E;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9F,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,OAAO,0BAA0B,CAAA;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAA;IACzC,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,wBAAwB,CAAA;IAClC,sEAAsE;IACtE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAC3B,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,EAAE,MAAM,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GACtF,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GACrG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,EAAE,MAAM,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAExF,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAAC,mBAAmB,CAAC,CAAA;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;CACjE;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,qBAAqB,EAAE;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChH,SAAS,CACP,KAAK,EAAE,OAAO,CAAC,qBAAqB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,EACvD,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,qBAAqB,EAAE;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5G,qDAAqD;IACrD,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C,0DAA0D;IAC1D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,QAAQ,EAAE,wBAAwB,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAA;CAChG;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAQD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,sBAAsB,EAC9B,OAAO,GAAE,sBAAsB,GAAG;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,GAChF,OAAO,CAAC,uBAAuB,CAAC,CAyClC;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,sBAAsB,GAAG;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAC9D,OAAO,CAAC,IAAI,CAAC,CAyBf"}
|
package/portable-home.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { posix } from 'node:path';
|
|
3
|
+
export const PORTABLE_DESK_HOME_VERSION = 1;
|
|
4
|
+
const DEFAULT_LIMITS = {
|
|
5
|
+
maxEntries: 20_000,
|
|
6
|
+
maxFileBytes: 64 * 1024 * 1024,
|
|
7
|
+
maxTotalBytes: 512 * 1024 * 1024,
|
|
8
|
+
};
|
|
9
|
+
export async function exportPortableDeskHome(source, options = {}) {
|
|
10
|
+
const limits = resolveLimits(options);
|
|
11
|
+
const controller = linkedController(options.signal);
|
|
12
|
+
const entries = [];
|
|
13
|
+
const blobs = new Map();
|
|
14
|
+
let totalBytes = 0;
|
|
15
|
+
for await (const raw of source.entries(controller.signal)) {
|
|
16
|
+
throwIfAborted(controller.signal);
|
|
17
|
+
if (entries.length >= limits.maxEntries)
|
|
18
|
+
throw new Error(`Desk home exceeds ${limits.maxEntries} entries.`);
|
|
19
|
+
const path = portablePath(raw.path);
|
|
20
|
+
const base = { path, mode: portableMode(raw.mode), modifiedAt: portableDate(raw.modifiedAt) };
|
|
21
|
+
if (raw.kind === 'directory') {
|
|
22
|
+
entries.push({ ...base, kind: 'directory' });
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (raw.kind === 'symlink') {
|
|
26
|
+
entries.push({ ...base, kind: 'symlink', target: portableSymlinkTarget(path, raw.target) });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const bytes = await source.readFile(path, controller.signal);
|
|
30
|
+
if (!(bytes instanceof Uint8Array))
|
|
31
|
+
throw new Error(`Desk home source returned invalid bytes for ${path}.`);
|
|
32
|
+
if (bytes.byteLength > limits.maxFileBytes)
|
|
33
|
+
throw new Error(`Desk home file ${path} exceeds ${limits.maxFileBytes} bytes.`);
|
|
34
|
+
totalBytes += bytes.byteLength;
|
|
35
|
+
if (totalBytes > limits.maxTotalBytes)
|
|
36
|
+
throw new Error(`Desk home exceeds ${limits.maxTotalBytes} total bytes.`);
|
|
37
|
+
const digest = digestBytes(bytes);
|
|
38
|
+
if (!blobs.has(digest))
|
|
39
|
+
blobs.set(digest, Uint8Array.from(bytes));
|
|
40
|
+
entries.push({ ...base, kind: 'file', size: bytes.byteLength, digest });
|
|
41
|
+
}
|
|
42
|
+
entries.sort(compareEntries);
|
|
43
|
+
assertUniquePaths(entries);
|
|
44
|
+
return {
|
|
45
|
+
manifest: {
|
|
46
|
+
version: PORTABLE_DESK_HOME_VERSION,
|
|
47
|
+
createdAt: (options.now?.() ?? new Date()).toISOString(),
|
|
48
|
+
entries,
|
|
49
|
+
totalBytes,
|
|
50
|
+
},
|
|
51
|
+
blobs,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export async function importPortableDeskHome(archive, sink, options = {}) {
|
|
55
|
+
const limits = resolveLimits(options);
|
|
56
|
+
const controller = linkedController(options.signal);
|
|
57
|
+
const manifest = validateArchive(archive, limits);
|
|
58
|
+
const writer = await sink.begin(manifest, controller.signal);
|
|
59
|
+
let committed = false;
|
|
60
|
+
try {
|
|
61
|
+
for (const entry of manifest.entries.filter((candidate) => candidate.kind === 'directory')) {
|
|
62
|
+
throwIfAborted(controller.signal);
|
|
63
|
+
await writer.writeDirectory(entry, controller.signal);
|
|
64
|
+
}
|
|
65
|
+
for (const entry of manifest.entries.filter((candidate) => candidate.kind !== 'directory')) {
|
|
66
|
+
throwIfAborted(controller.signal);
|
|
67
|
+
if (entry.kind === 'symlink')
|
|
68
|
+
await writer.writeSymlink(entry, controller.signal);
|
|
69
|
+
else {
|
|
70
|
+
const bytes = archive.blobs.get(entry.digest);
|
|
71
|
+
if (!bytes)
|
|
72
|
+
throw new Error(`Desk home blob ${entry.digest} is missing.`);
|
|
73
|
+
await writer.writeFile(entry, Uint8Array.from(bytes), controller.signal);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
await writer.commit(controller.signal);
|
|
77
|
+
committed = true;
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
if (!committed)
|
|
81
|
+
await writer.rollback().catch(() => undefined);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function validateArchive(archive, limits) {
|
|
85
|
+
const manifest = archive.manifest;
|
|
86
|
+
if (manifest.version !== PORTABLE_DESK_HOME_VERSION)
|
|
87
|
+
throw new Error(`Unsupported desk home version ${String(manifest.version)}.`);
|
|
88
|
+
if (!Number.isFinite(Date.parse(manifest.createdAt)))
|
|
89
|
+
throw new Error('Desk home createdAt is invalid.');
|
|
90
|
+
if (manifest.entries.length > limits.maxEntries)
|
|
91
|
+
throw new Error(`Desk home exceeds ${limits.maxEntries} entries.`);
|
|
92
|
+
let totalBytes = 0;
|
|
93
|
+
const entries = manifest.entries.map((entry) => {
|
|
94
|
+
const path = portablePath(entry.path);
|
|
95
|
+
const base = { path, mode: portableMode(entry.mode), modifiedAt: portableDate(entry.modifiedAt) };
|
|
96
|
+
if (entry.kind === 'directory')
|
|
97
|
+
return { ...base, kind: 'directory' };
|
|
98
|
+
if (entry.kind === 'symlink')
|
|
99
|
+
return { ...base, kind: 'symlink', target: portableSymlinkTarget(path, entry.target) };
|
|
100
|
+
if (!Number.isInteger(entry.size) || entry.size < 0 || entry.size > limits.maxFileBytes) {
|
|
101
|
+
throw new Error(`Desk home file ${path} has an invalid size.`);
|
|
102
|
+
}
|
|
103
|
+
const bytes = archive.blobs.get(entry.digest);
|
|
104
|
+
if (!bytes || bytes.byteLength !== entry.size || digestBytes(bytes) !== entry.digest) {
|
|
105
|
+
throw new Error(`Desk home file ${path} failed content verification.`);
|
|
106
|
+
}
|
|
107
|
+
totalBytes += entry.size;
|
|
108
|
+
if (totalBytes > limits.maxTotalBytes)
|
|
109
|
+
throw new Error(`Desk home exceeds ${limits.maxTotalBytes} total bytes.`);
|
|
110
|
+
return { ...base, kind: 'file', size: entry.size, digest: entry.digest };
|
|
111
|
+
});
|
|
112
|
+
assertUniquePaths(entries);
|
|
113
|
+
if (totalBytes !== manifest.totalBytes)
|
|
114
|
+
throw new Error('Desk home total byte count does not match its manifest.');
|
|
115
|
+
return { ...manifest, entries: [...entries].sort(compareEntries), totalBytes };
|
|
116
|
+
}
|
|
117
|
+
function portablePath(value) {
|
|
118
|
+
if (typeof value !== 'string' || value.length === 0 || value.includes('\0') || value.includes('\\')) {
|
|
119
|
+
throw new Error('Desk home paths must be non-empty portable paths.');
|
|
120
|
+
}
|
|
121
|
+
const normalized = posix.normalize(value);
|
|
122
|
+
if (normalized !== value || posix.isAbsolute(value) || value === '..' || value.startsWith('../')) {
|
|
123
|
+
throw new Error(`Desk home path escapes its root: ${value}`);
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
function portableSymlinkTarget(path, target) {
|
|
128
|
+
if (typeof target !== 'string' || target.length === 0 || target.includes('\0') || target.includes('\\') || posix.isAbsolute(target)) {
|
|
129
|
+
throw new Error(`Desk home symlink ${path} has an unsafe target.`);
|
|
130
|
+
}
|
|
131
|
+
const resolved = posix.normalize(posix.join(posix.dirname(path), target));
|
|
132
|
+
if (resolved === '..' || resolved.startsWith('../'))
|
|
133
|
+
throw new Error(`Desk home symlink ${path} escapes its root.`);
|
|
134
|
+
return target;
|
|
135
|
+
}
|
|
136
|
+
function portableMode(mode) {
|
|
137
|
+
if (!Number.isInteger(mode) || mode < 0 || mode > 0o7777)
|
|
138
|
+
throw new Error('Desk home mode must be between 0000 and 7777.');
|
|
139
|
+
return mode;
|
|
140
|
+
}
|
|
141
|
+
function portableDate(value) {
|
|
142
|
+
if (value === null)
|
|
143
|
+
return null;
|
|
144
|
+
if (!Number.isFinite(Date.parse(value)))
|
|
145
|
+
throw new Error('Desk home modifiedAt is invalid.');
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
function digestBytes(bytes) {
|
|
149
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
150
|
+
}
|
|
151
|
+
function compareEntries(left, right) {
|
|
152
|
+
const leftDepth = left.path.split('/').length;
|
|
153
|
+
const rightDepth = right.path.split('/').length;
|
|
154
|
+
return leftDepth - rightDepth || left.path.localeCompare(right.path);
|
|
155
|
+
}
|
|
156
|
+
function assertUniquePaths(entries) {
|
|
157
|
+
const paths = new Set();
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
if (paths.has(entry.path))
|
|
160
|
+
throw new Error(`Desk home contains duplicate path ${entry.path}.`);
|
|
161
|
+
paths.add(entry.path);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function resolveLimits(options) {
|
|
165
|
+
return {
|
|
166
|
+
maxEntries: positiveLimit(options.maxEntries ?? DEFAULT_LIMITS.maxEntries, 'maxEntries'),
|
|
167
|
+
maxFileBytes: positiveLimit(options.maxFileBytes ?? DEFAULT_LIMITS.maxFileBytes, 'maxFileBytes'),
|
|
168
|
+
maxTotalBytes: positiveLimit(options.maxTotalBytes ?? DEFAULT_LIMITS.maxTotalBytes, 'maxTotalBytes'),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function positiveLimit(value, name) {
|
|
172
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
173
|
+
throw new Error(`${name} must be a positive safe integer.`);
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
function linkedController(signal) {
|
|
177
|
+
const controller = new AbortController();
|
|
178
|
+
if (!signal)
|
|
179
|
+
return controller;
|
|
180
|
+
if (signal.aborted)
|
|
181
|
+
controller.abort(signal.reason);
|
|
182
|
+
else
|
|
183
|
+
signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
|
|
184
|
+
return controller;
|
|
185
|
+
}
|
|
186
|
+
function throwIfAborted(signal) {
|
|
187
|
+
if (!signal.aborted)
|
|
188
|
+
return;
|
|
189
|
+
if (signal.reason instanceof Error)
|
|
190
|
+
throw signal.reason;
|
|
191
|
+
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=portable-home.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-home.js","sourceRoot":"","sources":["../src/portable-home.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto'\nimport { posix } from 'node:path'\n\nexport const PORTABLE_DESK_HOME_VERSION = 1 as const\n\nexport type PortableDeskHomeEntry =\n | { path: string; kind: 'directory'; mode: number; modifiedAt: string | null }\n | { path: string; kind: 'file'; mode: number; modifiedAt: string | null; size: number; digest: string }\n | { path: string; kind: 'symlink'; mode: number; modifiedAt: string | null; target: string }\n\nexport type PortableDeskHomeManifest = {\n version: typeof PORTABLE_DESK_HOME_VERSION\n createdAt: string\n entries: readonly PortableDeskHomeEntry[]\n totalBytes: number\n}\n\nexport type PortableDeskHomeArchive = {\n manifest: PortableDeskHomeManifest\n /** Content-addressed file bodies. Identical files occupy one blob. */\n blobs: ReadonlyMap<string, Uint8Array>\n}\n\nexport type DeskHomeSourceEntry =\n | Omit<Extract<PortableDeskHomeEntry, { kind: 'directory' }>, 'path'> & { path: string }\n | Omit<Extract<PortableDeskHomeEntry, { kind: 'file' }>, 'path' | 'size' | 'digest'> & { path: string }\n | Omit<Extract<PortableDeskHomeEntry, { kind: 'symlink' }>, 'path'> & { path: string }\n\nexport interface PortableDeskHomeSource {\n entries(signal: AbortSignal): AsyncIterable<DeskHomeSourceEntry>\n readFile(path: string, signal: AbortSignal): Promise<Uint8Array>\n}\n\nexport interface PortableDeskHomeImport {\n writeDirectory(entry: Extract<PortableDeskHomeEntry, { kind: 'directory' }>, signal: AbortSignal): Promise<void>\n writeFile(\n entry: Extract<PortableDeskHomeEntry, { kind: 'file' }>,\n bytes: Uint8Array,\n signal: AbortSignal,\n ): Promise<void>\n writeSymlink(entry: Extract<PortableDeskHomeEntry, { kind: 'symlink' }>, signal: AbortSignal): Promise<void>\n /** Atomically make the staged home authoritative. */\n commit(signal: AbortSignal): Promise<void>\n /** Best-effort cleanup of an uncommitted staging area. */\n rollback(): Promise<void>\n}\n\nexport interface PortableDeskHomeSink {\n begin(manifest: PortableDeskHomeManifest, signal: AbortSignal): Promise<PortableDeskHomeImport>\n}\n\nexport type PortableDeskHomeLimits = {\n maxEntries?: number\n maxFileBytes?: number\n maxTotalBytes?: number\n}\n\nconst DEFAULT_LIMITS = {\n maxEntries: 20_000,\n maxFileBytes: 64 * 1024 * 1024,\n maxTotalBytes: 512 * 1024 * 1024,\n} as const\n\nexport async function exportPortableDeskHome(\n source: PortableDeskHomeSource,\n options: PortableDeskHomeLimits & { signal?: AbortSignal; now?: () => Date } = {},\n): Promise<PortableDeskHomeArchive> {\n const limits = resolveLimits(options)\n const controller = linkedController(options.signal)\n const entries: PortableDeskHomeEntry[] = []\n const blobs = new Map<string, Uint8Array>()\n let totalBytes = 0\n\n for await (const raw of source.entries(controller.signal)) {\n throwIfAborted(controller.signal)\n if (entries.length >= limits.maxEntries) throw new Error(`Desk home exceeds ${limits.maxEntries} entries.`)\n const path = portablePath(raw.path)\n const base = { path, mode: portableMode(raw.mode), modifiedAt: portableDate(raw.modifiedAt) }\n if (raw.kind === 'directory') {\n entries.push({ ...base, kind: 'directory' })\n continue\n }\n if (raw.kind === 'symlink') {\n entries.push({ ...base, kind: 'symlink', target: portableSymlinkTarget(path, raw.target) })\n continue\n }\n const bytes = await source.readFile(path, controller.signal)\n if (!(bytes instanceof Uint8Array)) throw new Error(`Desk home source returned invalid bytes for ${path}.`)\n if (bytes.byteLength > limits.maxFileBytes) throw new Error(`Desk home file ${path} exceeds ${limits.maxFileBytes} bytes.`)\n totalBytes += bytes.byteLength\n if (totalBytes > limits.maxTotalBytes) throw new Error(`Desk home exceeds ${limits.maxTotalBytes} total bytes.`)\n const digest = digestBytes(bytes)\n if (!blobs.has(digest)) blobs.set(digest, Uint8Array.from(bytes))\n entries.push({ ...base, kind: 'file', size: bytes.byteLength, digest })\n }\n\n entries.sort(compareEntries)\n assertUniquePaths(entries)\n return {\n manifest: {\n version: PORTABLE_DESK_HOME_VERSION,\n createdAt: (options.now?.() ?? new Date()).toISOString(),\n entries,\n totalBytes,\n },\n blobs,\n }\n}\n\nexport async function importPortableDeskHome(\n archive: PortableDeskHomeArchive,\n sink: PortableDeskHomeSink,\n options: PortableDeskHomeLimits & { signal?: AbortSignal } = {},\n): Promise<void> {\n const limits = resolveLimits(options)\n const controller = linkedController(options.signal)\n const manifest = validateArchive(archive, limits)\n const writer = await sink.begin(manifest, controller.signal)\n let committed = false\n try {\n for (const entry of manifest.entries.filter((candidate) => candidate.kind === 'directory')) {\n throwIfAborted(controller.signal)\n await writer.writeDirectory(entry, controller.signal)\n }\n for (const entry of manifest.entries.filter((candidate) => candidate.kind !== 'directory')) {\n throwIfAborted(controller.signal)\n if (entry.kind === 'symlink') await writer.writeSymlink(entry, controller.signal)\n else {\n const bytes = archive.blobs.get(entry.digest)\n if (!bytes) throw new Error(`Desk home blob ${entry.digest} is missing.`)\n await writer.writeFile(entry, Uint8Array.from(bytes), controller.signal)\n }\n }\n await writer.commit(controller.signal)\n committed = true\n } finally {\n if (!committed) await writer.rollback().catch(() => undefined)\n }\n}\n\nfunction validateArchive(archive: PortableDeskHomeArchive, limits: Required<PortableDeskHomeLimits>): PortableDeskHomeManifest {\n const manifest = archive.manifest\n if (manifest.version !== PORTABLE_DESK_HOME_VERSION) throw new Error(`Unsupported desk home version ${String(manifest.version)}.`)\n if (!Number.isFinite(Date.parse(manifest.createdAt))) throw new Error('Desk home createdAt is invalid.')\n if (manifest.entries.length > limits.maxEntries) throw new Error(`Desk home exceeds ${limits.maxEntries} entries.`)\n let totalBytes = 0\n const entries = manifest.entries.map((entry): PortableDeskHomeEntry => {\n const path = portablePath(entry.path)\n const base = { path, mode: portableMode(entry.mode), modifiedAt: portableDate(entry.modifiedAt) }\n if (entry.kind === 'directory') return { ...base, kind: 'directory' }\n if (entry.kind === 'symlink') return { ...base, kind: 'symlink', target: portableSymlinkTarget(path, entry.target) }\n if (!Number.isInteger(entry.size) || entry.size < 0 || entry.size > limits.maxFileBytes) {\n throw new Error(`Desk home file ${path} has an invalid size.`)\n }\n const bytes = archive.blobs.get(entry.digest)\n if (!bytes || bytes.byteLength !== entry.size || digestBytes(bytes) !== entry.digest) {\n throw new Error(`Desk home file ${path} failed content verification.`)\n }\n totalBytes += entry.size\n if (totalBytes > limits.maxTotalBytes) throw new Error(`Desk home exceeds ${limits.maxTotalBytes} total bytes.`)\n return { ...base, kind: 'file', size: entry.size, digest: entry.digest }\n })\n assertUniquePaths(entries)\n if (totalBytes !== manifest.totalBytes) throw new Error('Desk home total byte count does not match its manifest.')\n return { ...manifest, entries: [...entries].sort(compareEntries), totalBytes }\n}\n\nfunction portablePath(value: string): string {\n if (typeof value !== 'string' || value.length === 0 || value.includes('\\0') || value.includes('\\\\')) {\n throw new Error('Desk home paths must be non-empty portable paths.')\n }\n const normalized = posix.normalize(value)\n if (normalized !== value || posix.isAbsolute(value) || value === '..' || value.startsWith('../')) {\n throw new Error(`Desk home path escapes its root: ${value}`)\n }\n return value\n}\n\nfunction portableSymlinkTarget(path: string, target: string): string {\n if (typeof target !== 'string' || target.length === 0 || target.includes('\\0') || target.includes('\\\\') || posix.isAbsolute(target)) {\n throw new Error(`Desk home symlink ${path} has an unsafe target.`)\n }\n const resolved = posix.normalize(posix.join(posix.dirname(path), target))\n if (resolved === '..' || resolved.startsWith('../')) throw new Error(`Desk home symlink ${path} escapes its root.`)\n return target\n}\n\nfunction portableMode(mode: number): number {\n if (!Number.isInteger(mode) || mode < 0 || mode > 0o7777) throw new Error('Desk home mode must be between 0000 and 7777.')\n return mode\n}\n\nfunction portableDate(value: string | null): string | null {\n if (value === null) return null\n if (!Number.isFinite(Date.parse(value))) throw new Error('Desk home modifiedAt is invalid.')\n return value\n}\n\nfunction digestBytes(bytes: Uint8Array): string {\n return `sha256:${createHash('sha256').update(bytes).digest('hex')}`\n}\n\nfunction compareEntries(left: PortableDeskHomeEntry, right: PortableDeskHomeEntry): number {\n const leftDepth = left.path.split('/').length\n const rightDepth = right.path.split('/').length\n return leftDepth - rightDepth || left.path.localeCompare(right.path)\n}\n\nfunction assertUniquePaths(entries: readonly PortableDeskHomeEntry[]): void {\n const paths = new Set<string>()\n for (const entry of entries) {\n if (paths.has(entry.path)) throw new Error(`Desk home contains duplicate path ${entry.path}.`)\n paths.add(entry.path)\n }\n}\n\nfunction resolveLimits(options: PortableDeskHomeLimits): Required<PortableDeskHomeLimits> {\n return {\n maxEntries: positiveLimit(options.maxEntries ?? DEFAULT_LIMITS.maxEntries, 'maxEntries'),\n maxFileBytes: positiveLimit(options.maxFileBytes ?? DEFAULT_LIMITS.maxFileBytes, 'maxFileBytes'),\n maxTotalBytes: positiveLimit(options.maxTotalBytes ?? DEFAULT_LIMITS.maxTotalBytes, 'maxTotalBytes'),\n }\n}\n\nfunction positiveLimit(value: number, name: string): number {\n if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive safe integer.`)\n return value\n}\n\nfunction linkedController(signal?: AbortSignal): AbortController {\n const controller = new AbortController()\n if (!signal) return controller\n if (signal.aborted) controller.abort(signal.reason)\n else signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true })\n return controller\n}\n\nfunction throwIfAborted(signal: AbortSignal): void {\n if (!signal.aborted) return\n if (signal.reason instanceof Error) throw signal.reason\n throw new DOMException('The operation was aborted.', 'AbortError')\n}\n"]}
|
package/protocol.d.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framed JSON protocol spoken between the host and the in-guest agent
|
|
3
|
+
* over vsock. Everything here is pure encode/decode/validate — no sockets, no
|
|
4
|
+
* processes — so the security-critical parsing is unit-testable on any
|
|
5
|
+
* platform, and both the host backend and the guest agent share one message
|
|
6
|
+
* vocabulary.
|
|
7
|
+
*
|
|
8
|
+
* Wire format: a 4-byte big-endian length prefix followed by that many bytes
|
|
9
|
+
* of UTF-8 JSON. Frame sizes are bounded; a peer declaring an oversized frame
|
|
10
|
+
* is a protocol violation and the connection must be dropped, not resynced.
|
|
11
|
+
*/
|
|
12
|
+
import { Buffer } from 'node:buffer';
|
|
13
|
+
import type { A11yNode, DeskFrameFormat, DeskHandoverScope, DeskPoint, DeskPointerButton, DeskVideoChunkKind, WindowInfo } from './events.js';
|
|
14
|
+
export declare const FRAME_HEADER_BYTES = 4;
|
|
15
|
+
export declare const DEFAULT_MAX_FRAME_BYTES: number;
|
|
16
|
+
/** The vsock port the in-guest agent listens on. */
|
|
17
|
+
export declare const GUEST_AGENT_VSOCK_PORT = 5252;
|
|
18
|
+
export declare class DeskProtocolError extends Error {
|
|
19
|
+
readonly name = "DeskProtocolError";
|
|
20
|
+
}
|
|
21
|
+
export type GuestInput = {
|
|
22
|
+
type: 'move';
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'click';
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
button: DeskPointerButton;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'type';
|
|
32
|
+
text: string;
|
|
33
|
+
} | {
|
|
34
|
+
type: 'key';
|
|
35
|
+
combo: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'scroll';
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
dx: number;
|
|
41
|
+
dy: number;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'drag';
|
|
44
|
+
from: DeskPoint;
|
|
45
|
+
to: DeskPoint;
|
|
46
|
+
};
|
|
47
|
+
/** A request body, before the transport assigns its correlation id. */
|
|
48
|
+
export type GuestCommand = {
|
|
49
|
+
op: 'ping';
|
|
50
|
+
} | {
|
|
51
|
+
op: 'capabilities';
|
|
52
|
+
} | {
|
|
53
|
+
op: 'exec';
|
|
54
|
+
command: string;
|
|
55
|
+
args?: readonly string[];
|
|
56
|
+
cwd?: string;
|
|
57
|
+
env?: Readonly<Record<string, string>>;
|
|
58
|
+
timeoutMs?: number;
|
|
59
|
+
} | {
|
|
60
|
+
op: 'job-start';
|
|
61
|
+
command: string;
|
|
62
|
+
args?: readonly string[];
|
|
63
|
+
cwd?: string;
|
|
64
|
+
env?: Readonly<Record<string, string>>;
|
|
65
|
+
} | {
|
|
66
|
+
op: 'job-signal';
|
|
67
|
+
jobId: string;
|
|
68
|
+
signal: 'SIGTERM' | 'SIGKILL';
|
|
69
|
+
} | {
|
|
70
|
+
op: 'screen-start';
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
} | {
|
|
74
|
+
op: 'screen-stop';
|
|
75
|
+
} | {
|
|
76
|
+
op: 'observe';
|
|
77
|
+
} | {
|
|
78
|
+
op: 'input';
|
|
79
|
+
input: GuestInput;
|
|
80
|
+
} | {
|
|
81
|
+
op: 'a11y-invoke';
|
|
82
|
+
nodeId: string;
|
|
83
|
+
action: string;
|
|
84
|
+
} | {
|
|
85
|
+
op: 'launch';
|
|
86
|
+
appId: string;
|
|
87
|
+
args?: readonly string[];
|
|
88
|
+
} | {
|
|
89
|
+
op: 'clipboard-read';
|
|
90
|
+
} | {
|
|
91
|
+
op: 'clipboard-write';
|
|
92
|
+
text: string;
|
|
93
|
+
} | {
|
|
94
|
+
op: 'frames-start';
|
|
95
|
+
fps: number;
|
|
96
|
+
width: number;
|
|
97
|
+
height: number;
|
|
98
|
+
/** Omitted lets the guest choose; a guest that predates this ignores it. */
|
|
99
|
+
format?: DeskFrameFormat;
|
|
100
|
+
} | {
|
|
101
|
+
op: 'frames-stop';
|
|
102
|
+
} | {
|
|
103
|
+
op: 'video-start';
|
|
104
|
+
fps: number;
|
|
105
|
+
width: number;
|
|
106
|
+
height: number;
|
|
107
|
+
} | {
|
|
108
|
+
op: 'video-stop';
|
|
109
|
+
} | {
|
|
110
|
+
op: 'handover-begin';
|
|
111
|
+
ttlMs: number;
|
|
112
|
+
scope: DeskHandoverScope;
|
|
113
|
+
} | {
|
|
114
|
+
op: 'handover-end';
|
|
115
|
+
};
|
|
116
|
+
export type GuestOp = GuestCommand['op'];
|
|
117
|
+
export type GuestRequest = GuestCommand & {
|
|
118
|
+
id: string;
|
|
119
|
+
};
|
|
120
|
+
export type GuestResponse = {
|
|
121
|
+
id: string;
|
|
122
|
+
ok: true;
|
|
123
|
+
result: unknown;
|
|
124
|
+
} | {
|
|
125
|
+
id: string;
|
|
126
|
+
ok: false;
|
|
127
|
+
error: string;
|
|
128
|
+
};
|
|
129
|
+
/** Unsolicited guest-to-host messages: job exits, frames, focus changes. */
|
|
130
|
+
export type GuestEventMessage = {
|
|
131
|
+
event: 'job-exit';
|
|
132
|
+
jobId: string;
|
|
133
|
+
exitCode: number | null;
|
|
134
|
+
signal: string | null;
|
|
135
|
+
} | {
|
|
136
|
+
event: 'frame';
|
|
137
|
+
seq: number;
|
|
138
|
+
width: number;
|
|
139
|
+
height: number;
|
|
140
|
+
data: string;
|
|
141
|
+
/**
|
|
142
|
+
* How `data` is encoded. Absent on the wire means `png`: frames were
|
|
143
|
+
* PNG-only before this field existed, so an older guest paired with a
|
|
144
|
+
* newer host keeps working rather than having its every frame rejected.
|
|
145
|
+
*/
|
|
146
|
+
format: DeskFrameFormat;
|
|
147
|
+
} | {
|
|
148
|
+
event: 'video-chunk';
|
|
149
|
+
seq: number;
|
|
150
|
+
kind: DeskVideoChunkKind;
|
|
151
|
+
/** RFC 6381, e.g. `avc1.42C020`, read out of the bytes by the guest. */
|
|
152
|
+
codec: string;
|
|
153
|
+
width: number;
|
|
154
|
+
height: number;
|
|
155
|
+
/**
|
|
156
|
+
* Whether a `media` chunk begins at a sync sample. Only a chunk that does
|
|
157
|
+
* can start or resume a decoder; `init` chunks report false because the
|
|
158
|
+
* question does not apply to them.
|
|
159
|
+
*/
|
|
160
|
+
keyframe: boolean;
|
|
161
|
+
data: string;
|
|
162
|
+
} | {
|
|
163
|
+
event: 'window-focus';
|
|
164
|
+
window: WindowInfo;
|
|
165
|
+
};
|
|
166
|
+
export type HostBoundMessage = GuestResponse | GuestEventMessage;
|
|
167
|
+
/** The wire shape of an exec result, before the host turns it into a snapshot. */
|
|
168
|
+
export interface GuestExecResult {
|
|
169
|
+
exitCode: number | null;
|
|
170
|
+
signal: string | null;
|
|
171
|
+
stdout: string;
|
|
172
|
+
stderr: string;
|
|
173
|
+
truncated: boolean;
|
|
174
|
+
}
|
|
175
|
+
/** The wire shape of an observation; `png` is base64. */
|
|
176
|
+
export interface GuestObservation {
|
|
177
|
+
png: string;
|
|
178
|
+
width: number;
|
|
179
|
+
height: number;
|
|
180
|
+
a11y: A11yNode | null;
|
|
181
|
+
windows: WindowInfo[];
|
|
182
|
+
focused: WindowInfo | null;
|
|
183
|
+
}
|
|
184
|
+
/** Encode one message as a length-prefixed JSON frame. */
|
|
185
|
+
export declare function encodeFrame(message: unknown, maxFrameBytes?: number): Buffer;
|
|
186
|
+
/**
|
|
187
|
+
* Incremental frame decoder. Feed it arbitrary chunk boundaries; it returns
|
|
188
|
+
* every complete parsed JSON value. A declared frame length beyond the bound
|
|
189
|
+
* or a body that is not valid JSON throws — framing violations are fatal to
|
|
190
|
+
* the connection because there is no way to resynchronize a byte stream whose
|
|
191
|
+
* lengths cannot be trusted.
|
|
192
|
+
*/
|
|
193
|
+
export declare class FrameDecoder {
|
|
194
|
+
#private;
|
|
195
|
+
constructor(options?: {
|
|
196
|
+
maxFrameBytes?: number;
|
|
197
|
+
});
|
|
198
|
+
push(chunk: Buffer): unknown[];
|
|
199
|
+
}
|
|
200
|
+
/** Validate a host-to-guest request. Unknown ops and malformed fields throw. */
|
|
201
|
+
export declare function parseGuestRequest(value: unknown): GuestRequest;
|
|
202
|
+
export declare function parseGuestInput(value: unknown): GuestInput;
|
|
203
|
+
/** Validate a guest-to-host message: a correlated response or an event. */
|
|
204
|
+
export declare function parseHostBoundMessage(value: unknown): HostBoundMessage;
|
|
205
|
+
/** Validate the result payload of an `exec` response. */
|
|
206
|
+
export declare function parseExecResult(value: unknown): GuestExecResult;
|
|
207
|
+
/** Validate the result payload of a `job-start` response. */
|
|
208
|
+
export declare function parseJobStarted(value: unknown): {
|
|
209
|
+
jobId: string;
|
|
210
|
+
};
|
|
211
|
+
/** Validate the result payload of an `observe` response. */
|
|
212
|
+
export declare function parseObservation(value: unknown): GuestObservation;
|
|
213
|
+
/** Validate the result payload of a `handover-begin` response. */
|
|
214
|
+
export declare function parseHandoverBegun(value: unknown): {
|
|
215
|
+
url: string;
|
|
216
|
+
};
|
|
217
|
+
/** Validate the result payload of a `clipboard-read` response. */
|
|
218
|
+
export declare function parseClipboardText(value: unknown): string;
|
|
219
|
+
/** Validate the result payload of a `capabilities` response. */
|
|
220
|
+
export declare function parseCapabilities(value: unknown): {
|
|
221
|
+
virtioGpu: boolean;
|
|
222
|
+
};
|
|
223
|
+
export declare function parseWindowInfo(value: unknown): WindowInfo;
|
|
224
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|