232 lines
7.0 KiB
JavaScript
232 lines
7.0 KiB
JavaScript
import { io } from 'socket.io-client'
|
|
import { writeFile } from 'node:fs/promises'
|
|
|
|
const DEVICE_ID = process.env.RC_DEVICE_ID || process.argv[2]
|
|
const SERVER_HTTP = process.env.RC_SERVER_HTTP || 'http://127.0.0.1:3001'
|
|
const SOCKET_URL = process.env.RC_SOCKET_URL || SERVER_HTTP
|
|
const USERNAME = process.env.RC_USER || 'superadmin'
|
|
const PASSWORD = process.env.RC_PASS || 'superadmin123456789'
|
|
const RUNS = Number(process.env.RC_APP_LIST_RUNS || 4)
|
|
const OPEN_BATTERY_SETTINGS = String(process.env.RC_OPEN_BATTERY_SETTINGS || '0') === '1'
|
|
const REPORT_PATH = process.env.RC_REPORT_PATH || './_tmp_keepalive_app_list_probe.json'
|
|
|
|
if (!DEVICE_ID) {
|
|
console.error('usage: node scripts/keepalive_app_list_probe.mjs <deviceId>')
|
|
process.exit(1)
|
|
}
|
|
|
|
const nowIso = () => new Date().toISOString()
|
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
|
|
const report = {
|
|
startedAt: nowIso(),
|
|
serverHttp: SERVER_HTTP,
|
|
socketUrl: SOCKET_URL,
|
|
deviceId: DEVICE_ID,
|
|
runs: [],
|
|
keepalive: {},
|
|
openBatterySettings: null,
|
|
events: [],
|
|
summary: { appListOk: 0, appListFail: 0 }
|
|
}
|
|
|
|
function trimPreview(value, maxLen = 360) {
|
|
try {
|
|
const text = JSON.stringify(value)
|
|
if (!text) return ''
|
|
return text.length > maxLen ? `${text.slice(0, maxLen)}...<trimmed>` : text
|
|
} catch {
|
|
return String(value)
|
|
}
|
|
}
|
|
|
|
function pushEvent(event, payload) {
|
|
report.events.push({ at: nowIso(), event, payload: trimPreview(payload) })
|
|
if (report.events.length > 600) report.events.shift()
|
|
}
|
|
|
|
function waitEvent(socket, event, predicate, timeoutMs) {
|
|
return new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => {
|
|
cleanup()
|
|
reject(new Error(`timeout_waiting_${event}_${timeoutMs}ms`))
|
|
}, timeoutMs)
|
|
|
|
const handler = (...args) => {
|
|
try {
|
|
if (!predicate(...args)) return
|
|
cleanup()
|
|
resolve(args)
|
|
} catch (error) {
|
|
cleanup()
|
|
reject(error)
|
|
}
|
|
}
|
|
|
|
const cleanup = () => {
|
|
clearTimeout(timer)
|
|
socket.off(event, handler)
|
|
}
|
|
|
|
socket.on(event, handler)
|
|
})
|
|
}
|
|
|
|
async function loginToken() {
|
|
const resp = await fetch(`${SERVER_HTTP}/api/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: USERNAME, password: PASSWORD })
|
|
})
|
|
const data = await resp.json().catch(() => ({}))
|
|
if (!resp.ok || !data?.success || !data?.token) {
|
|
throw new Error(`login_failed_${resp.status}_${trimPreview(data, 800)}`)
|
|
}
|
|
return data.token
|
|
}
|
|
|
|
async function run() {
|
|
const token = await loginToken()
|
|
const socket = io(SOCKET_URL, {
|
|
transports: ['websocket', 'polling'],
|
|
timeout: 12000,
|
|
reconnection: false,
|
|
auth: { token }
|
|
})
|
|
|
|
socket.onAny((event, ...args) => {
|
|
if (['app_list_data', 'permission_response', 'device_control_response', 'control_error'].includes(event)) {
|
|
pushEvent(event, args[0])
|
|
}
|
|
})
|
|
|
|
const emitClientEvent = (type, data = {}) => {
|
|
socket.emit('client_event', { type, data: { ...data, deviceId: DEVICE_ID }, timestamp: Date.now() })
|
|
}
|
|
|
|
const emitControl = (type, data = {}) => {
|
|
socket.emit('control_message', { type, deviceId: DEVICE_ID, data, timestamp: Date.now() })
|
|
}
|
|
|
|
const emitCamera = (action, data = {}) => {
|
|
socket.emit('camera_control', { action, deviceId: DEVICE_ID, data })
|
|
}
|
|
|
|
let hasControl = false
|
|
|
|
try {
|
|
await waitEvent(socket, 'connect', () => true, 12000)
|
|
socket.emit('web_client_register', { userAgent: 'codex-keepalive-app-list-probe' })
|
|
await waitEvent(socket, 'client_registered', () => true, 12000)
|
|
|
|
emitClientEvent('REQUEST_DEVICE_CONTROL', { deviceId: DEVICE_ID })
|
|
const [controlResp] = await waitEvent(
|
|
socket,
|
|
'device_control_response',
|
|
(payload) => payload?.deviceId === DEVICE_ID,
|
|
12000
|
|
)
|
|
if (!controlResp?.success) {
|
|
throw new Error(controlResp?.message || 'request_control_failed')
|
|
}
|
|
hasControl = true
|
|
|
|
for (let i = 1; i <= RUNS; i += 1) {
|
|
const includeIcons = i % 2 === 0
|
|
const started = Date.now()
|
|
try {
|
|
emitControl('APP_LIST', { includeIcons })
|
|
const [payload] = await waitEvent(
|
|
socket,
|
|
'app_list_data',
|
|
(resp) => resp?.deviceId === DEVICE_ID && Array.isArray(resp?.appList),
|
|
includeIcons ? 35000 : 18000
|
|
)
|
|
const count = Array.isArray(payload?.appList) ? payload.appList.length : 0
|
|
report.runs.push({ idx: i, includeIcons, ok: true, count, durationMs: Date.now() - started })
|
|
report.summary.appListOk += 1
|
|
} catch (error) {
|
|
report.runs.push({
|
|
idx: i,
|
|
includeIcons,
|
|
ok: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
durationMs: Date.now() - started
|
|
})
|
|
report.summary.appListFail += 1
|
|
}
|
|
await sleep(includeIcons ? 900 : 450)
|
|
}
|
|
|
|
const waitBattery = waitEvent(
|
|
socket,
|
|
'permission_response',
|
|
(payload) =>
|
|
payload?.deviceId === DEVICE_ID &&
|
|
String(payload?.permissionType || '').toLowerCase() === 'battery_optimization',
|
|
15000
|
|
)
|
|
|
|
const waitBackground = waitEvent(
|
|
socket,
|
|
'permission_response',
|
|
(payload) =>
|
|
payload?.deviceId === DEVICE_ID &&
|
|
String(payload?.permissionType || '').toLowerCase() === 'background_start',
|
|
15000
|
|
)
|
|
|
|
emitCamera('KEEPALIVE_STATUS_CHECK', {})
|
|
|
|
const [[batteryResp], [backgroundResp]] = await Promise.all([waitBattery, waitBackground])
|
|
|
|
report.keepalive = {
|
|
batteryOptimization: {
|
|
success: !!batteryResp?.success,
|
|
message: batteryResp?.message || ''
|
|
},
|
|
backgroundStart: {
|
|
success: !!backgroundResp?.success,
|
|
message: backgroundResp?.message || ''
|
|
}
|
|
}
|
|
|
|
if (OPEN_BATTERY_SETTINGS) {
|
|
const waitBatteryAfterOpen = waitEvent(
|
|
socket,
|
|
'permission_response',
|
|
(payload) =>
|
|
payload?.deviceId === DEVICE_ID &&
|
|
String(payload?.permissionType || '').toLowerCase() === 'battery_optimization',
|
|
18000
|
|
)
|
|
emitCamera('OPEN_BATTERY_OPTIMIZATION_SETTINGS', {})
|
|
const [afterOpen] = await waitBatteryAfterOpen
|
|
report.openBatterySettings = {
|
|
success: !!afterOpen?.success,
|
|
message: afterOpen?.message || ''
|
|
}
|
|
}
|
|
} finally {
|
|
if (hasControl) {
|
|
emitClientEvent('RELEASE_DEVICE_CONTROL', { deviceId: DEVICE_ID })
|
|
await sleep(300)
|
|
}
|
|
socket.close()
|
|
}
|
|
|
|
report.finishedAt = nowIso()
|
|
await writeFile(REPORT_PATH, JSON.stringify(report, null, 2), 'utf8')
|
|
console.log(`report=${REPORT_PATH}`)
|
|
console.log(`app_list_ok=${report.summary.appListOk} app_list_fail=${report.summary.appListFail}`)
|
|
console.log(`keepalive_battery=${report.keepalive?.batteryOptimization?.success} keepalive_bg=${report.keepalive?.backgroundStart?.success}`)
|
|
}
|
|
|
|
run().catch(async (error) => {
|
|
report.finishedAt = nowIso()
|
|
report.fatal = error instanceof Error ? error.message : String(error)
|
|
await writeFile(REPORT_PATH, JSON.stringify(report, null, 2), 'utf8')
|
|
console.error(report.fatal)
|
|
process.exit(1)
|
|
})
|