@braedonsaunders/appkit-desk 0.5.0 → 0.6.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/backend.d.ts +43 -1
- package/backend.d.ts.map +1 -1
- package/backend.js +110 -1
- package/backend.js.map +1 -1
- package/events.d.ts +1 -0
- package/events.d.ts.map +1 -1
- package/events.js.map +1 -1
- package/host.d.ts +26 -1
- package/host.d.ts.map +1 -1
- package/host.js +126 -12
- package/host.js.map +1 -1
- package/package.json +1 -1
- package/plan.d.ts +21 -0
- package/plan.d.ts.map +1 -1
- package/plan.js +58 -17
- package/plan.js.map +1 -1
- package/protocol.d.ts +1 -0
- package/protocol.d.ts.map +1 -1
- package/protocol.js +8 -0
- package/protocol.js.map +1 -1
package/plan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAC3D,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAC1C,eAAO,MAAM,qBAAqB,sBAAsB,CAAA;AACxD,eAAO,MAAM,mBAAmB,qBAAqB,CAAA;AACrD,eAAO,MAAM,iBAAiB,MAAM,CAAA;AACpC,eAAO,MAAM,aAAa,IAAI,CAAA;AAC9B,eAAO,MAAM,sBAAsB,2BAA2B,CAAA;AAS9D;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAkB,IAAI,EAAE,MAAM,CAAc;CAC7C;AAED,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAA;IACX,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAA;IACrB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gBAAgB,CAAC,EAAE,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAC3D,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAC1C,eAAO,MAAM,qBAAqB,sBAAsB,CAAA;AACxD,eAAO,MAAM,mBAAmB,qBAAqB,CAAA;AACrD,eAAO,MAAM,iBAAiB,MAAM,CAAA;AACpC,eAAO,MAAM,aAAa,IAAI,CAAA;AAC9B,eAAO,MAAM,sBAAsB,2BAA2B,CAAA;AAS9D;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAkB,IAAI,EAAE,MAAM,CAAc;CAC7C;AAED,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAA;IACX,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAA;IACrB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gBAAgB,CAAC,EAAE,oBAAoB,CAAA;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IACxC,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAA;QACZ,QAAQ,EAAE,MAAM,CAAA;QAChB,4EAA4E;QAC5E,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,EAAE,CAAA;SAAE,GAAG,IAAI,CAAA;KACnD,CAAA;IACD,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,GAAG,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3B,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC,2EAA2E;IAC3E,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAA;IAC7C,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,qEAAqE;IACrE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,oBAAoB,CAAA;CACxC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAClC,GAAG,OAAO,CAQf;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,qBAAqB,EAC9B,YAAY,GAAE;IACZ,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACtC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CACpC,GACL,cAAc,CAsIhB;AAoCD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,oBAAoB,GAAG,SAAS,GACzC,oBAAoB,GAAG,SAAS,CAUlC"}
|
package/plan.js
CHANGED
|
@@ -89,6 +89,19 @@ export function buildDeskLaunchPlan(options, dependencies = {}) {
|
|
|
89
89
|
const launcherIdentity = cleanLauncherIdentity(options.launcherIdentity);
|
|
90
90
|
const vsockSocketPath = join(runtimeDir, `desk-${deskId}.vsock`);
|
|
91
91
|
const apiSocketPath = join(runtimeDir, `desk-${deskId}.api.sock`);
|
|
92
|
+
// Beside the overlay by default: a snapshot is the guest's RAM, so it belongs
|
|
93
|
+
// on the volume provisioned for desk state rather than on a runtime tmpfs.
|
|
94
|
+
const snapshotDir = options.snapshotDir
|
|
95
|
+
? absolutePath(options.snapshotDir, 'snapshotDir')
|
|
96
|
+
: join(overlayDirectory, `${deskId}.snapshot`);
|
|
97
|
+
const restoring = options.restore === true;
|
|
98
|
+
if (restoring && !pathExists(snapshotDir)) {
|
|
99
|
+
// Refused rather than silently cold-booted. A caller asking to restore has
|
|
100
|
+
// been told this desk kept its memory; quietly booting a fresh kernel would
|
|
101
|
+
// lose the process tree it was relying on and look like a crash inside the
|
|
102
|
+
// guest instead of a missing snapshot out here.
|
|
103
|
+
throw new DeskError(`Desk ${deskId} has no snapshot to restore at ${snapshotDir}`);
|
|
104
|
+
}
|
|
92
105
|
const create = pathExists(overlayPath)
|
|
93
106
|
? null
|
|
94
107
|
: {
|
|
@@ -101,23 +114,50 @@ export function buildDeskLaunchPlan(options, dependencies = {}) {
|
|
|
101
114
|
command: 'cp',
|
|
102
115
|
args: ['--reflink=auto', baseImagePath, overlayPath],
|
|
103
116
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// A restoring VMM is handed the snapshot and nothing else.
|
|
118
|
+
//
|
|
119
|
+
// The snapshot carries the whole machine config — memory size, cpus, disk,
|
|
120
|
+
// vsock, net — so passing `--kernel`/`--disk` alongside `--restore` is not
|
|
121
|
+
// redundant, it is rejected.
|
|
122
|
+
//
|
|
123
|
+
// `resume=false` is deliberate, and it is the difference between this being
|
|
124
|
+
// safe and being a data-loss bug. A memory snapshot is only valid against the
|
|
125
|
+
// disk as it stood when the snapshot was taken. Resume the guest in this same
|
|
126
|
+
// spawn and it starts writing immediately, so for as long as the snapshot
|
|
127
|
+
// still exists there are two valid-looking pasts for one desk — and a host
|
|
128
|
+
// that died in that window would, on the next start, replay this memory
|
|
129
|
+
// against a disk that had moved on. That is corruption, not a stale cache.
|
|
130
|
+
//
|
|
131
|
+
// So the guest comes back PAUSED, the caller destroys the snapshot while
|
|
132
|
+
// nothing can write, and only then is the VM resumed.
|
|
133
|
+
//
|
|
134
|
+
// The tap is reopened BY NAME from the snapshotted config. That is the detail
|
|
135
|
+
// that makes this possible at all: Cloud Hypervisor's `net_fds` restore
|
|
136
|
+
// parameter exists for VMs whose tap was passed as a file descriptor, and
|
|
137
|
+
// producing one from Node would mean an ioctl(TUNSETIFF) it cannot make.
|
|
138
|
+
// Because `deskPlan` configures `--net tap=<name>`, there is nothing to pass.
|
|
139
|
+
const args = restoring
|
|
140
|
+
? [
|
|
141
|
+
'--api-socket', apiSocketPath,
|
|
142
|
+
'--restore', `source_url=file://${snapshotDir},resume=false`,
|
|
143
|
+
]
|
|
144
|
+
: [
|
|
145
|
+
'--api-socket', apiSocketPath,
|
|
146
|
+
'--kernel', kernelPath,
|
|
147
|
+
// A modular distro kernel needs its initramfs to bring up virtio_blk and
|
|
148
|
+
// mount root; omitted only for a kernel with those drivers built in.
|
|
149
|
+
...(initramfsPath === null ? [] : ['--initramfs', initramfsPath]),
|
|
150
|
+
'--cmdline', kernelCmdline,
|
|
151
|
+
// Be explicit that the disk is raw: CH deprecated image-format auto-detection
|
|
152
|
+
// and warns without image_type; there is never a qcow2 overlay here.
|
|
153
|
+
'--disk', `path=${overlayPath},image_type=raw`,
|
|
154
|
+
'--memory', `size=${memoryMb}M`,
|
|
155
|
+
'--cpus', `boot=${vcpus}`,
|
|
156
|
+
'--vsock', `cid=${vsockCid},socket=${vsockSocketPath}`,
|
|
157
|
+
'--net', `tap=${tapDevice},mac=${macAddress}`,
|
|
158
|
+
'--serial', 'off',
|
|
159
|
+
'--console', 'off',
|
|
160
|
+
];
|
|
121
161
|
return {
|
|
122
162
|
deskId,
|
|
123
163
|
vmm: { command: vmmPath, args },
|
|
@@ -125,6 +165,7 @@ export function buildDeskLaunchPlan(options, dependencies = {}) {
|
|
|
125
165
|
vsock: { cid: vsockCid, socketPath: vsockSocketPath },
|
|
126
166
|
api: { socketPath: apiSocketPath },
|
|
127
167
|
tap: { device: tapDevice, mac: macAddress },
|
|
168
|
+
snapshot: { dir: snapshotDir, restoring },
|
|
128
169
|
memoryMb,
|
|
129
170
|
vcpus,
|
|
130
171
|
kernelPath,
|
package/plan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAE9D,MAAM,CAAC,MAAM,gBAAgB,GAAG,2BAA2B,CAAA;AAC3D,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA;AAC1C,MAAM,CAAC,MAAM,qBAAqB,GAAG,mBAAmB,CAAA;AACxD,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAA;AACrD,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAA;AACpC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAA;AAC9B,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAA;AAE9D,MAAM,eAAe,GAAG,mCAAmC,CAAA;AAC3D,MAAM,kBAAkB,GAAG,kCAAkC,CAAA;AAC7D,MAAM,mBAAmB,GAAG,mCAAmC,CAAA;AAC/D,gEAAgE;AAChE,MAAM,aAAa,GAAG,CAAC,CAAA;AACvB,MAAM,aAAa,GAAG,WAAW,CAAA;AAEjC;;;;;GAKG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAChB,IAAI,GAAW,WAAW,CAAA;CAC7C;AAmED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,UAK5B,EAAE;IACJ,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAA;IACrD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,UAAU,CAAA;IACnD,OAAO,CACL,QAAQ,KAAK,OAAO;WACjB,UAAU,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC;WAC/C,UAAU,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC,CACnD,CAAA;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CACjB,sEAAsE,CACvE,CAAA;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAA8B,EAC9B,eAGI,EAAE;IAEN,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,IAAI,UAAU,CAAA;IACxD,MAAM,YAAY,GAAG,YAAY,CAAC,YAAY,IAAI,UAAU,CAAA;IAC5D,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,EAAE,SAAS,CAAC,CAAA;IAC5E,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,SAAS,CACjB,2BAA2B,OAAO,2CAA2C;cACzE,0CAA0C,CAC/C,CAAA;IACH,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,EAAE,SAAS,CAAC,CAAA;IAC5E,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,SAAS,CACjB,qCAAqC,OAAO,kDAAkD,CAC/F,CAAA;IACH,CAAC;IACD,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IACjE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,SAAS,CAAC,gCAAgC,UAAU,EAAE,CAAC,CAAA;IACnE,CAAC;IACD,MAAM,aAAa,GACjB,OAAO,CAAC,aAAa,KAAK,SAAS;QACjC,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,aAAa,EAAE,eAAe,CAAC,CAAA;IAC1D,IAAI,aAAa,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,SAAS,CAAC,6BAA6B,aAAa,EAAE,CAAC,CAAA;IACnE,CAAC;IACD,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,aAAa,EAAE,eAAe,CAAC,CAAA;IAC1E,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,SAAS,CAAC,8BAA8B,aAAa,EAAE,CAAC,CAAA;IACpE,CAAC;IACD,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,aAAa,CAAC,CAAA;IACpE,MAAM,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7C,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,SAAS,CAAC,qCAAqC,gBAAgB,EAAE,CAAC,CAAA;IAC9E,CAAC;IACD,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,IAAI,iBAAiB,EAAE,UAAU,CAAC,CAAA;IACnF,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,IAAI,aAAa,EAAE,OAAO,CAAC,CAAA;IACtE,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC3C,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,UAAU,IAAI,mBAAmB,EAAE,YAAY,CAAC,CAAA;IACxF,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,CAAC,SAAS,IAAI,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAA;IACjF,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,UAAU,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAA;IACrF,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,sBAAsB,CAAA;IACrE,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACxE,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,MAAM,QAAQ,CAAC,CAAA;IAChE,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,MAAM,WAAW,CAAC,CAAA;IAEjE,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC;QACpC,CAAC,CAAC,IAAI;QACN,CAAC,CAAC;YACE,sEAAsE;YACtE,8DAA8D;YAC9D,sEAAsE;YACtE,yEAAyE;YACzE,wEAAwE;YACxE,0EAA0E;YAC1E,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,CAAC,gBAAgB,EAAE,aAAa,EAAE,WAAW,CAAC;SACrD,CAAA;IAEL,MAAM,IAAI,GAAG;QACX,cAAc,EAAE,aAAa;QAC7B,UAAU,EAAE,UAAU;QACtB,yEAAyE;QACzE,qEAAqE;QACrE,GAAG,CAAC,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACjE,WAAW,EAAE,aAAa;QAC1B,8EAA8E;QAC9E,qEAAqE;QACrE,QAAQ,EAAE,QAAQ,WAAW,iBAAiB;QAC9C,UAAU,EAAE,QAAQ,QAAQ,GAAG;QAC/B,QAAQ,EAAE,QAAQ,KAAK,EAAE;QACzB,SAAS,EAAE,OAAO,QAAQ,WAAW,eAAe,EAAE;QACtD,OAAO,EAAE,OAAO,SAAS,QAAQ,UAAU,EAAE;QAC7C,UAAU,EAAE,KAAK;QACjB,WAAW,EAAE,KAAK;KACnB,CAAA;IAED,OAAO;QACL,MAAM;QACN,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE;QAC/B,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE;QAC/D,KAAK,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE;QACrD,GAAG,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE;QAClC,GAAG,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE;QAC3C,QAAQ;QACR,KAAK;QACL,UAAU;QACV,aAAa;QACb,aAAa;QACb,gBAAgB;KACjB,CAAA;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,MAAM,GAAG,EAAE,CAAA;AACpB,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAW;IACpC,6EAA6E;IAC7E,2EAA2E;IAC3E,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC,CAAA;IACrG,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC7E,CAAC;AAED,SAAS,QAAQ,CAAC,KAAa;IAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,aAAa,IAAI,KAAK,GAAG,aAAa,EAAE,CAAC;QAC/E,MAAM,IAAI,SAAS,CACjB,uCAAuC,aAAa,QAAQ,aAAa,GAAG,CAC7E,CAAA;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CAAC,iFAAiF,CAAC,CAAA;IACxG,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,SAAS,CAAC,0CAA0C,KAAK,EAAE,CAAC,CAAA;IACxE,CAAC;IACD,OAAO,KAAK,CAAC,WAAW,EAAE,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,QAA0C;IAE1C,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC/B,MAAM,GAAG,GAAG,aAAa,CAAA;IACzB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,GAAG,GAAG,CAAC,IAAI,QAAQ,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;QAC9E,MAAM,IAAI,SAAS,CAAC,yDAAyD,GAAG,GAAG,CAAC,CAAA;IACtF,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,GAAG,GAAG,CAAC,IAAI,QAAQ,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;QAC9E,MAAM,IAAI,SAAS,CAAC,yDAAyD,GAAG,GAAG,CAAC,CAAA;IACtF,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAA;AACjD,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,KAAa;IAChD,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CAAC,GAAG,KAAK,4BAA4B,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAA;AACvB,CAAC;AAED,SAAS,eAAe,CAAC,KAAa,EAAE,KAAa;IACnD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,SAAS,CAAC,GAAG,KAAK,8BAA8B,CAAC,CAAA;IAC7D,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC","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"]}
|
|
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 * Where this desk's memory snapshot lives. Defaults beside the overlay, which\n * is the volume sized for desk state — a snapshot is roughly `memoryMb` of\n * guest RAM, so it does not belong on a runtime tmpfs.\n */\n snapshotDir?: string\n /**\n * Resume from that snapshot instead of booting the kernel.\n *\n * A restored VM continues with its process tree intact, which is the whole\n * point of parking with memory rather than shutting down: an agent's in-guest\n * daemon survives. Refused when the snapshot is not there, rather than falling\n * back to a cold boot — a silent cold boot is how a desk loses the state it\n * was promised to have kept.\n */\n restore?: boolean\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 /** Where a memory snapshot of this desk is written, and read back from. */\n snapshot: { dir: string; restoring: boolean }\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 // Beside the overlay by default: a snapshot is the guest's RAM, so it belongs\n // on the volume provisioned for desk state rather than on a runtime tmpfs.\n const snapshotDir = options.snapshotDir\n ? absolutePath(options.snapshotDir, 'snapshotDir')\n : join(overlayDirectory, `${deskId}.snapshot`)\n const restoring = options.restore === true\n if (restoring && !pathExists(snapshotDir)) {\n // Refused rather than silently cold-booted. A caller asking to restore has\n // been told this desk kept its memory; quietly booting a fresh kernel would\n // lose the process tree it was relying on and look like a crash inside the\n // guest instead of a missing snapshot out here.\n throw new DeskError(`Desk ${deskId} has no snapshot to restore at ${snapshotDir}`)\n }\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 // A restoring VMM is handed the snapshot and nothing else.\n //\n // The snapshot carries the whole machine config — memory size, cpus, disk,\n // vsock, net — so passing `--kernel`/`--disk` alongside `--restore` is not\n // redundant, it is rejected.\n //\n // `resume=false` is deliberate, and it is the difference between this being\n // safe and being a data-loss bug. A memory snapshot is only valid against the\n // disk as it stood when the snapshot was taken. Resume the guest in this same\n // spawn and it starts writing immediately, so for as long as the snapshot\n // still exists there are two valid-looking pasts for one desk — and a host\n // that died in that window would, on the next start, replay this memory\n // against a disk that had moved on. That is corruption, not a stale cache.\n //\n // So the guest comes back PAUSED, the caller destroys the snapshot while\n // nothing can write, and only then is the VM resumed.\n //\n // The tap is reopened BY NAME from the snapshotted config. That is the detail\n // that makes this possible at all: Cloud Hypervisor's `net_fds` restore\n // parameter exists for VMs whose tap was passed as a file descriptor, and\n // producing one from Node would mean an ioctl(TUNSETIFF) it cannot make.\n // Because `deskPlan` configures `--net tap=<name>`, there is nothing to pass.\n const args = restoring\n ? [\n '--api-socket', apiSocketPath,\n '--restore', `source_url=file://${snapshotDir},resume=false`,\n ]\n : [\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 snapshot: { dir: snapshotDir, restoring },\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"]}
|
package/protocol.d.ts
CHANGED
package/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACX,MAAM,UAAU,CAAA;AAEjB,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,uBAAuB,QAAkB,CAAA;AACtD,oDAAoD;AACpD,eAAO,MAAM,sBAAsB,OAAO,CAAA;AAU1C,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAkB,IAAI,uBAAsB;CAC7C;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACX,MAAM,UAAU,CAAA;AAEjB,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,uBAAuB,QAAkB,CAAA;AACtD,oDAAoD;AACpD,eAAO,MAAM,sBAAsB,OAAO,CAAA;AAU1C,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAkB,IAAI,uBAAsB;CAC7C;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,SAAS,CAAA;CAAE,CAAA;AAEpD,uEAAuE;AACvE,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GACd;IAAE,EAAE,EAAE,cAAc,CAAA;CAAE,GACtB;IACE,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GACD;IACE,EAAE,EAAE,WAAW,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACvC,GACD;IAAE,EAAE,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,GAClE;IAAE,EAAE,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,EAAE,EAAE,aAAa,CAAA;CAAE,GACrB;IAAE,EAAE,EAAE,SAAS,CAAA;CAAE,GACjB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACzD;IAAE,EAAE,EAAE,gBAAgB,CAAA;CAAE,GACxB;IAAE,EAAE,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvC;IACE,EAAE,EAAE,cAAc,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,MAAM,CAAC,EAAE,eAAe,CAAA;CACzB,GACD;IAAE,EAAE,EAAE,aAAa,CAAA;CAAE,GACrB;IAAE,EAAE,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,EAAE,EAAE,YAAY,CAAA;CAAE,GACpB;IAAE,EAAE,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,GACjE;IAAE,EAAE,EAAE,cAAc,CAAA;CAAE,CAAA;AAE1B,MAAM,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;AAExC,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAExD,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5C,4EAA4E;AAC5E,MAAM,MAAM,iBAAiB,GACzB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACpF;IACE,KAAK,EAAE,OAAO,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,EAAE,eAAe,CAAA;CACxB,GACD;IACE,KAAK,EAAE,aAAa,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,kBAAkB,CAAA;IACxB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAAA;AAEjD,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAEhE,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAA;IACrB,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,OAAO,EAAE,UAAU,GAAG,IAAI,CAAA;CAC3B;AAED,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,SAA0B,GAAG,MAAM,CAW7F;AAED;;;;;;GAMG;AACH,qBAAa,YAAY;;gBAIX,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAO;IAQpD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE;CAyB/B;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CA+F9D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CA+B1D;AAQD,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CA2CtE;AAED,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAS/D;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAGjE;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAejE;AAED,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAGlE;AAED,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGzD;AAED,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,CAGxE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAO1D"}
|
package/protocol.js
CHANGED
|
@@ -185,6 +185,7 @@ export function parseGuestInput(value) {
|
|
|
185
185
|
x: requireCoordinate(record, 'x'),
|
|
186
186
|
y: requireCoordinate(record, 'y'),
|
|
187
187
|
button: parsePointerButton(record.button),
|
|
188
|
+
clicks: parseClickCount(record.clicks),
|
|
188
189
|
};
|
|
189
190
|
case 'type':
|
|
190
191
|
return { type, text: requireString(record, 'text') };
|
|
@@ -204,6 +205,13 @@ export function parseGuestInput(value) {
|
|
|
204
205
|
throw new DeskProtocolError(`Unknown input type: ${type}`);
|
|
205
206
|
}
|
|
206
207
|
}
|
|
208
|
+
function parseClickCount(value) {
|
|
209
|
+
if (value === undefined || value === 1)
|
|
210
|
+
return 1;
|
|
211
|
+
if (value === 2)
|
|
212
|
+
return 2;
|
|
213
|
+
throw new DeskProtocolError('clicks must be 1 or 2.');
|
|
214
|
+
}
|
|
207
215
|
/** Validate a guest-to-host message: a correlated response or an event. */
|
|
208
216
|
export function parseHostBoundMessage(value) {
|
|
209
217
|
const record = asRecord(value, 'message');
|
package/protocol.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The framed JSON protocol spoken between the host and the in-guest agent\n * over vsock. Everything here is pure encode/decode/validate — no sockets, no\n * processes — so the security-critical parsing is unit-testable on any\n * platform, and both the host backend and the guest agent share one message\n * vocabulary.\n *\n * Wire format: a 4-byte big-endian length prefix followed by that many bytes\n * of UTF-8 JSON. Frame sizes are bounded; a peer declaring an oversized frame\n * is a protocol violation and the connection must be dropped, not resynced.\n */\nimport { Buffer } from 'node:buffer'\nimport type {\n A11yNode,\n DeskFrameFormat,\n DeskHandoverScope,\n DeskPoint,\n DeskPointerButton,\n DeskVideoChunkKind,\n WindowInfo,\n} from './events'\n\nexport const FRAME_HEADER_BYTES = 4\nexport const DEFAULT_MAX_FRAME_BYTES = 8 * 1024 * 1024\n/** The vsock port the in-guest agent listens on. */\nexport const GUEST_AGENT_VSOCK_PORT = 5252\n\nconst REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/\nconst MAX_STRING_LENGTH = 65_536\nconst MAX_ARGUMENTS = 256\nconst MAX_ENVIRONMENT_ENTRIES = 128\nconst MAX_WINDOWS = 256\nconst MAX_A11Y_NODES = 10_000\nconst MAX_A11Y_DEPTH = 32\n\nexport class DeskProtocolError extends Error {\n override readonly name = 'DeskProtocolError'\n}\n\nexport type GuestInput =\n | { type: 'move'; x: number; y: number }\n | { type: 'click'; x: number; y: number; button: DeskPointerButton }\n | { type: 'type'; text: string }\n | { type: 'key'; combo: string }\n | { type: 'scroll'; x: number; y: number; dx: number; dy: number }\n | { type: 'drag'; from: DeskPoint; to: DeskPoint }\n\n/** A request body, before the transport assigns its correlation id. */\nexport type GuestCommand =\n | { op: 'ping' }\n | { op: 'capabilities' }\n | {\n op: 'exec'\n command: string\n args?: readonly string[]\n cwd?: string\n env?: Readonly<Record<string, string>>\n timeoutMs?: number\n }\n | {\n op: 'job-start'\n command: string\n args?: readonly string[]\n cwd?: string\n env?: Readonly<Record<string, string>>\n }\n | { op: 'job-signal'; jobId: string; signal: 'SIGTERM' | 'SIGKILL' }\n | { op: 'screen-start'; width: number; height: number }\n | { op: 'screen-stop' }\n | { op: 'observe' }\n | { op: 'input'; input: GuestInput }\n | { op: 'a11y-invoke'; nodeId: string; action: string }\n | { op: 'launch'; appId: string; args?: readonly string[] }\n | { op: 'clipboard-read' }\n | { op: 'clipboard-write'; text: string }\n | {\n op: 'frames-start'\n fps: number\n width: number\n height: number\n /** Omitted lets the guest choose; a guest that predates this ignores it. */\n format?: DeskFrameFormat\n }\n | { op: 'frames-stop' }\n | { op: 'video-start'; fps: number; width: number; height: number }\n | { op: 'video-stop' }\n | { op: 'handover-begin'; ttlMs: number; scope: DeskHandoverScope }\n | { op: 'handover-end' }\n\nexport type GuestOp = GuestCommand['op']\n\nexport type GuestRequest = GuestCommand & { id: string }\n\nexport type GuestResponse =\n | { id: string; ok: true; result: unknown }\n | { id: string; ok: false; error: string }\n\n/** Unsolicited guest-to-host messages: job exits, frames, focus changes. */\nexport type GuestEventMessage =\n | { event: 'job-exit'; jobId: string; exitCode: number | null; signal: string | null }\n | {\n event: 'frame'\n seq: number\n width: number\n height: number\n data: string\n /**\n * How `data` is encoded. Absent on the wire means `png`: frames were\n * PNG-only before this field existed, so an older guest paired with a\n * newer host keeps working rather than having its every frame rejected.\n */\n format: DeskFrameFormat\n }\n | {\n event: 'video-chunk'\n seq: number\n kind: DeskVideoChunkKind\n /** RFC 6381, e.g. `avc1.42C020`, read out of the bytes by the guest. */\n codec: string\n width: number\n height: number\n /**\n * Whether a `media` chunk begins at a sync sample. Only a chunk that does\n * can start or resume a decoder; `init` chunks report false because the\n * question does not apply to them.\n */\n keyframe: boolean\n data: string\n }\n | { event: 'window-focus'; window: WindowInfo }\n\nexport type HostBoundMessage = GuestResponse | GuestEventMessage\n\n/** The wire shape of an exec result, before the host turns it into a snapshot. */\nexport interface GuestExecResult {\n exitCode: number | null\n signal: string | null\n stdout: string\n stderr: string\n truncated: boolean\n}\n\n/** The wire shape of an observation; `png` is base64. */\nexport interface GuestObservation {\n png: string\n width: number\n height: number\n a11y: A11yNode | null\n windows: WindowInfo[]\n focused: WindowInfo | null\n}\n\n/** Encode one message as a length-prefixed JSON frame. */\nexport function encodeFrame(message: unknown, maxFrameBytes = DEFAULT_MAX_FRAME_BYTES): Buffer {\n const body = Buffer.from(JSON.stringify(message), 'utf8')\n if (body.byteLength > maxFrameBytes) {\n throw new DeskProtocolError(\n `Refusing to encode a ${body.byteLength}-byte frame; the limit is ${maxFrameBytes}.`,\n )\n }\n const frame = Buffer.allocUnsafe(FRAME_HEADER_BYTES + body.byteLength)\n frame.writeUInt32BE(body.byteLength, 0)\n body.copy(frame, FRAME_HEADER_BYTES)\n return frame\n}\n\n/**\n * Incremental frame decoder. Feed it arbitrary chunk boundaries; it returns\n * every complete parsed JSON value. A declared frame length beyond the bound\n * or a body that is not valid JSON throws — framing violations are fatal to\n * the connection because there is no way to resynchronize a byte stream whose\n * lengths cannot be trusted.\n */\nexport class FrameDecoder {\n readonly #maxFrameBytes: number\n #buffered: Buffer = Buffer.alloc(0)\n\n constructor(options: { maxFrameBytes?: number } = {}) {\n const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES\n if (!Number.isInteger(maxFrameBytes) || maxFrameBytes < 1) {\n throw new DeskProtocolError('maxFrameBytes must be a positive integer.')\n }\n this.#maxFrameBytes = maxFrameBytes\n }\n\n push(chunk: Buffer): unknown[] {\n this.#buffered = this.#buffered.byteLength === 0\n ? chunk\n : Buffer.concat([this.#buffered, chunk])\n const messages: unknown[] = []\n while (this.#buffered.byteLength >= FRAME_HEADER_BYTES) {\n const declared = this.#buffered.readUInt32BE(0)\n if (declared > this.#maxFrameBytes) {\n throw new DeskProtocolError(\n `Peer declared a ${declared}-byte frame; the limit is ${this.#maxFrameBytes}.`,\n )\n }\n if (this.#buffered.byteLength < FRAME_HEADER_BYTES + declared) break\n const body = this.#buffered.subarray(FRAME_HEADER_BYTES, FRAME_HEADER_BYTES + declared)\n this.#buffered = this.#buffered.subarray(FRAME_HEADER_BYTES + declared)\n try {\n messages.push(JSON.parse(body.toString('utf8')))\n } catch (error) {\n throw new DeskProtocolError(\n `Frame body is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n return messages\n }\n}\n\n/** Validate a host-to-guest request. Unknown ops and malformed fields throw. */\nexport function parseGuestRequest(value: unknown): GuestRequest {\n const record = asRecord(value, 'request')\n const id = requireString(record, 'id')\n if (!REQUEST_ID_PATTERN.test(id)) {\n throw new DeskProtocolError('Request id must be 1–128 URL-safe characters.')\n }\n const op = requireString(record, 'op')\n switch (op) {\n case 'ping':\n case 'capabilities':\n case 'screen-stop':\n case 'observe':\n case 'clipboard-read':\n case 'frames-stop':\n case 'video-stop':\n case 'handover-end':\n return { id, op }\n case 'exec':\n return {\n id,\n op,\n command: requireString(record, 'command'),\n args: optionalStringArray(record, 'args'),\n cwd: optionalString(record, 'cwd'),\n env: optionalEnvironment(record),\n timeoutMs: optionalPositiveInteger(record, 'timeoutMs'),\n }\n case 'job-start':\n return {\n id,\n op,\n command: requireString(record, 'command'),\n args: optionalStringArray(record, 'args'),\n cwd: optionalString(record, 'cwd'),\n env: optionalEnvironment(record),\n }\n case 'job-signal': {\n const signal = requireString(record, 'signal')\n if (signal !== 'SIGTERM' && signal !== 'SIGKILL') {\n throw new DeskProtocolError('job-signal signal must be SIGTERM or SIGKILL.')\n }\n return { id, op, jobId: requireString(record, 'jobId'), signal }\n }\n case 'screen-start':\n return {\n id,\n op,\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n }\n case 'input':\n return { id, op, input: parseGuestInput(record.input) }\n case 'a11y-invoke':\n return {\n id,\n op,\n nodeId: requireString(record, 'nodeId'),\n action: requireString(record, 'action'),\n }\n case 'launch':\n return {\n id,\n op,\n appId: requireString(record, 'appId'),\n args: optionalStringArray(record, 'args'),\n }\n case 'clipboard-write':\n return { id, op, text: requireString(record, 'text') }\n case 'frames-start':\n return {\n id,\n op,\n fps: requirePositiveInteger(record, 'fps'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n format: optionalFrameFormat(record),\n }\n case 'video-start':\n return {\n id,\n op,\n fps: requirePositiveInteger(record, 'fps'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n }\n case 'handover-begin':\n return {\n id,\n op,\n ttlMs: requirePositiveInteger(record, 'ttlMs'),\n scope: parseHandoverScope(record.scope),\n }\n default:\n throw new DeskProtocolError(`Unknown request op: ${op}`)\n }\n}\n\nexport function parseGuestInput(value: unknown): GuestInput {\n const record = asRecord(value, 'input')\n const type = requireString(record, 'type')\n switch (type) {\n case 'move':\n return { type, x: requireCoordinate(record, 'x'), y: requireCoordinate(record, 'y') }\n case 'click':\n return {\n type,\n x: requireCoordinate(record, 'x'),\n y: requireCoordinate(record, 'y'),\n button: parsePointerButton(record.button),\n }\n case 'type':\n return { type, text: requireString(record, 'text') }\n case 'key':\n return { type, combo: requireString(record, 'combo') }\n case 'scroll':\n return {\n type,\n x: requireCoordinate(record, 'x'),\n y: requireCoordinate(record, 'y'),\n dx: requireDelta(record, 'dx'),\n dy: requireDelta(record, 'dy'),\n }\n case 'drag':\n return { type, from: parsePoint(record.from), to: parsePoint(record.to) }\n default:\n throw new DeskProtocolError(`Unknown input type: ${type}`)\n }\n}\n\n/** Validate a guest-to-host message: a correlated response or an event. */\nexport function parseHostBoundMessage(value: unknown): HostBoundMessage {\n const record = asRecord(value, 'message')\n if ('id' in record) {\n const id = requireString(record, 'id')\n if (record.ok === true) return { id, ok: true, result: record.result ?? {} }\n if (record.ok === false) return { id, ok: false, error: requireString(record, 'error') }\n throw new DeskProtocolError('Response ok must be a boolean.')\n }\n const event = requireString(record, 'event')\n switch (event) {\n case 'job-exit':\n return {\n event,\n jobId: requireString(record, 'jobId'),\n exitCode: nullableInteger(record, 'exitCode'),\n signal: nullableString(record, 'signal'),\n }\n case 'frame':\n return {\n event,\n seq: requireNonNegativeInteger(record, 'seq'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n data: requireUnboundedString(record, 'data'),\n // Absent means png — see the field's contract on GuestEventMessage.\n format: optionalFrameFormat(record) ?? 'png',\n }\n case 'video-chunk':\n return {\n event,\n seq: requireNonNegativeInteger(record, 'seq'),\n kind: parseVideoChunkKind(record.kind),\n codec: requireString(record, 'codec'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n keyframe: record.keyframe === true,\n data: requireUnboundedString(record, 'data'),\n }\n case 'window-focus':\n return { event, window: parseWindowInfo(record.window) }\n default:\n throw new DeskProtocolError(`Unknown event: ${event}`)\n }\n}\n\n/** Validate the result payload of an `exec` response. */\nexport function parseExecResult(value: unknown): GuestExecResult {\n const record = asRecord(value, 'exec result')\n return {\n exitCode: nullableInteger(record, 'exitCode'),\n signal: nullableString(record, 'signal'),\n stdout: requireUnboundedString(record, 'stdout'),\n stderr: requireUnboundedString(record, 'stderr'),\n truncated: record.truncated === true,\n }\n}\n\n/** Validate the result payload of a `job-start` response. */\nexport function parseJobStarted(value: unknown): { jobId: string } {\n const record = asRecord(value, 'job-start result')\n return { jobId: requireString(record, 'jobId') }\n}\n\n/** Validate the result payload of an `observe` response. */\nexport function parseObservation(value: unknown): GuestObservation {\n const record = asRecord(value, 'observation')\n const windows = parseWindows(record.windows)\n return {\n png: requireUnboundedString(record, 'png'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n a11y: record.a11y === null || record.a11y === undefined\n ? null\n : parseA11yTree(record.a11y),\n windows,\n focused: record.focused === null || record.focused === undefined\n ? null\n : parseWindowInfo(record.focused),\n }\n}\n\n/** Validate the result payload of a `handover-begin` response. */\nexport function parseHandoverBegun(value: unknown): { url: string } {\n const record = asRecord(value, 'handover-begin result')\n return { url: requireString(record, 'url') }\n}\n\n/** Validate the result payload of a `clipboard-read` response. */\nexport function parseClipboardText(value: unknown): string {\n const record = asRecord(value, 'clipboard-read result')\n return requireUnboundedString(record, 'text')\n}\n\n/** Validate the result payload of a `capabilities` response. */\nexport function parseCapabilities(value: unknown): { virtioGpu: boolean } {\n const record = asRecord(value, 'capabilities result')\n return { virtioGpu: record.virtioGpu === true }\n}\n\nexport function parseWindowInfo(value: unknown): WindowInfo {\n const record = asRecord(value, 'window')\n return {\n id: requireString(record, 'id'),\n title: requireString(record, 'title'),\n appId: nullableString(record, 'appId'),\n }\n}\n\nfunction parseWindows(value: unknown): WindowInfo[] {\n if (value === undefined || value === null) return []\n if (!Array.isArray(value) || value.length > MAX_WINDOWS) {\n throw new DeskProtocolError(`windows must be an array of at most ${MAX_WINDOWS} entries.`)\n }\n return value.map(parseWindowInfo)\n}\n\nfunction parseA11yTree(value: unknown): A11yNode {\n const budget = { remaining: MAX_A11Y_NODES }\n return parseA11yNode(value, budget, 0)\n}\n\nfunction parseA11yNode(value: unknown, budget: { remaining: number }, depth: number): A11yNode {\n if (depth > MAX_A11Y_DEPTH) {\n throw new DeskProtocolError(`Accessibility tree exceeds depth ${MAX_A11Y_DEPTH}.`)\n }\n if (budget.remaining <= 0) {\n throw new DeskProtocolError(`Accessibility tree exceeds ${MAX_A11Y_NODES} nodes.`)\n }\n budget.remaining -= 1\n const record = asRecord(value, 'a11y node')\n const rawChildren = record.children ?? []\n if (!Array.isArray(rawChildren)) {\n throw new DeskProtocolError('a11y node children must be an array.')\n }\n return {\n id: requireString(record, 'id'),\n role: requireString(record, 'role'),\n name: nullableString(record, 'name'),\n actions: optionalStringArray(record, 'actions') ?? [],\n bounds: record.bounds === null || record.bounds === undefined\n ? null\n : parseBounds(record.bounds),\n children: rawChildren.map((child) => parseA11yNode(child, budget, depth + 1)),\n }\n}\n\nfunction parseBounds(value: unknown): { x: number; y: number; width: number; height: number } {\n const record = asRecord(value, 'bounds')\n return {\n x: requireDelta(record, 'x'),\n y: requireDelta(record, 'y'),\n width: requireNonNegativeInteger(record, 'width'),\n height: requireNonNegativeInteger(record, 'height'),\n }\n}\n\nfunction parsePoint(value: unknown): DeskPoint {\n const record = asRecord(value, 'point')\n return { x: requireCoordinate(record, 'x'), y: requireCoordinate(record, 'y') }\n}\n\nfunction parsePointerButton(value: unknown): DeskPointerButton {\n if (value === undefined) return 'left'\n if (value === 'left' || value === 'middle' || value === 'right') return value\n throw new DeskProtocolError('button must be left, middle, or right.')\n}\n\n/**\n * A frame format, when the field is present. Absent is `undefined` here and\n * every caller decides what that means; on a frame event it means `png`,\n * because that is what the field's absence used to imply.\n */\nfunction optionalFrameFormat(record: Record<string, unknown>): DeskFrameFormat | undefined {\n const value = record.format\n if (value === undefined) return undefined\n if (value === 'png' || value === 'jpeg') return value\n throw new DeskProtocolError('format must be png or jpeg.')\n}\n\nfunction parseVideoChunkKind(value: unknown): DeskVideoChunkKind {\n if (value === 'init' || value === 'media') return value\n throw new DeskProtocolError('video chunk kind must be init or media.')\n}\n\nfunction parseHandoverScope(value: unknown): DeskHandoverScope {\n if (value === 'view' || value === 'control') return value\n throw new DeskProtocolError('scope must be view or control.')\n}\n\nfunction asRecord(value: unknown, label: string): Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new DeskProtocolError(`Expected ${label} to be an object.`)\n }\n return value as Record<string, unknown>\n}\n\nfunction requireString(record: Record<string, unknown>, field: string): string {\n const value = record[field]\n if (typeof value !== 'string' || value.length === 0 || value.length > MAX_STRING_LENGTH) {\n throw new DeskProtocolError(\n `${field} must be a string of 1–${MAX_STRING_LENGTH} characters.`,\n )\n }\n return value\n}\n\nfunction requireUnboundedString(record: Record<string, unknown>, field: string): string {\n // Bounded by the frame limit rather than the field limit; used for payloads\n // such as command output, screenshots, and clipboard text.\n const value = record[field]\n if (typeof value !== 'string') {\n throw new DeskProtocolError(`${field} must be a string.`)\n }\n return value\n}\n\nfunction optionalString(record: Record<string, unknown>, field: string): string | undefined {\n return record[field] === undefined ? undefined : requireString(record, field)\n}\n\nfunction nullableString(record: Record<string, unknown>, field: string): string | null {\n const value = record[field]\n if (value === null || value === undefined) return null\n return requireString(record, field)\n}\n\nfunction optionalStringArray(\n record: Record<string, unknown>,\n field: string,\n): string[] | undefined {\n const value = record[field]\n if (value === undefined) return undefined\n if (!Array.isArray(value) || value.length > MAX_ARGUMENTS) {\n throw new DeskProtocolError(`${field} must be an array of at most ${MAX_ARGUMENTS} strings.`)\n }\n return value.map((entry, index) => {\n if (typeof entry !== 'string' || entry.length > MAX_STRING_LENGTH) {\n throw new DeskProtocolError(`${field}[${index}] must be a string of at most ${MAX_STRING_LENGTH} characters.`)\n }\n return entry\n })\n}\n\nfunction optionalEnvironment(\n record: Record<string, unknown>,\n): Record<string, string> | undefined {\n const value = record.env\n if (value === undefined) return undefined\n const env = asRecord(value, 'env')\n const entries = Object.entries(env)\n if (entries.length > MAX_ENVIRONMENT_ENTRIES) {\n throw new DeskProtocolError(`env must have at most ${MAX_ENVIRONMENT_ENTRIES} entries.`)\n }\n const clean: Record<string, string> = {}\n for (const [key, entry] of entries) {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {\n throw new DeskProtocolError(`Invalid environment variable name: ${key}`)\n }\n if (typeof entry !== 'string' || entry.length > MAX_STRING_LENGTH) {\n throw new DeskProtocolError(`env.${key} must be a string of at most ${MAX_STRING_LENGTH} characters.`)\n }\n clean[key] = entry\n }\n return clean\n}\n\nfunction requirePositiveInteger(record: Record<string, unknown>, field: string): number {\n const value = record[field]\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {\n throw new DeskProtocolError(`${field} must be a positive integer.`)\n }\n return value\n}\n\nfunction optionalPositiveInteger(\n record: Record<string, unknown>,\n field: string,\n): number | undefined {\n return record[field] === undefined ? undefined : requirePositiveInteger(record, field)\n}\n\nfunction requireNonNegativeInteger(record: Record<string, unknown>, field: string): number {\n const value = record[field]\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n throw new DeskProtocolError(`${field} must be a non-negative integer.`)\n }\n return value\n}\n\nfunction requireDimension(record: Record<string, unknown>, field: string): number {\n const value = requirePositiveInteger(record, field)\n if (value > 16_384) {\n throw new DeskProtocolError(`${field} must be at most 16384 pixels.`)\n }\n return value\n}\n\nfunction requireCoordinate(record: Record<string, unknown>, field: string): number {\n return requireNonNegativeInteger(record, field)\n}\n\nfunction requireDelta(record: Record<string, unknown>, field: string): number {\n const value = record[field]\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n throw new DeskProtocolError(`${field} must be an integer.`)\n }\n return value\n}\n\nfunction nullableInteger(record: Record<string, unknown>, field: string): number | null {\n const value = record[field]\n if (value === null || value === undefined) return null\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n throw new DeskProtocolError(`${field} must be an integer or null.`)\n }\n return value\n}\n"]}
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The framed JSON protocol spoken between the host and the in-guest agent\n * over vsock. Everything here is pure encode/decode/validate — no sockets, no\n * processes — so the security-critical parsing is unit-testable on any\n * platform, and both the host backend and the guest agent share one message\n * vocabulary.\n *\n * Wire format: a 4-byte big-endian length prefix followed by that many bytes\n * of UTF-8 JSON. Frame sizes are bounded; a peer declaring an oversized frame\n * is a protocol violation and the connection must be dropped, not resynced.\n */\nimport { Buffer } from 'node:buffer'\nimport type {\n A11yNode,\n DeskFrameFormat,\n DeskHandoverScope,\n DeskPoint,\n DeskPointerButton,\n DeskVideoChunkKind,\n WindowInfo,\n} from './events'\n\nexport const FRAME_HEADER_BYTES = 4\nexport const DEFAULT_MAX_FRAME_BYTES = 8 * 1024 * 1024\n/** The vsock port the in-guest agent listens on. */\nexport const GUEST_AGENT_VSOCK_PORT = 5252\n\nconst REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/\nconst MAX_STRING_LENGTH = 65_536\nconst MAX_ARGUMENTS = 256\nconst MAX_ENVIRONMENT_ENTRIES = 128\nconst MAX_WINDOWS = 256\nconst MAX_A11Y_NODES = 10_000\nconst MAX_A11Y_DEPTH = 32\n\nexport class DeskProtocolError extends Error {\n override readonly name = 'DeskProtocolError'\n}\n\nexport type GuestInput =\n | { type: 'move'; x: number; y: number }\n | { type: 'click'; x: number; y: number; button: DeskPointerButton; clicks: 1 | 2 }\n | { type: 'type'; text: string }\n | { type: 'key'; combo: string }\n | { type: 'scroll'; x: number; y: number; dx: number; dy: number }\n | { type: 'drag'; from: DeskPoint; to: DeskPoint }\n\n/** A request body, before the transport assigns its correlation id. */\nexport type GuestCommand =\n | { op: 'ping' }\n | { op: 'capabilities' }\n | {\n op: 'exec'\n command: string\n args?: readonly string[]\n cwd?: string\n env?: Readonly<Record<string, string>>\n timeoutMs?: number\n }\n | {\n op: 'job-start'\n command: string\n args?: readonly string[]\n cwd?: string\n env?: Readonly<Record<string, string>>\n }\n | { op: 'job-signal'; jobId: string; signal: 'SIGTERM' | 'SIGKILL' }\n | { op: 'screen-start'; width: number; height: number }\n | { op: 'screen-stop' }\n | { op: 'observe' }\n | { op: 'input'; input: GuestInput }\n | { op: 'a11y-invoke'; nodeId: string; action: string }\n | { op: 'launch'; appId: string; args?: readonly string[] }\n | { op: 'clipboard-read' }\n | { op: 'clipboard-write'; text: string }\n | {\n op: 'frames-start'\n fps: number\n width: number\n height: number\n /** Omitted lets the guest choose; a guest that predates this ignores it. */\n format?: DeskFrameFormat\n }\n | { op: 'frames-stop' }\n | { op: 'video-start'; fps: number; width: number; height: number }\n | { op: 'video-stop' }\n | { op: 'handover-begin'; ttlMs: number; scope: DeskHandoverScope }\n | { op: 'handover-end' }\n\nexport type GuestOp = GuestCommand['op']\n\nexport type GuestRequest = GuestCommand & { id: string }\n\nexport type GuestResponse =\n | { id: string; ok: true; result: unknown }\n | { id: string; ok: false; error: string }\n\n/** Unsolicited guest-to-host messages: job exits, frames, focus changes. */\nexport type GuestEventMessage =\n | { event: 'job-exit'; jobId: string; exitCode: number | null; signal: string | null }\n | {\n event: 'frame'\n seq: number\n width: number\n height: number\n data: string\n /**\n * How `data` is encoded. Absent on the wire means `png`: frames were\n * PNG-only before this field existed, so an older guest paired with a\n * newer host keeps working rather than having its every frame rejected.\n */\n format: DeskFrameFormat\n }\n | {\n event: 'video-chunk'\n seq: number\n kind: DeskVideoChunkKind\n /** RFC 6381, e.g. `avc1.42C020`, read out of the bytes by the guest. */\n codec: string\n width: number\n height: number\n /**\n * Whether a `media` chunk begins at a sync sample. Only a chunk that does\n * can start or resume a decoder; `init` chunks report false because the\n * question does not apply to them.\n */\n keyframe: boolean\n data: string\n }\n | { event: 'window-focus'; window: WindowInfo }\n\nexport type HostBoundMessage = GuestResponse | GuestEventMessage\n\n/** The wire shape of an exec result, before the host turns it into a snapshot. */\nexport interface GuestExecResult {\n exitCode: number | null\n signal: string | null\n stdout: string\n stderr: string\n truncated: boolean\n}\n\n/** The wire shape of an observation; `png` is base64. */\nexport interface GuestObservation {\n png: string\n width: number\n height: number\n a11y: A11yNode | null\n windows: WindowInfo[]\n focused: WindowInfo | null\n}\n\n/** Encode one message as a length-prefixed JSON frame. */\nexport function encodeFrame(message: unknown, maxFrameBytes = DEFAULT_MAX_FRAME_BYTES): Buffer {\n const body = Buffer.from(JSON.stringify(message), 'utf8')\n if (body.byteLength > maxFrameBytes) {\n throw new DeskProtocolError(\n `Refusing to encode a ${body.byteLength}-byte frame; the limit is ${maxFrameBytes}.`,\n )\n }\n const frame = Buffer.allocUnsafe(FRAME_HEADER_BYTES + body.byteLength)\n frame.writeUInt32BE(body.byteLength, 0)\n body.copy(frame, FRAME_HEADER_BYTES)\n return frame\n}\n\n/**\n * Incremental frame decoder. Feed it arbitrary chunk boundaries; it returns\n * every complete parsed JSON value. A declared frame length beyond the bound\n * or a body that is not valid JSON throws — framing violations are fatal to\n * the connection because there is no way to resynchronize a byte stream whose\n * lengths cannot be trusted.\n */\nexport class FrameDecoder {\n readonly #maxFrameBytes: number\n #buffered: Buffer = Buffer.alloc(0)\n\n constructor(options: { maxFrameBytes?: number } = {}) {\n const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES\n if (!Number.isInteger(maxFrameBytes) || maxFrameBytes < 1) {\n throw new DeskProtocolError('maxFrameBytes must be a positive integer.')\n }\n this.#maxFrameBytes = maxFrameBytes\n }\n\n push(chunk: Buffer): unknown[] {\n this.#buffered = this.#buffered.byteLength === 0\n ? chunk\n : Buffer.concat([this.#buffered, chunk])\n const messages: unknown[] = []\n while (this.#buffered.byteLength >= FRAME_HEADER_BYTES) {\n const declared = this.#buffered.readUInt32BE(0)\n if (declared > this.#maxFrameBytes) {\n throw new DeskProtocolError(\n `Peer declared a ${declared}-byte frame; the limit is ${this.#maxFrameBytes}.`,\n )\n }\n if (this.#buffered.byteLength < FRAME_HEADER_BYTES + declared) break\n const body = this.#buffered.subarray(FRAME_HEADER_BYTES, FRAME_HEADER_BYTES + declared)\n this.#buffered = this.#buffered.subarray(FRAME_HEADER_BYTES + declared)\n try {\n messages.push(JSON.parse(body.toString('utf8')))\n } catch (error) {\n throw new DeskProtocolError(\n `Frame body is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n return messages\n }\n}\n\n/** Validate a host-to-guest request. Unknown ops and malformed fields throw. */\nexport function parseGuestRequest(value: unknown): GuestRequest {\n const record = asRecord(value, 'request')\n const id = requireString(record, 'id')\n if (!REQUEST_ID_PATTERN.test(id)) {\n throw new DeskProtocolError('Request id must be 1–128 URL-safe characters.')\n }\n const op = requireString(record, 'op')\n switch (op) {\n case 'ping':\n case 'capabilities':\n case 'screen-stop':\n case 'observe':\n case 'clipboard-read':\n case 'frames-stop':\n case 'video-stop':\n case 'handover-end':\n return { id, op }\n case 'exec':\n return {\n id,\n op,\n command: requireString(record, 'command'),\n args: optionalStringArray(record, 'args'),\n cwd: optionalString(record, 'cwd'),\n env: optionalEnvironment(record),\n timeoutMs: optionalPositiveInteger(record, 'timeoutMs'),\n }\n case 'job-start':\n return {\n id,\n op,\n command: requireString(record, 'command'),\n args: optionalStringArray(record, 'args'),\n cwd: optionalString(record, 'cwd'),\n env: optionalEnvironment(record),\n }\n case 'job-signal': {\n const signal = requireString(record, 'signal')\n if (signal !== 'SIGTERM' && signal !== 'SIGKILL') {\n throw new DeskProtocolError('job-signal signal must be SIGTERM or SIGKILL.')\n }\n return { id, op, jobId: requireString(record, 'jobId'), signal }\n }\n case 'screen-start':\n return {\n id,\n op,\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n }\n case 'input':\n return { id, op, input: parseGuestInput(record.input) }\n case 'a11y-invoke':\n return {\n id,\n op,\n nodeId: requireString(record, 'nodeId'),\n action: requireString(record, 'action'),\n }\n case 'launch':\n return {\n id,\n op,\n appId: requireString(record, 'appId'),\n args: optionalStringArray(record, 'args'),\n }\n case 'clipboard-write':\n return { id, op, text: requireString(record, 'text') }\n case 'frames-start':\n return {\n id,\n op,\n fps: requirePositiveInteger(record, 'fps'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n format: optionalFrameFormat(record),\n }\n case 'video-start':\n return {\n id,\n op,\n fps: requirePositiveInteger(record, 'fps'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n }\n case 'handover-begin':\n return {\n id,\n op,\n ttlMs: requirePositiveInteger(record, 'ttlMs'),\n scope: parseHandoverScope(record.scope),\n }\n default:\n throw new DeskProtocolError(`Unknown request op: ${op}`)\n }\n}\n\nexport function parseGuestInput(value: unknown): GuestInput {\n const record = asRecord(value, 'input')\n const type = requireString(record, 'type')\n switch (type) {\n case 'move':\n return { type, x: requireCoordinate(record, 'x'), y: requireCoordinate(record, 'y') }\n case 'click':\n return {\n type,\n x: requireCoordinate(record, 'x'),\n y: requireCoordinate(record, 'y'),\n button: parsePointerButton(record.button),\n clicks: parseClickCount(record.clicks),\n }\n case 'type':\n return { type, text: requireString(record, 'text') }\n case 'key':\n return { type, combo: requireString(record, 'combo') }\n case 'scroll':\n return {\n type,\n x: requireCoordinate(record, 'x'),\n y: requireCoordinate(record, 'y'),\n dx: requireDelta(record, 'dx'),\n dy: requireDelta(record, 'dy'),\n }\n case 'drag':\n return { type, from: parsePoint(record.from), to: parsePoint(record.to) }\n default:\n throw new DeskProtocolError(`Unknown input type: ${type}`)\n }\n}\n\nfunction parseClickCount(value: unknown): 1 | 2 {\n if (value === undefined || value === 1) return 1\n if (value === 2) return 2\n throw new DeskProtocolError('clicks must be 1 or 2.')\n}\n\n/** Validate a guest-to-host message: a correlated response or an event. */\nexport function parseHostBoundMessage(value: unknown): HostBoundMessage {\n const record = asRecord(value, 'message')\n if ('id' in record) {\n const id = requireString(record, 'id')\n if (record.ok === true) return { id, ok: true, result: record.result ?? {} }\n if (record.ok === false) return { id, ok: false, error: requireString(record, 'error') }\n throw new DeskProtocolError('Response ok must be a boolean.')\n }\n const event = requireString(record, 'event')\n switch (event) {\n case 'job-exit':\n return {\n event,\n jobId: requireString(record, 'jobId'),\n exitCode: nullableInteger(record, 'exitCode'),\n signal: nullableString(record, 'signal'),\n }\n case 'frame':\n return {\n event,\n seq: requireNonNegativeInteger(record, 'seq'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n data: requireUnboundedString(record, 'data'),\n // Absent means png — see the field's contract on GuestEventMessage.\n format: optionalFrameFormat(record) ?? 'png',\n }\n case 'video-chunk':\n return {\n event,\n seq: requireNonNegativeInteger(record, 'seq'),\n kind: parseVideoChunkKind(record.kind),\n codec: requireString(record, 'codec'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n keyframe: record.keyframe === true,\n data: requireUnboundedString(record, 'data'),\n }\n case 'window-focus':\n return { event, window: parseWindowInfo(record.window) }\n default:\n throw new DeskProtocolError(`Unknown event: ${event}`)\n }\n}\n\n/** Validate the result payload of an `exec` response. */\nexport function parseExecResult(value: unknown): GuestExecResult {\n const record = asRecord(value, 'exec result')\n return {\n exitCode: nullableInteger(record, 'exitCode'),\n signal: nullableString(record, 'signal'),\n stdout: requireUnboundedString(record, 'stdout'),\n stderr: requireUnboundedString(record, 'stderr'),\n truncated: record.truncated === true,\n }\n}\n\n/** Validate the result payload of a `job-start` response. */\nexport function parseJobStarted(value: unknown): { jobId: string } {\n const record = asRecord(value, 'job-start result')\n return { jobId: requireString(record, 'jobId') }\n}\n\n/** Validate the result payload of an `observe` response. */\nexport function parseObservation(value: unknown): GuestObservation {\n const record = asRecord(value, 'observation')\n const windows = parseWindows(record.windows)\n return {\n png: requireUnboundedString(record, 'png'),\n width: requireDimension(record, 'width'),\n height: requireDimension(record, 'height'),\n a11y: record.a11y === null || record.a11y === undefined\n ? null\n : parseA11yTree(record.a11y),\n windows,\n focused: record.focused === null || record.focused === undefined\n ? null\n : parseWindowInfo(record.focused),\n }\n}\n\n/** Validate the result payload of a `handover-begin` response. */\nexport function parseHandoverBegun(value: unknown): { url: string } {\n const record = asRecord(value, 'handover-begin result')\n return { url: requireString(record, 'url') }\n}\n\n/** Validate the result payload of a `clipboard-read` response. */\nexport function parseClipboardText(value: unknown): string {\n const record = asRecord(value, 'clipboard-read result')\n return requireUnboundedString(record, 'text')\n}\n\n/** Validate the result payload of a `capabilities` response. */\nexport function parseCapabilities(value: unknown): { virtioGpu: boolean } {\n const record = asRecord(value, 'capabilities result')\n return { virtioGpu: record.virtioGpu === true }\n}\n\nexport function parseWindowInfo(value: unknown): WindowInfo {\n const record = asRecord(value, 'window')\n return {\n id: requireString(record, 'id'),\n title: requireString(record, 'title'),\n appId: nullableString(record, 'appId'),\n }\n}\n\nfunction parseWindows(value: unknown): WindowInfo[] {\n if (value === undefined || value === null) return []\n if (!Array.isArray(value) || value.length > MAX_WINDOWS) {\n throw new DeskProtocolError(`windows must be an array of at most ${MAX_WINDOWS} entries.`)\n }\n return value.map(parseWindowInfo)\n}\n\nfunction parseA11yTree(value: unknown): A11yNode {\n const budget = { remaining: MAX_A11Y_NODES }\n return parseA11yNode(value, budget, 0)\n}\n\nfunction parseA11yNode(value: unknown, budget: { remaining: number }, depth: number): A11yNode {\n if (depth > MAX_A11Y_DEPTH) {\n throw new DeskProtocolError(`Accessibility tree exceeds depth ${MAX_A11Y_DEPTH}.`)\n }\n if (budget.remaining <= 0) {\n throw new DeskProtocolError(`Accessibility tree exceeds ${MAX_A11Y_NODES} nodes.`)\n }\n budget.remaining -= 1\n const record = asRecord(value, 'a11y node')\n const rawChildren = record.children ?? []\n if (!Array.isArray(rawChildren)) {\n throw new DeskProtocolError('a11y node children must be an array.')\n }\n return {\n id: requireString(record, 'id'),\n role: requireString(record, 'role'),\n name: nullableString(record, 'name'),\n actions: optionalStringArray(record, 'actions') ?? [],\n bounds: record.bounds === null || record.bounds === undefined\n ? null\n : parseBounds(record.bounds),\n children: rawChildren.map((child) => parseA11yNode(child, budget, depth + 1)),\n }\n}\n\nfunction parseBounds(value: unknown): { x: number; y: number; width: number; height: number } {\n const record = asRecord(value, 'bounds')\n return {\n x: requireDelta(record, 'x'),\n y: requireDelta(record, 'y'),\n width: requireNonNegativeInteger(record, 'width'),\n height: requireNonNegativeInteger(record, 'height'),\n }\n}\n\nfunction parsePoint(value: unknown): DeskPoint {\n const record = asRecord(value, 'point')\n return { x: requireCoordinate(record, 'x'), y: requireCoordinate(record, 'y') }\n}\n\nfunction parsePointerButton(value: unknown): DeskPointerButton {\n if (value === undefined) return 'left'\n if (value === 'left' || value === 'middle' || value === 'right') return value\n throw new DeskProtocolError('button must be left, middle, or right.')\n}\n\n/**\n * A frame format, when the field is present. Absent is `undefined` here and\n * every caller decides what that means; on a frame event it means `png`,\n * because that is what the field's absence used to imply.\n */\nfunction optionalFrameFormat(record: Record<string, unknown>): DeskFrameFormat | undefined {\n const value = record.format\n if (value === undefined) return undefined\n if (value === 'png' || value === 'jpeg') return value\n throw new DeskProtocolError('format must be png or jpeg.')\n}\n\nfunction parseVideoChunkKind(value: unknown): DeskVideoChunkKind {\n if (value === 'init' || value === 'media') return value\n throw new DeskProtocolError('video chunk kind must be init or media.')\n}\n\nfunction parseHandoverScope(value: unknown): DeskHandoverScope {\n if (value === 'view' || value === 'control') return value\n throw new DeskProtocolError('scope must be view or control.')\n}\n\nfunction asRecord(value: unknown, label: string): Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new DeskProtocolError(`Expected ${label} to be an object.`)\n }\n return value as Record<string, unknown>\n}\n\nfunction requireString(record: Record<string, unknown>, field: string): string {\n const value = record[field]\n if (typeof value !== 'string' || value.length === 0 || value.length > MAX_STRING_LENGTH) {\n throw new DeskProtocolError(\n `${field} must be a string of 1–${MAX_STRING_LENGTH} characters.`,\n )\n }\n return value\n}\n\nfunction requireUnboundedString(record: Record<string, unknown>, field: string): string {\n // Bounded by the frame limit rather than the field limit; used for payloads\n // such as command output, screenshots, and clipboard text.\n const value = record[field]\n if (typeof value !== 'string') {\n throw new DeskProtocolError(`${field} must be a string.`)\n }\n return value\n}\n\nfunction optionalString(record: Record<string, unknown>, field: string): string | undefined {\n return record[field] === undefined ? undefined : requireString(record, field)\n}\n\nfunction nullableString(record: Record<string, unknown>, field: string): string | null {\n const value = record[field]\n if (value === null || value === undefined) return null\n return requireString(record, field)\n}\n\nfunction optionalStringArray(\n record: Record<string, unknown>,\n field: string,\n): string[] | undefined {\n const value = record[field]\n if (value === undefined) return undefined\n if (!Array.isArray(value) || value.length > MAX_ARGUMENTS) {\n throw new DeskProtocolError(`${field} must be an array of at most ${MAX_ARGUMENTS} strings.`)\n }\n return value.map((entry, index) => {\n if (typeof entry !== 'string' || entry.length > MAX_STRING_LENGTH) {\n throw new DeskProtocolError(`${field}[${index}] must be a string of at most ${MAX_STRING_LENGTH} characters.`)\n }\n return entry\n })\n}\n\nfunction optionalEnvironment(\n record: Record<string, unknown>,\n): Record<string, string> | undefined {\n const value = record.env\n if (value === undefined) return undefined\n const env = asRecord(value, 'env')\n const entries = Object.entries(env)\n if (entries.length > MAX_ENVIRONMENT_ENTRIES) {\n throw new DeskProtocolError(`env must have at most ${MAX_ENVIRONMENT_ENTRIES} entries.`)\n }\n const clean: Record<string, string> = {}\n for (const [key, entry] of entries) {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {\n throw new DeskProtocolError(`Invalid environment variable name: ${key}`)\n }\n if (typeof entry !== 'string' || entry.length > MAX_STRING_LENGTH) {\n throw new DeskProtocolError(`env.${key} must be a string of at most ${MAX_STRING_LENGTH} characters.`)\n }\n clean[key] = entry\n }\n return clean\n}\n\nfunction requirePositiveInteger(record: Record<string, unknown>, field: string): number {\n const value = record[field]\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {\n throw new DeskProtocolError(`${field} must be a positive integer.`)\n }\n return value\n}\n\nfunction optionalPositiveInteger(\n record: Record<string, unknown>,\n field: string,\n): number | undefined {\n return record[field] === undefined ? undefined : requirePositiveInteger(record, field)\n}\n\nfunction requireNonNegativeInteger(record: Record<string, unknown>, field: string): number {\n const value = record[field]\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n throw new DeskProtocolError(`${field} must be a non-negative integer.`)\n }\n return value\n}\n\nfunction requireDimension(record: Record<string, unknown>, field: string): number {\n const value = requirePositiveInteger(record, field)\n if (value > 16_384) {\n throw new DeskProtocolError(`${field} must be at most 16384 pixels.`)\n }\n return value\n}\n\nfunction requireCoordinate(record: Record<string, unknown>, field: string): number {\n return requireNonNegativeInteger(record, field)\n}\n\nfunction requireDelta(record: Record<string, unknown>, field: string): number {\n const value = record[field]\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n throw new DeskProtocolError(`${field} must be an integer.`)\n }\n return value\n}\n\nfunction nullableInteger(record: Record<string, unknown>, field: string): number | null {\n const value = record[field]\n if (value === null || value === undefined) return null\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n throw new DeskProtocolError(`${field} must be an integer or null.`)\n }\n return value\n}\n"]}
|