Files
2026-08-26 20:13:10 +08:00

190 lines
4.6 KiB
JavaScript

import { env, exit, stdin, stdout } from 'node:process'
import { createInterface } from 'node:readline'
// TODO(@nekomeowww): try now to directly embed binary / base64, even tests. `xz` warned us.
const tinyPngBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wn8vO0AAAAASUVORK5CYII='
const state = {
sessionTag: env.FAKE_RUNNER_SESSION_TAG || 'vm-local-1',
displayId: env.FAKE_RUNNER_DISPLAY_ID || ':99',
hostName: env.FAKE_RUNNER_HOST_NAME || 'fake-remote',
remoteUser: env.FAKE_RUNNER_REMOTE_USER || 'airi',
width: Number.parseInt(env.FAKE_RUNNER_WIDTH || '1280', 10),
height: Number.parseInt(env.FAKE_RUNNER_HEIGHT || '720', 10),
observationBaseUrl: env.FAKE_RUNNER_OBSERVATION_BASE_URL || '',
}
function executionTarget() {
return {
mode: 'remote',
transport: 'ssh-stdio',
hostName: state.hostName,
remoteUser: state.remoteUser,
displayId: state.displayId,
sessionTag: state.sessionTag,
isolated: true,
tainted: false,
}
}
function permissionInfo() {
return {
screenRecording: {
status: 'granted',
target: `${state.displayId} via scrot`,
checkedBy: 'scrot',
},
accessibility: {
status: 'unsupported',
target: `${state.displayId} linux-x11 session`,
note: 'linux-x11 runner does not rely on accessibility APIs',
},
automationToSystemEvents: {
status: 'unsupported',
target: `${state.displayId} linux-x11 session`,
note: 'linux-x11 runner does not use System Events',
},
}
}
function displayInfo() {
return {
available: true,
platform: 'linux',
logicalWidth: state.width,
logicalHeight: state.height,
pixelWidth: state.width,
pixelHeight: state.height,
scaleFactor: 1,
isRetina: false,
note: `managed virtual X session ${state.displayId}`,
}
}
function writeResponse(response) {
stdout.write(`${JSON.stringify(response)}\n`)
}
const rl = createInterface({
input: stdin,
crlfDelay: Infinity,
})
rl.on('line', (line) => {
const trimmed = line.trim()
if (!trimmed) {
return
}
const request = JSON.parse(trimmed)
if (env.FAKE_RUNNER_CLOSE_ON_MUTATION === '1' && ['click', 'typeText', 'pressKeys', 'scroll'].includes(request.method)) {
exit(1)
}
switch (request.method) {
case 'initialize':
writeResponse({
id: request.id,
ok: true,
result: {
executionTarget: executionTarget(),
displayInfo: displayInfo(),
permissionInfo: permissionInfo(),
},
})
return
case 'getExecutionTarget':
writeResponse({
id: request.id,
ok: true,
result: executionTarget(),
})
return
case 'getDisplayInfo':
writeResponse({
id: request.id,
ok: true,
result: displayInfo(),
})
return
case 'getForegroundContext':
writeResponse({
id: request.id,
ok: true,
result: {
available: true,
appName: 'mousepad',
windowTitle: 'Mousepad',
platform: 'linux',
},
})
return
case 'getPermissionInfo':
writeResponse({
id: request.id,
ok: true,
result: permissionInfo(),
})
return
case 'takeScreenshot':
writeResponse({
id: request.id,
ok: true,
result: {
dataBase64: tinyPngBase64,
mimeType: 'image/png',
...(state.observationBaseUrl
? {
publicUrl: `${state.observationBaseUrl.replace(/\/$/, '')}/fake-screenshot.png`,
}
: {}),
width: state.width,
height: state.height,
executionTarget: executionTarget(),
},
})
return
case 'click':
case 'typeText':
case 'pressKeys':
case 'scroll':
case 'wait':
writeResponse({
id: request.id,
ok: true,
result: {
performed: true,
backend: 'linux-x11',
notes: [`${request.method} executed`],
executionTarget: executionTarget(),
},
})
return
case 'openTestTarget':
writeResponse({
id: request.id,
ok: true,
result: {
launched: true,
appName: 'mousepad',
windowTitle: 'Mousepad',
recommendedClickPoint: {
x: 180,
y: 150,
},
executionTarget: executionTarget(),
},
})
return
case 'shutdown':
writeResponse({
id: request.id,
ok: true,
result: {
ok: true,
},
})
exit(0)
}
})