@xenon-device-management/xenon 1.9.1 → 1.10.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/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenon-device-management/xenon",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "Xenon - Intelligent Mobile Infrastructure. A self-healing device orchestration platform for Appium.",
5
5
  "main": "./lib/src/index.js",
6
6
  "exports": {
@@ -12,7 +12,7 @@
12
12
  "postinstall": "node scripts/generate-prisma.js",
13
13
  "build": "node scripts/generate-prisma.js && tsc -b && npm run build:copy",
14
14
  "build:all": "npm run build:xenon && npm run build",
15
- "build:copy": "rm -rf lib/public && mkdir -p lib/src && cp -R src/public lib/ && cp -R src/generated lib/src/ && cp schema.json lib/",
15
+ "build:copy": "rm -rf lib/public && mkdir -p lib/src && cp -R src/public lib/ && cp -R src/generated lib/src/ && cp schema.json lib/ && mkdir -p lib/src/device-managers/android/vendor && cp src/device-managers/android/vendor/*.jar lib/src/device-managers/android/vendor/",
16
16
  "build:schema": "node scripts/generate-types-from-schema.js",
17
17
  "build:xenon": "sh scripts/build-xenon.sh",
18
18
  "dev": "npm run db:migrate && npm run build && npm run plugin:reinstall && npm run server",
package/lib/schema.json CHANGED
@@ -475,9 +475,16 @@
475
475
  "description": "Live-streaming options.",
476
476
  "properties": {
477
477
  "androidH264": {
478
- "type": "boolean",
479
478
  "default": false,
480
- "description": "Use scrcpy H.264 for Android live preview (WebCodecs in the browser) instead of the MJPEG screencap loop. Falls back to MJPEG when unsupported."
479
+ "description": "Android live preview: false/omitted = MJPEG; true = H.264 via scrcpy; { \"source\": \"scrcpy\" | \"screenrecord\" } to pick the capture source explicitly. Falls back to MJPEG when unsupported.",
480
+ "oneOf": [
481
+ { "type": "boolean" },
482
+ {
483
+ "type": "object",
484
+ "additionalProperties": false,
485
+ "properties": { "source": { "type": "string", "enum": ["scrcpy", "screenrecord"] } }
486
+ }
487
+ ]
481
488
  }
482
489
  }
483
490
  }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAndroidH264 = resolveAndroidH264;
4
+ /**
5
+ * Normalize the three accepted flag shapes to one:
6
+ * false | undefined → { enabled:false } (pure MJPEG — default)
7
+ * true → { enabled:true, scrcpy } (new default source)
8
+ * { source } → { enabled:true, source } (explicit; scrcpy default)
9
+ */
10
+ function resolveAndroidH264(cfg) {
11
+ if (!cfg)
12
+ return { enabled: false, source: 'scrcpy' };
13
+ if (cfg === true)
14
+ return { enabled: true, source: 'scrcpy' };
15
+ return { enabled: true, source: cfg.source === 'screenrecord' ? 'screenrecord' : 'scrcpy' };
16
+ }
@@ -64,6 +64,7 @@ const InspectorService_1 = require("../../services/InspectorService");
64
64
  const StreamTicketService_1 = require("../../services/token/StreamTicketService");
65
65
  const PluginContext_1 = require("../../PluginContext");
66
66
  const streamType_1 = require("./streamType");
67
+ const androidH264Config_1 = require("./androidH264Config");
67
68
  const recording_store_1 = require("../../services/recording/recording-store");
68
69
  const scopeGuard_1 = require("../../middleware/scopeGuard");
69
70
  const roleGuard_1 = require("../../middleware/roleGuard");
@@ -562,7 +563,8 @@ router.post('/:udid/stream/start', (req, res) => __awaiter(void 0, void 0, void
562
563
  });
563
564
  }
564
565
  try {
565
- const flagOn = !!((_a = typedi_1.Container.get(PluginContext_1.PluginContext).pluginArgs.streaming) === null || _a === void 0 ? void 0 : _a.androidH264);
566
+ const h264Cfg = (0, androidH264Config_1.resolveAndroidH264)((_a = typedi_1.Container.get(PluginContext_1.PluginContext).pluginArgs.streaming) === null || _a === void 0 ? void 0 : _a.androidH264);
567
+ const flagOn = h264Cfg.enabled;
566
568
  // A recording device keeps MJPEG preview so we never run screenrecord and
567
569
  // the recording's screencap pipeline against the same device at once.
568
570
  const recording = yield typedi_1.Container.get(recording_store_1.RecordingStore).isRecording(udid);
@@ -574,7 +576,7 @@ router.post('/:udid/stream/start', (req, res) => __awaiter(void 0, void 0, void
574
576
  else if (streamType === 'h264') {
575
577
  // H.264 preview: start the scrcpy/screenrecord service. Do NOT also start
576
578
  // the MJPEG screencap loop — one capture pipeline per device.
577
- yield typedi_1.Container.get(AndroidH264StreamService_1.default).start(udid);
579
+ yield typedi_1.Container.get(AndroidH264StreamService_1.default).start(udid, { source: h264Cfg.source });
578
580
  }
579
581
  else {
580
582
  mjpegPort = (yield typedi_1.Container.get(AndroidStreamService_1.default).startStream(udid)).mjpegPort;
@@ -708,7 +710,7 @@ router.get('/:udid/stream/status', (req, res) => __awaiter(void 0, void 0, void
708
710
  return res.status(404).send('Device not found');
709
711
  // Advertise which transport the frontend player should use. A recording
710
712
  // device keeps MJPEG (one capture pipeline per device).
711
- const flagOn = !!((_a = typedi_1.Container.get(PluginContext_1.PluginContext).pluginArgs.streaming) === null || _a === void 0 ? void 0 : _a.androidH264);
713
+ const flagOn = (0, androidH264Config_1.resolveAndroidH264)((_a = typedi_1.Container.get(PluginContext_1.PluginContext).pluginArgs.streaming) === null || _a === void 0 ? void 0 : _a.androidH264).enabled;
712
714
  const recording = yield typedi_1.Container.get(recording_store_1.RecordingStore).isRecording(udid);
713
715
  const type = (0, streamType_1.resolveStreamType)(device.platform, flagOn, recording);
714
716
  const h264Path = type === 'h264' ? `/xenon/api/control/${encodeURIComponent(udid)}/stream/h264` : undefined;
@@ -60,6 +60,8 @@ const logger_1 = __importDefault(require("../../logger"));
60
60
  const device_store_1 = require("../../data-service/device-store");
61
61
  const H264Multiplexer_1 = require("./H264Multiplexer");
62
62
  const h264NalParser_1 = require("./h264NalParser");
63
+ const androidH264Config_1 = require("../../app/routers/androidH264Config");
64
+ const PluginContext_1 = require("../../PluginContext");
63
65
  const IDLE_TIMEOUT_MS = 600000; // stop a stream after 10 min with zero viewers
64
66
  const RESTART_MIN_INTERVAL_MS = 300; // fast restart after a healthy stream hits the cap/rotation
65
67
  // After this many consecutive restarts that produced no frames (device gone,
@@ -106,7 +108,7 @@ let AndroidH264StreamService = class AndroidH264StreamService {
106
108
  var _a;
107
109
  return (_a = this.sessions.get(udid)) === null || _a === void 0 ? void 0 : _a.mux;
108
110
  }
109
- start(udid) {
111
+ start(udid, opts) {
110
112
  return __awaiter(this, void 0, void 0, function* () {
111
113
  const inflight = this.startPromises.get(udid);
112
114
  if (inflight)
@@ -115,6 +117,7 @@ let AndroidH264StreamService = class AndroidH264StreamService {
115
117
  if (existing && existing.status === 'running')
116
118
  return existing.mux;
117
119
  const promise = (() => __awaiter(this, void 0, void 0, function* () {
120
+ var _a, _b;
118
121
  const mux = new H264Multiplexer_1.H264Multiplexer();
119
122
  const session = { status: 'running', mux };
120
123
  this.sessions.set(udid, session);
@@ -129,7 +132,13 @@ let AndroidH264StreamService = class AndroidH264StreamService {
129
132
  resolveConfig();
130
133
  }
131
134
  };
132
- session.capture = yield this.openCapture(udid, onPacket);
135
+ // Resolve the capture source: an explicit opts.source wins; otherwise
136
+ // read the server-wide flag config. Resolving here (not just at the REST
137
+ // call site) means the WS auto-start path — start(udid) with no opts —
138
+ // also honours a configured { source: 'screenrecord' } instead of
139
+ // silently defaulting to scrcpy.
140
+ const source = (_a = opts === null || opts === void 0 ? void 0 : opts.source) !== null && _a !== void 0 ? _a : (0, androidH264Config_1.resolveAndroidH264)((_b = typedi_1.Container.get(PluginContext_1.PluginContext).pluginArgs.streaming) === null || _b === void 0 ? void 0 : _b.androidH264).source;
141
+ session.capture = yield this.openCapture(udid, onPacket, source);
133
142
  // Give the first keyframe/config a moment so callers get a ready stream,
134
143
  // but never block start-up indefinitely.
135
144
  yield Promise.race([firstConfig, new Promise((r) => setTimeout(r, 3000))]);
@@ -169,13 +178,54 @@ let AndroidH264StreamService = class AndroidH264StreamService {
169
178
  logger_1.default.info(`[${udid}] H.264 stream terminated.`);
170
179
  });
171
180
  }
181
+ /**
182
+ * Select the capture source: `screenrecord` (legacy, rollback path) or
183
+ * `scrcpy` (default). Dispatches to the matching producer.
184
+ */
185
+ openCapture(udid, onPacket, source) {
186
+ return __awaiter(this, void 0, void 0, function* () {
187
+ return source === 'screenrecord'
188
+ ? this.openScreenrecordCapture(udid, onPacket)
189
+ : this.openScrcpyCapture(udid, onPacket);
190
+ });
191
+ }
192
+ /**
193
+ * scrcpy-server capture: pushes/launches the vendored server jar over adb
194
+ * and streams its Annex-B H.264 socket output through a fresh
195
+ * {@link H264NalParser}. Unlike screenrecord, scrcpy has no time cap, so a
196
+ * 'close' while the session is still 'running' is unexpected — treat it as
197
+ * a crash and stop the session rather than silently going dark.
198
+ */
199
+ openScrcpyCapture(udid, onPacket) {
200
+ return __awaiter(this, void 0, void 0, function* () {
201
+ const { ScrcpyServerSession, scrcpyMaxSizeFromDims } = yield Promise.resolve().then(() => __importStar(require('./ScrcpyServerSession')));
202
+ const device = yield device_store_1.DeviceStoreFactory.getStore().findDevice({ udid });
203
+ const maxSize = scrcpyMaxSizeFromDims(Number(device === null || device === void 0 ? void 0 : device.screenWidth), Number(device === null || device === void 0 ? void 0 : device.screenHeight));
204
+ const parser = new h264NalParser_1.H264NalParser();
205
+ const session = new ScrcpyServerSession(udid);
206
+ session.on('data', (b) => {
207
+ for (const p of parser.push(b))
208
+ onPacket(p);
209
+ });
210
+ session.on('close', () => {
211
+ var _a;
212
+ // Crash recovery: scrcpy has no time cap, so a close while running is unexpected.
213
+ if (((_a = this.sessions.get(udid)) === null || _a === void 0 ? void 0 : _a.status) === 'running') {
214
+ logger_1.default.warn(`[${udid}] scrcpy stream closed unexpectedly; stopping H.264 session.`);
215
+ this.stop(udid);
216
+ }
217
+ });
218
+ yield session.start(maxSize);
219
+ return { kill: () => session.stop() };
220
+ });
221
+ }
172
222
  /**
173
223
  * Spawn screenrecord with the resolved adb binary (never a bare `adb` — the
174
224
  * GUI-launch PATH trap; see 1.8.2) and feed its Annex-B output through a
175
225
  * fresh {@link H264NalParser}. Restarts on process exit (cap/rotation) while
176
226
  * the session is still running.
177
227
  */
178
- openCapture(udid, onPacket) {
228
+ openScreenrecordCapture(udid, onPacket) {
179
229
  return __awaiter(this, void 0, void 0, function* () {
180
230
  var _a;
181
231
  const { default: AndroidDeviceManager } = yield Promise.resolve().then(() => __importStar(require('../AndroidDeviceManager')));
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.ScrcpyServerSession = exports.SCRCPY_DEVICE_JAR_PATH = void 0;
49
+ exports.buildScrcpyServerArgs = buildScrcpyServerArgs;
50
+ exports.scrcpyMaxSizeFromDims = scrcpyMaxSizeFromDims;
51
+ exports.parseAdbForwardPort = parseAdbForwardPort;
52
+ const child_process_1 = require("child_process");
53
+ const events_1 = require("events");
54
+ const net_1 = __importDefault(require("net"));
55
+ const util_1 = require("util");
56
+ const typedi_1 = require("typedi");
57
+ const logger_1 = __importDefault(require("../../logger"));
58
+ const scrcpyVersion_1 = require("./scrcpyVersion");
59
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
60
+ const LOCAL_ABSTRACT = 'scrcpy'; // device-side socket name scrcpy listens on
61
+ exports.SCRCPY_DEVICE_JAR_PATH = '/data/local/tmp/scrcpy-server-manual.jar';
62
+ /**
63
+ * The argv passed to the resolved adb AFTER any `-s <udid>` — a headless,
64
+ * video-only scrcpy-server launch. All three metadata channels are disabled
65
+ * (`send_device_meta=false`, `send_codec_meta=false`, `send_frame_meta=false`)
66
+ * so the socket carries plain Annex-B H.264 that the existing H264NalParser
67
+ * consumes unchanged. `send_dummy_byte=true` (the tunnel_forward readiness byte)
68
+ * is explicit because the socket reader skips exactly one leading byte.
69
+ * Arg NAMES verified against the vendored scrcpy 3.3.4 jar's dex; the version
70
+ * constant and this argv move together (see scrcpyVersion.ts / vendor/README.md).
71
+ * No `scid` → the server listens on `localabstract:scrcpy` (per-device namespace).
72
+ */
73
+ function buildScrcpyServerArgs(opts) {
74
+ return [
75
+ 'shell',
76
+ `CLASSPATH=${opts.jarDevicePath}`,
77
+ 'app_process',
78
+ '/',
79
+ 'com.genymobile.scrcpy.Server',
80
+ opts.version,
81
+ 'tunnel_forward=true',
82
+ 'audio=false',
83
+ 'control=false',
84
+ 'video=true',
85
+ 'video_codec=h264',
86
+ `max_size=${opts.maxSize}`,
87
+ 'video_bit_rate=4000000',
88
+ 'max_fps=30',
89
+ 'send_device_meta=false',
90
+ 'send_codec_meta=false',
91
+ 'send_frame_meta=false',
92
+ 'send_dummy_byte=true',
93
+ 'cleanup=true',
94
+ ];
95
+ }
96
+ /**
97
+ * scrcpy `max_size` caps the device's LONGER edge (single int, aspect preserved).
98
+ * Derive it so the SHORTER edge lands near `targetShortEdge` (matches today's
99
+ * ~720-wide screenrecord downscale). Never upscales.
100
+ */
101
+ function scrcpyMaxSizeFromDims(sw, sh, targetShortEdge = 720) {
102
+ const shortE = Math.min(sw, sh);
103
+ const longE = Math.max(sw, sh);
104
+ if (!Number.isFinite(shortE) || !Number.isFinite(longE) || shortE <= 0 || longE <= 0) {
105
+ return targetShortEdge * 2; // safe default longer-edge cap
106
+ }
107
+ if (shortE <= targetShortEdge)
108
+ return longE; // no upscale
109
+ return Math.round(longE * (targetShortEdge / shortE));
110
+ }
111
+ /** Parse the local TCP port that `adb forward tcp:0 …` prints on stdout. */
112
+ function parseAdbForwardPort(stdout) {
113
+ const port = parseInt(String(stdout).trim(), 10);
114
+ if (!Number.isInteger(port) || port <= 0) {
115
+ throw new Error(`Unparseable adb forward port: ${JSON.stringify(stdout)}`);
116
+ }
117
+ return port;
118
+ }
119
+ class ScrcpyServerSession extends events_1.EventEmitter {
120
+ constructor(udid) {
121
+ super();
122
+ this.udid = udid;
123
+ this.stopped = false;
124
+ this.closeEmitted = false;
125
+ this.adbPath = 'adb';
126
+ this.hostArgs = [];
127
+ this.base = []; // [...hostArgs, '-s', udid]
128
+ }
129
+ /** Guarantees at most one public 'close' event, and none once the session was intentionally stopped. */
130
+ emitClosedOnce() {
131
+ if (this.closeEmitted || this.stopped)
132
+ return;
133
+ this.closeEmitted = true;
134
+ this.emit('close');
135
+ }
136
+ start(maxSize) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ var _a, _b, _c;
139
+ const { default: AndroidDeviceManager } = yield Promise.resolve().then(() => __importStar(require('../AndroidDeviceManager')));
140
+ const adb = yield typedi_1.Container.get(AndroidDeviceManager).getAdbForDevice(this.udid);
141
+ if (this.stopped)
142
+ throw new Error('session stopped during start');
143
+ if (!((_a = adb === null || adb === void 0 ? void 0 : adb.executable) === null || _a === void 0 ? void 0 : _a.path))
144
+ throw new Error(`[${this.udid}] adb executable path not resolved`);
145
+ this.adbPath = adb.executable.path;
146
+ this.hostArgs = (adb === null || adb === void 0 ? void 0 : adb.adbHost) && (adb === null || adb === void 0 ? void 0 : adb.adbPort) ? ['-H', adb.adbHost, '-P', String(adb.adbPort)] : [];
147
+ this.base = [...this.hostArgs, '-s', this.udid];
148
+ try {
149
+ // 1) push jar (idempotent enough; overwrite is cheap and safe). Push
150
+ // destination and the CLASSPATH must be the same path — use the one
151
+ // constant so editing it can never point CLASSPATH at an unpushed jar.
152
+ yield execFileAsync(this.adbPath, [...this.base, 'push', (0, scrcpyVersion_1.scrcpyServerJarPath)(), exports.SCRCPY_DEVICE_JAR_PATH]);
153
+ if (this.stopped)
154
+ throw new Error('session stopped during start');
155
+ // 2) app_process (long-lived). buildScrcpyServerArgs + SCRCPY_DEVICE_JAR_PATH
156
+ // are defined in THIS file (Task 2) — reference them directly (no self-import).
157
+ const serverArgs = buildScrcpyServerArgs({
158
+ version: scrcpyVersion_1.SCRCPY_SERVER_VERSION,
159
+ jarDevicePath: exports.SCRCPY_DEVICE_JAR_PATH,
160
+ maxSize,
161
+ });
162
+ this.proc = (0, child_process_1.spawn)(this.adbPath, [...this.base, ...serverArgs]);
163
+ (_b = this.proc.stdout) === null || _b === void 0 ? void 0 : _b.resume(); // scrcpy-server writes video to the socket, not stdout; drain so it can't backpressure
164
+ (_c = this.proc.stderr) === null || _c === void 0 ? void 0 : _c.on('data', (d) => logger_1.default.debug(`[${this.udid}] scrcpy-server: ${d.toString().trim()}`));
165
+ this.proc.on('close', () => { this.emitClosedOnce(); });
166
+ this.proc.on('error', (e) => {
167
+ // A ChildProcess emitting 'error' with zero listeners crashes the process — always have one.
168
+ logger_1.default.warn(`[${this.udid}] scrcpy-server proc error: ${e.message}`);
169
+ this.emitClosedOnce();
170
+ });
171
+ // 3) forward a local port to the device socket, then connect
172
+ // (parseAdbForwardPort is defined in this file — Task 3 — so call it directly)
173
+ const { stdout } = yield execFileAsync(this.adbPath, [...this.base, 'forward', 'tcp:0', `localabstract:${LOCAL_ABSTRACT}`]);
174
+ const port = parseAdbForwardPort(stdout);
175
+ this.forwardSpec = `tcp:${port}`;
176
+ if (this.stopped)
177
+ throw new Error('session stopped during start');
178
+ yield this.connectWithRetry(port);
179
+ if (this.stopped)
180
+ throw new Error('stopped during start');
181
+ }
182
+ catch (e) {
183
+ this.cleanup();
184
+ throw e;
185
+ }
186
+ });
187
+ }
188
+ connectWithRetry(port, attempt = 0) {
189
+ return new Promise((resolve, reject) => {
190
+ if (this.stopped) {
191
+ reject(new Error('stopped'));
192
+ return;
193
+ }
194
+ const sock = net_1.default.connect(port, '127.0.0.1');
195
+ let settled = false;
196
+ // `adb forward` accepts the LOCAL tcp connection immediately (~5ms) and only
197
+ // THEN dials the device-side scrcpy socket; if scrcpy-server hasn't bound it
198
+ // yet, adb closes us with zero bytes. So 'connect' is NOT proof the server is
199
+ // ready — a connect that closes/errors before any byte means "not ready yet"
200
+ // and we retry. A connection counts as real only once scrcpy's readiness
201
+ // dummy byte (send_dummy_byte=true) arrives. This is why the dummy byte exists.
202
+ const retry = (reason) => {
203
+ if (settled)
204
+ return;
205
+ settled = true;
206
+ sock.removeListener('data', onFirstData);
207
+ sock.removeListener('error', retryErr);
208
+ sock.removeListener('close', retryClose);
209
+ sock.destroy(); // don't leak the failed socket
210
+ if (attempt < 19 && !this.stopped) { // 20 attempts total (0..19)
211
+ setTimeout(() => this.connectWithRetry(port, attempt + 1).then(resolve, reject), 100);
212
+ }
213
+ else {
214
+ reject(new Error(`scrcpy connect failed after ${attempt + 1} attempts: ${reason}`));
215
+ }
216
+ };
217
+ const retryErr = (e) => retry(e.message || 'socket error');
218
+ const retryClose = () => retry('closed before data');
219
+ const onFirstData = (first) => {
220
+ if (settled)
221
+ return;
222
+ settled = true;
223
+ sock.removeListener('error', retryErr);
224
+ sock.removeListener('close', retryClose);
225
+ if (this.stopped) {
226
+ sock.destroy();
227
+ reject(new Error('stopped during connect'));
228
+ return;
229
+ }
230
+ this.socket = sock;
231
+ // The first byte is scrcpy's readiness dummy byte; the raw H.264 Annex-B
232
+ // stream (SPS/PPS/IDR…) begins at byte 1 of this same chunk.
233
+ const rest = first.subarray(1);
234
+ sock.on('data', (chunk) => { if (!this.stopped)
235
+ this.emit('data', chunk); });
236
+ sock.on('error', (e) => logger_1.default.warn(`[${this.udid}] scrcpy socket error: ${e.message}`)); // steady-state: log; 'close' follows
237
+ sock.on('close', () => { this.emitClosedOnce(); });
238
+ if (rest.length && !this.stopped)
239
+ this.emit('data', rest);
240
+ resolve();
241
+ };
242
+ sock.once('error', retryErr);
243
+ sock.once('close', retryClose);
244
+ sock.once('data', onFirstData);
245
+ });
246
+ }
247
+ cleanup() {
248
+ var _a, _b;
249
+ this.stopped = true; // must be first: gates emitClosedOnce + connectWithRetry + connect-handler against a proc/socket event racing this cleanup
250
+ try {
251
+ (_a = this.socket) === null || _a === void 0 ? void 0 : _a.destroy();
252
+ }
253
+ catch ( /* best-effort */_c) { /* best-effort */ }
254
+ try {
255
+ (_b = this.proc) === null || _b === void 0 ? void 0 : _b.kill('SIGKILL');
256
+ }
257
+ catch ( /* best-effort */_d) { /* best-effort */ }
258
+ if (this.forwardSpec) {
259
+ const spec = this.forwardSpec;
260
+ this.forwardSpec = undefined; // clear first so a repeated stop()/cleanup() doesn't re-spawn adb
261
+ (0, child_process_1.execFile)(this.adbPath, [...this.base, 'forward', '--remove', spec], () => undefined);
262
+ }
263
+ }
264
+ stop() {
265
+ this.stopped = true;
266
+ this.cleanup();
267
+ }
268
+ }
269
+ exports.ScrcpyServerSession = ScrcpyServerSession;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SCRCPY_SERVER_VERSION = void 0;
7
+ exports.scrcpyServerJarFilename = scrcpyServerJarFilename;
8
+ exports.scrcpyServerJarPath = scrcpyServerJarPath;
9
+ const path_1 = __importDefault(require("path"));
10
+ /**
11
+ * Pinned scrcpy-server version. The vendored jar filename AND the `app_process`
12
+ * <version> argument both derive from this — they must always agree, so this is
13
+ * the single source of truth. Bump procedure: see vendor/README.md.
14
+ */
15
+ exports.SCRCPY_SERVER_VERSION = '3.3.4';
16
+ function scrcpyServerJarFilename() {
17
+ return `scrcpy-server-${exports.SCRCPY_SERVER_VERSION}.jar`;
18
+ }
19
+ /** Absolute path to the vendored jar, valid in src (ts-node) and lib (built). */
20
+ function scrcpyServerJarPath() {
21
+ return path_1.default.join(__dirname, 'vendor', scrcpyServerJarFilename());
22
+ }
@@ -114,6 +114,9 @@ let ServerManager = ServerManager_1 = class ServerManager {
114
114
  if (httpServer) {
115
115
  (0, h264StreamWs_1.attachH264Ws)(httpServer, {
116
116
  redeem: (ticket, udid) => typedi_1.Container.get(StreamTicketService_1.StreamTicketService).redeem(ticket, udid),
117
+ // No source opt on purpose: start() resolves the capture source from the
118
+ // streaming.androidH264 config itself, so this WS auto-start honours a
119
+ // { source: 'screenrecord' } rollback just like the REST stream/start.
117
120
  startStream: (udid) => typedi_1.Container.get(AndroidH264StreamService_1.default).start(udid),
118
121
  });
119
122
  }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const androidH264Config_1 = require("../../src/app/routers/androidH264Config");
5
+ describe('resolveAndroidH264', () => {
6
+ it('undefined/false → disabled', () => {
7
+ (0, chai_1.expect)((0, androidH264Config_1.resolveAndroidH264)(undefined)).to.deep.equal({ enabled: false, source: 'scrcpy' });
8
+ (0, chai_1.expect)((0, androidH264Config_1.resolveAndroidH264)(false)).to.deep.equal({ enabled: false, source: 'scrcpy' });
9
+ });
10
+ it('true → enabled with scrcpy (the new meaning of true)', () => {
11
+ (0, chai_1.expect)((0, androidH264Config_1.resolveAndroidH264)(true)).to.deep.equal({ enabled: true, source: 'scrcpy' });
12
+ });
13
+ it('object → enabled; source defaults to scrcpy, honored when explicit', () => {
14
+ (0, chai_1.expect)((0, androidH264Config_1.resolveAndroidH264)({})).to.deep.equal({ enabled: true, source: 'scrcpy' });
15
+ (0, chai_1.expect)((0, androidH264Config_1.resolveAndroidH264)({ source: 'screenrecord' })).to.deep.equal({
16
+ enabled: true,
17
+ source: 'screenrecord',
18
+ });
19
+ });
20
+ });
@@ -14,7 +14,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  require("reflect-metadata");
16
16
  const chai_1 = require("chai");
17
+ const typedi_1 = require("typedi");
17
18
  const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
19
+ const PluginContext_1 = require("../../src/PluginContext");
18
20
  // Lifecycle/state only — the capture seam (openCapture) is stubbed so the test
19
21
  // drives packet flow without a real device or screenrecord process.
20
22
  function make() {
@@ -24,6 +26,13 @@ function make() {
24
26
  return svc;
25
27
  }
26
28
  describe('AndroidH264StreamService', () => {
29
+ // start(udid) with no opts reads the flag config from PluginContext. Seed a
30
+ // known default before each test (streaming undefined → source 'scrcpy'); the
31
+ // WS-source tests override it in-test. Set (not remove) so TypeDI never hits a
32
+ // ServiceNotFoundError on the next Container.get.
33
+ beforeEach(() => {
34
+ typedi_1.Container.set(PluginContext_1.PluginContext, { pluginArgs: {} });
35
+ });
27
36
  it('start() creates a multiplexer, seeds it with the config packet, and resolves', () => __awaiter(void 0, void 0, void 0, function* () {
28
37
  const svc = make();
29
38
  svc.openCapture = (_udid, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
@@ -80,4 +89,65 @@ describe('AndroidH264StreamService', () => {
80
89
  (0, chai_1.expect)(killed).to.equal(true);
81
90
  (0, chai_1.expect)(svc.getMultiplexer('udid-z')).to.equal(undefined);
82
91
  }));
92
+ it('start({source:"scrcpy"}) uses the scrcpy capture seam', () => __awaiter(void 0, void 0, void 0, function* () {
93
+ const svc = make();
94
+ let used = '';
95
+ svc.openScrcpyCapture = (_u, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
96
+ used = 'scrcpy';
97
+ onPacket({ type: 'config', data: Buffer.from([0]), ptsMs: 0 });
98
+ return { kill: () => undefined };
99
+ });
100
+ svc.openScreenrecordCapture = () => __awaiter(void 0, void 0, void 0, function* () { used = 'screenrecord'; return { kill: () => undefined }; });
101
+ yield svc.start('udid-s', { source: 'scrcpy' });
102
+ (0, chai_1.expect)(used).to.equal('scrcpy');
103
+ }));
104
+ it('start({source:"screenrecord"}) uses the legacy capture seam', () => __awaiter(void 0, void 0, void 0, function* () {
105
+ const svc = make();
106
+ let used = '';
107
+ svc.openScrcpyCapture = () => __awaiter(void 0, void 0, void 0, function* () { used = 'scrcpy'; return { kill: () => undefined }; });
108
+ svc.openScreenrecordCapture = (_u, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
109
+ used = 'screenrecord';
110
+ onPacket({ type: 'config', data: Buffer.from([0]), ptsMs: 0 });
111
+ return { kill: () => undefined };
112
+ });
113
+ yield svc.start('udid-r', { source: 'screenrecord' });
114
+ (0, chai_1.expect)(used).to.equal('screenrecord');
115
+ }));
116
+ // The WebSocket auto-start path calls start(udid) with NO opts. It must still
117
+ // honour the configured capture source (read from PluginContext), not silently
118
+ // default to scrcpy — otherwise a { source: 'screenrecord' } rollback is bypassed.
119
+ it('start(udid) with no opts uses the source configured in PluginContext (screenrecord)', () => __awaiter(void 0, void 0, void 0, function* () {
120
+ const svc = make();
121
+ typedi_1.Container.set(PluginContext_1.PluginContext, {
122
+ pluginArgs: { streaming: { androidH264: { source: 'screenrecord' } } },
123
+ });
124
+ let used = '';
125
+ svc.openScrcpyCapture = () => __awaiter(void 0, void 0, void 0, function* () {
126
+ used = 'scrcpy';
127
+ return { kill: () => undefined };
128
+ });
129
+ svc.openScreenrecordCapture = (_u, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
130
+ used = 'screenrecord';
131
+ onPacket({ type: 'config', data: Buffer.from([0]), ptsMs: 0 });
132
+ return { kill: () => undefined };
133
+ });
134
+ yield svc.start('udid-ws-sr'); // no opts — mimics the WS startStream(udid) path
135
+ (0, chai_1.expect)(used).to.equal('screenrecord');
136
+ }));
137
+ it('start(udid) with no opts uses the source configured in PluginContext (scrcpy)', () => __awaiter(void 0, void 0, void 0, function* () {
138
+ const svc = make();
139
+ typedi_1.Container.set(PluginContext_1.PluginContext, { pluginArgs: { streaming: { androidH264: true } } });
140
+ let used = '';
141
+ svc.openScrcpyCapture = (_u, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
142
+ used = 'scrcpy';
143
+ onPacket({ type: 'config', data: Buffer.from([0]), ptsMs: 0 });
144
+ return { kill: () => undefined };
145
+ });
146
+ svc.openScreenrecordCapture = () => __awaiter(void 0, void 0, void 0, function* () {
147
+ used = 'screenrecord';
148
+ return { kill: () => undefined };
149
+ });
150
+ yield svc.start('udid-ws-scrcpy'); // no opts
151
+ (0, chai_1.expect)(used).to.equal('scrcpy');
152
+ }));
83
153
  });
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const ScrcpyServerSession_1 = require("../../src/device-managers/android/ScrcpyServerSession");
5
+ describe('buildScrcpyServerArgs', () => {
6
+ it('builds the exact video-only app_process argv', () => {
7
+ const argv = (0, ScrcpyServerSession_1.buildScrcpyServerArgs)({ version: '3.3.4', jarDevicePath: ScrcpyServerSession_1.SCRCPY_DEVICE_JAR_PATH, maxSize: 1560 });
8
+ (0, chai_1.expect)(argv).to.deep.equal([
9
+ 'shell',
10
+ `CLASSPATH=${ScrcpyServerSession_1.SCRCPY_DEVICE_JAR_PATH}`,
11
+ 'app_process',
12
+ '/',
13
+ 'com.genymobile.scrcpy.Server',
14
+ '3.3.4',
15
+ 'tunnel_forward=true',
16
+ 'audio=false',
17
+ 'control=false',
18
+ 'video=true',
19
+ 'video_codec=h264',
20
+ 'max_size=1560',
21
+ 'video_bit_rate=4000000',
22
+ 'max_fps=30',
23
+ 'send_device_meta=false',
24
+ 'send_codec_meta=false',
25
+ 'send_frame_meta=false',
26
+ 'send_dummy_byte=true',
27
+ 'cleanup=true',
28
+ ]);
29
+ });
30
+ });
31
+ describe('scrcpyMaxSizeFromDims', () => {
32
+ it('caps the LONGER edge so the shorter edge lands near the target', () => {
33
+ // 1080x2340: short=1080 → scale 720/1080; long=2340*0.6667 ≈ 1560
34
+ (0, chai_1.expect)((0, ScrcpyServerSession_1.scrcpyMaxSizeFromDims)(1080, 2340)).to.equal(1560);
35
+ });
36
+ it('is orientation-agnostic (landscape same result)', () => {
37
+ (0, chai_1.expect)((0, ScrcpyServerSession_1.scrcpyMaxSizeFromDims)(2340, 1080)).to.equal(1560);
38
+ });
39
+ it('never upscales when the short edge is already below target', () => {
40
+ (0, chai_1.expect)((0, ScrcpyServerSession_1.scrcpyMaxSizeFromDims)(480, 800)).to.equal(800);
41
+ });
42
+ it('returns safe default (1440) for non-finite or non-positive dimensions', () => {
43
+ (0, chai_1.expect)((0, ScrcpyServerSession_1.scrcpyMaxSizeFromDims)(NaN, 800)).to.equal(1440);
44
+ (0, chai_1.expect)((0, ScrcpyServerSession_1.scrcpyMaxSizeFromDims)(0, 800)).to.equal(1440);
45
+ (0, chai_1.expect)((0, ScrcpyServerSession_1.scrcpyMaxSizeFromDims)(-100, 800)).to.equal(1440);
46
+ });
47
+ });
48
+ describe('parseAdbForwardPort', () => {
49
+ it('reads the assigned port from `adb forward tcp:0` output', () => {
50
+ (0, chai_1.expect)((0, ScrcpyServerSession_1.parseAdbForwardPort)('41337\n')).to.equal(41337);
51
+ });
52
+ it('throws on non-numeric output', () => {
53
+ (0, chai_1.expect)(() => (0, ScrcpyServerSession_1.parseAdbForwardPort)('error: device offline')).to.throw();
54
+ });
55
+ it('throws on zero or negative port numbers', () => {
56
+ (0, chai_1.expect)(() => (0, ScrcpyServerSession_1.parseAdbForwardPort)('0')).to.throw();
57
+ (0, chai_1.expect)(() => (0, ScrcpyServerSession_1.parseAdbForwardPort)('-1')).to.throw();
58
+ });
59
+ });
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const chai_1 = require("chai");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const scrcpyVersion_1 = require("../../src/device-managers/android/scrcpyVersion");
9
+ describe('scrcpyVersion', () => {
10
+ it('derives the jar filename from the single version constant', () => {
11
+ (0, chai_1.expect)((0, scrcpyVersion_1.scrcpyServerJarFilename)()).to.equal(`scrcpy-server-${scrcpyVersion_1.SCRCPY_SERVER_VERSION}.jar`);
12
+ });
13
+ it('the vendored jar exists on disk at the resolved path', () => {
14
+ (0, chai_1.expect)(fs_1.default.existsSync((0, scrcpyVersion_1.scrcpyServerJarPath)()), (0, scrcpyVersion_1.scrcpyServerJarPath)()).to.equal(true);
15
+ });
16
+ });
@@ -1 +1 @@
1
- {"root":["../src/InternalHttpClient.ts","../src/PluginContext.ts","../src/XenonCapabilityManager.ts","../src/chromeUtils.ts","../src/config.ts","../src/device-utils.ts","../src/index.ts","../src/logger.ts","../src/plugin.ts","../src/prisma.ts","../src/app/index.ts","../src/app/swagger-docs.ts","../src/app/swagger.ts","../src/app/routers/apikeys.ts","../src/app/routers/apps.ts","../src/app/routers/audit.ts","../src/app/routers/auth.ts","../src/app/routers/bug-report.ts","../src/app/routers/build-export.ts","../src/app/routers/capabilities.ts","../src/app/routers/config.ts","../src/app/routers/control.ts","../src/app/routers/dashboard.ts","../src/app/routers/grid.ts","../src/app/routers/interceptor.ts","../src/app/routers/ports.ts","../src/app/routers/processes.ts","../src/app/routers/profile.ts","../src/app/routers/profileIdentity.ts","../src/app/routers/projects.ts","../src/app/routers/recordings.ts","../src/app/routers/reservation.ts","../src/app/routers/sdk-leases.ts","../src/app/routers/sdk-version.ts","../src/app/routers/streamType.ts","../src/app/routers/teams.ts","../src/app/routers/users.ts","../src/app/routers/webhook.ts","../src/app/ws/h264StreamWs.ts","../src/commands/handle.ts","../src/commands/index.ts","../src/dashboard/asset-manager.ts","../src/dashboard/commands.ts","../src/dashboard/event-manager.ts","../src/dashboard/services/app-service.ts","../src/dashboard/services/failure-analysis-service.ts","../src/dashboard/services/session-service.ts","../src/data-service/CircuitBreaker.ts","../src/data-service/config-service.ts","../src/data-service/db.ts","../src/data-service/device-service.ts","../src/data-service/device-store.interface.ts","../src/data-service/device-store.ts","../src/data-service/pending-sessions-service.ts","../src/data-service/pluginArgs.ts","../src/data-service/prisma-service.ts","../src/data-service/prisma-store.ts","../src/data-service/queue-service.ts","../src/data-service/web-config-service.ts","../src/device-managers/AndroidDeviceManager.ts","../src/device-managers/ChromeDriverManager.ts","../src/device-managers/HealthMonitorService.ts","../src/device-managers/IOSDeviceManager.ts","../src/device-managers/NodeDevices.ts","../src/device-managers/iOSTracker.ts","../src/device-managers/index.ts","../src/device-managers/android/AndroidH264StreamService.ts","../src/device-managers/android/AndroidStreamService.ts","../src/device-managers/android/DeviceLockManager.ts","../src/device-managers/android/H264Multiplexer.ts","../src/device-managers/android/h264NalParser.ts","../src/device-managers/cloud/CapabilityManager.ts","../src/device-managers/cloud/Devices.ts","../src/device-managers/ios/IOSDiscoveryService.ts","../src/device-managers/ios/IOSStreamService.ts","../src/device-managers/ios/WDAClient.ts","../src/device-managers/ios/iosBattery.ts","../src/device-managers/ios/iosStreamDiagnostics.ts","../src/enums/Capabilities.ts","../src/enums/Cloud.ts","../src/enums/Platform.ts","../src/enums/SessionType.ts","../src/enums/SocketEvents.ts","../src/generated/client/default.d.ts","../src/generated/client/default.js","../src/generated/client/edge.d.ts","../src/generated/client/edge.js","../src/generated/client/index-browser.js","../src/generated/client/index.d.ts","../src/generated/client/index.js","../src/generated/client/wasm.d.ts","../src/generated/client/wasm.js","../src/generated/client/runtime/edge-esm.js","../src/generated/client/runtime/edge.js","../src/generated/client/runtime/index-browser.d.ts","../src/generated/client/runtime/index-browser.js","../src/generated/client/runtime/library.d.ts","../src/generated/client/runtime/library.js","../src/generated/client/runtime/react-native.js","../src/generated/client/runtime/wasm.js","../src/helpers/UniversalMjpegProxy.ts","../src/helpers/index.ts","../src/interceptors/CommandInterceptor.ts","../src/interfaces/ICloudManager.ts","../src/interfaces/IDevice.ts","../src/interfaces/IDeviceFilterOptions.ts","../src/interfaces/IDeviceManager.ts","../src/interfaces/IOptions.ts","../src/interfaces/IPluginArgs.ts","../src/interfaces/ISessionCapability.ts","../src/logging/sessionContext.ts","../src/middleware/authMiddleware.ts","../src/middleware/csrfMiddleware.ts","../src/middleware/leaseTokenMiddleware.ts","../src/middleware/loginRateLimiter.ts","../src/middleware/rateLimitMiddleware.ts","../src/middleware/roleGuard.ts","../src/middleware/scopeGuard.ts","../src/profiling/AndroidAppProfiler.ts","../src/proxy/wd-command-proxy.ts","../src/scripts/generate-database-migration.ts","../src/scripts/initialize-database.ts","../src/scripts/install-go-ios.ts","../src/scripts/prepare-prisma.ts","../src/scripts/run-migrations.ts","../src/services/AICommandService.ts","../src/services/AIService.ts","../src/services/ApiKeyService.ts","../src/services/CircuitBreaker.ts","../src/services/CleanupService.ts","../src/services/DeviceReconciler.ts","../src/services/EmailService.ts","../src/services/EventBus.ts","../src/services/EventLogService.ts","../src/services/InspectorService.ts","../src/services/InterceptorService.ts","../src/services/MetricsService.ts","../src/services/NetworkConditioningService.ts","../src/services/NotificationService.ts","../src/services/OrphanSweeper.ts","../src/services/PasswordResetService.ts","../src/services/PortAllocator.ts","../src/services/ProcessMetricsService.ts","../src/services/ProcessRegistry.ts","../src/services/RequestLogService.ts","../src/services/ResourceIsolationService.ts","../src/services/SecurityService.ts","../src/services/SelectorStateService.ts","../src/services/SelectorVerificationJob.ts","../src/services/ServerManager.ts","../src/services/SessionHeartbeatService.ts","../src/services/SessionLifecycleService.ts","../src/services/ShutdownCoordinator.ts","../src/services/SocketClient.ts","../src/services/SocketServer.ts","../src/services/TeamService.ts","../src/services/TracingService.ts","../src/services/UserService.ts","../src/services/UserSessionService.ts","../src/services/VideoPipelineService.ts","../src/services/sessionTokenGate.ts","../src/services/artifacts/ArtifactStore.ts","../src/services/autowait/AutowaitService.ts","../src/services/autowait/waitFor.ts","../src/services/bug-report/BugReportService.ts","../src/services/bug-report/archive.ts","../src/services/bug-report/har-collector.ts","../src/services/bug-report/manifest.ts","../src/services/bug-report/readme.ts","../src/services/bug-report/types.ts","../src/services/bug-report/video-slice.ts","../src/services/bug-report/window.ts","../src/services/healing/FuzzyXmlHealingProvider.ts","../src/services/healing/HealEtalonService.ts","../src/services/healing/HealedLocatorGenerator.ts","../src/services/healing/HealingMetrics.ts","../src/services/healing/HealingOrchestrator.ts","../src/services/healing/LlmHealingProvider.ts","../src/services/healing/OcrHealingProvider.ts","../src/services/healing/ResilioTreeHealingProvider.ts","../src/services/healing/VisualAiHealingProvider.ts","../src/services/healing/types.ts","../src/services/identity/bootstrap.ts","../src/services/identity/sessionCleanupCron.ts","../src/services/identity/userAuthorization.ts","../src/services/interceptor/AndroidProxyAdapter.ts","../src/services/interceptor/CertManager.ts","../src/services/interceptor/HarBuilder.ts","../src/services/interceptor/HostFilter.ts","../src/services/interceptor/MitmProxyHost.ts","../src/services/interceptor/MockEngine.ts","../src/services/interceptor/RequestBuffer.ts","../src/services/interceptor/SessionArchive.ts","../src/services/interceptor/types.ts","../src/services/lease/LeaseOrphanSweeper.ts","../src/services/lease/LeaseService.ts","../src/services/lease/buildCapabilityBag.ts","../src/services/lease/leaseToken.ts","../src/services/omni-vision/OmniVisionService.ts","../src/services/omni-vision/VisionAssertionService.ts","../src/services/ports/PortAllocatorClient.ts","../src/services/ports/PortAllocatorService.ts","../src/services/recording/RecordingOrchestrator.ts","../src/services/recording/annotation-render.ts","../src/services/recording/busy-precheck.ts","../src/services/recording/concurrency-gate.ts","../src/services/recording/manualLock.ts","../src/services/recording/proof-bundle.ts","../src/services/recording/recording-store.ts","../src/services/telemetry/attributes.ts","../src/services/token/JwtKeyService.ts","../src/services/token/StreamTicketService.ts","../src/services/token/mcpScopes.ts","../src/sessions/CloudSession.ts","../src/sessions/LocalSession.ts","../src/sessions/RemoteSession.ts","../src/sessions/SessionManager.ts","../src/sessions/XenonSession.ts","../src/types/CLIArgs.ts","../src/types/CloudArgs.ts","../src/types/CloudSchema.ts","../src/types/DeviceType.ts","../src/types/DeviceUpdate.ts","../src/types/IOSDevice.ts","../src/types/Platform.ts","../src/types/SessionStatus.ts","../src/types/express.d.ts","../src/types/healing.d.ts","../src/types/identity.ts","../src/validators/CapabilityValidator.ts","../test/e2e/e2ehelper.ts","../test/e2e/plugin-harness.ts","../test/e2e/plugin.spec.ts","../test/e2e/startup-verification.spec.ts","../test/e2e/telemetry_verification.spec.ts","../test/e2e/video-recording-test.spec.js","../test/e2e/android/conf.spec.js","../test/e2e/android/conf2.spec.js","../test/e2e/android/conf3.spec.js","../test/e2e/hubnode/forward-request.spec.ts","../test/e2e/hubnode/hubnode.spec.ts","../test/e2e/ios/conf1.spec.js","../test/e2e/ios/conf2.spec.js","../test/helpers/seedUser.ts","../test/helpers/test-container.ts","../test/integration/androidDevices.spec.ts","../test/integration/audit-ingest.spec.ts","../test/integration/auth-flow.spec.ts","../test/integration/auth-rate-limit.spec.ts","../test/integration/bug-report-route.spec.ts","../test/integration/cliArgs.js","../test/integration/cross-workflow.spec.ts","../test/integration/forgot-password-rate-limit.spec.ts","../test/integration/forgot-password.spec.ts","../test/integration/healing-events-sessionfilter.spec.ts","../test/integration/legacy-deprecation-headers.spec.ts","../test/integration/migration.spec.ts","../test/integration/node-pair-auth-rest.spec.ts","../test/integration/ports-router.spec.ts","../test/integration/profile-tokens.spec.ts","../test/integration/recordings-router.spec.ts","../test/integration/reset-revokes-sessions.spec.ts","../test/integration/role-matrix.spec.ts","../test/integration/sdk-leases-router.spec.ts","../test/integration/sdk-version.spec.ts","../test/integration/selector-health-endpoint.spec.ts","../test/integration/selector-state-flow.spec.ts","../test/integration/team-membership-migration.spec.ts","../test/integration/team-visibility-dashboard.spec.ts","../test/integration/team-visibility-grid.spec.ts","../test/integration/team-visibility-recordings.spec.ts","../test/integration/users-flow.spec.ts","../test/integration/users-lockout.spec.ts","../test/integration/ios/01iOSSimulator.spec.ts","../test/integration/ios/02iOSDevices.spec.ts","../test/unit/AndroidDeviceManager.spec.ts","../test/unit/ApiKeyService.test.ts","../test/unit/ArtifactStore.spec.ts","../test/unit/AutowaitService.spec.ts","../test/unit/CapabilitiesRouter.spec.ts","../test/unit/ChromeDriverManager.spec.js","../test/unit/CleanupService.spec.ts","../test/unit/CommandInterceptor.autowait.spec.ts","../test/unit/DeviceModel.spec.ts","../test/unit/EmailService.test.ts","../test/unit/EventLogService.spec.ts","../test/unit/FuzzyXmlHealingProvider.test.ts","../test/unit/GetAdbOriginal.js","../test/unit/HealingCascade.test.ts","../test/unit/IOSDeviceManager.spec.js","../test/unit/JwtKeyService.spec.ts","../test/unit/McpScopes.spec.ts","../test/unit/OrphanSweeper.test.ts","../test/unit/PasswordResetService.test.ts","../test/unit/PortAllocator.test.ts","../test/unit/ProcessRegistry.test.ts","../test/unit/ProjectsRouter.spec.ts","../test/unit/RemoteIOs.spec.js","../test/unit/ResilioTreeHealingProvider.test.ts","../test/unit/SessionHeartbeatWrite.test.ts","../test/unit/SessionLifecycleCaps.spec.ts","../test/unit/SessionTokenGate.spec.ts","../test/unit/StreamTicketService.spec.ts","../test/unit/TeamService.test.ts","../test/unit/TokenEndpoint.spec.ts","../test/unit/UserService.test.ts","../test/unit/UserSessionService.test.ts","../test/unit/XenonCapabilityManager.spec.ts","../test/unit/aggregate-hotspots-tuple-key.spec.ts","../test/unit/android-h264-service.spec.ts","../test/unit/android-stream-adb-path.spec.ts","../test/unit/android-stream-encoder.spec.ts","../test/unit/android-stream-fast-start.spec.ts","../test/unit/android-stream-release.spec.ts","../test/unit/auth-me-auth-disabled.test.ts","../test/unit/authMiddleware.bearer.spec.ts","../test/unit/authMiddleware.mcpAudience.spec.ts","../test/unit/authMiddleware.streamTicket.spec.ts","../test/unit/authMiddleware.test.ts","../test/unit/bootstrap-identity.test.ts","../test/unit/build-export.spec.ts","../test/unit/busy-precheck.spec.ts","../test/unit/commands.spec.js","../test/unit/concurrency-gate.spec.ts","../test/unit/config.spec.js","../test/unit/device-service.spec.ts","../test/unit/device-utils.spec.ts","../test/unit/event-manager-regression-hook.spec.ts","../test/unit/extract-access-key-token-pair.test.ts","../test/unit/findAndLockDevice-leasemerge.spec.ts","../test/unit/h264-multiplexer.spec.ts","../test/unit/h264-nal-parser.spec.ts","../test/unit/h264-ws-frame.spec.ts","../test/unit/healing-instrumentation.spec.ts","../test/unit/healing-state-endpoints.spec.ts","../test/unit/healing-tier-gate.spec.ts","../test/unit/helpers.spec.ts","../test/unit/iosBattery.spec.ts","../test/unit/iosStreamDiagnostics.spec.ts","../test/unit/loginRateLimiter.test.ts","../test/unit/manualLock.test.ts","../test/unit/omni-vision.spec.ts","../test/unit/plugin.spec.ts","../test/unit/prisma.spec.ts","../test/unit/profile-router.test.ts","../test/unit/profileIdentity.spec.ts","../test/unit/proof-bundle.spec.ts","../test/unit/rateLimitMiddleware.test.ts","../test/unit/recording-instrumentation.spec.ts","../test/unit/recording-orchestrator.spec.ts","../test/unit/recording-store.spec.ts","../test/unit/roleGuard.test.ts","../test/unit/run-migrations-fail-fast.test.ts","../test/unit/run-migrations.test.ts","../test/unit/selector-state-service.spec.ts","../test/unit/selector-verification-job.spec.ts","../test/unit/socketServer.bearer.spec.ts","../test/unit/stream-status-type.spec.ts","../test/unit/team-filter.test.ts","../test/unit/tracing-service-init.spec.ts","../test/unit/userAuthorization.test.ts","../test/unit/users-router.test.ts","../test/unit/video-pipeline-args.spec.ts","../test/unit/video-pipeline-outputpath.spec.ts","../test/unit/bug-report/archive.spec.ts","../test/unit/bug-report/har-collector.spec.ts","../test/unit/bug-report/manifest.spec.ts","../test/unit/bug-report/readme.spec.ts","../test/unit/bug-report/route.spec.ts","../test/unit/bug-report/service.spec.ts","../test/unit/bug-report/video-slice.spec.ts","../test/unit/bug-report/window.spec.ts","../test/unit/fixtures/device.config.js","../test/unit/fixtures/devices.js","../test/unit/interceptor/AndroidProxyAdapter.spec.ts","../test/unit/interceptor/CertManager.spec.ts","../test/unit/interceptor/HarBuilder.spec.ts","../test/unit/interceptor/HostFilter.spec.ts","../test/unit/interceptor/MitmProxyHost.spec.ts","../test/unit/interceptor/MockEngine.spec.ts","../test/unit/interceptor/RequestBuffer.spec.ts","../test/unit/interceptor/SessionArchive.spec.ts","../test/unit/lease/LeaseOrphanSweeper.spec.ts","../test/unit/lease/LeaseService.spec.ts","../test/unit/lease/buildCapabilityBag.spec.ts","../test/unit/lease/leaseToken.spec.ts","../test/unit/lease/nodePairAuth.spec.ts","../test/unit/ports/PortAllocatorService.spec.ts"],"version":"5.9.3"}
1
+ {"root":["../src/InternalHttpClient.ts","../src/PluginContext.ts","../src/XenonCapabilityManager.ts","../src/chromeUtils.ts","../src/config.ts","../src/device-utils.ts","../src/index.ts","../src/logger.ts","../src/plugin.ts","../src/prisma.ts","../src/app/index.ts","../src/app/swagger-docs.ts","../src/app/swagger.ts","../src/app/routers/androidH264Config.ts","../src/app/routers/apikeys.ts","../src/app/routers/apps.ts","../src/app/routers/audit.ts","../src/app/routers/auth.ts","../src/app/routers/bug-report.ts","../src/app/routers/build-export.ts","../src/app/routers/capabilities.ts","../src/app/routers/config.ts","../src/app/routers/control.ts","../src/app/routers/dashboard.ts","../src/app/routers/grid.ts","../src/app/routers/interceptor.ts","../src/app/routers/ports.ts","../src/app/routers/processes.ts","../src/app/routers/profile.ts","../src/app/routers/profileIdentity.ts","../src/app/routers/projects.ts","../src/app/routers/recordings.ts","../src/app/routers/reservation.ts","../src/app/routers/sdk-leases.ts","../src/app/routers/sdk-version.ts","../src/app/routers/streamType.ts","../src/app/routers/teams.ts","../src/app/routers/users.ts","../src/app/routers/webhook.ts","../src/app/ws/h264StreamWs.ts","../src/commands/handle.ts","../src/commands/index.ts","../src/dashboard/asset-manager.ts","../src/dashboard/commands.ts","../src/dashboard/event-manager.ts","../src/dashboard/services/app-service.ts","../src/dashboard/services/failure-analysis-service.ts","../src/dashboard/services/session-service.ts","../src/data-service/CircuitBreaker.ts","../src/data-service/config-service.ts","../src/data-service/db.ts","../src/data-service/device-service.ts","../src/data-service/device-store.interface.ts","../src/data-service/device-store.ts","../src/data-service/pending-sessions-service.ts","../src/data-service/pluginArgs.ts","../src/data-service/prisma-service.ts","../src/data-service/prisma-store.ts","../src/data-service/queue-service.ts","../src/data-service/web-config-service.ts","../src/device-managers/AndroidDeviceManager.ts","../src/device-managers/ChromeDriverManager.ts","../src/device-managers/HealthMonitorService.ts","../src/device-managers/IOSDeviceManager.ts","../src/device-managers/NodeDevices.ts","../src/device-managers/iOSTracker.ts","../src/device-managers/index.ts","../src/device-managers/android/AndroidH264StreamService.ts","../src/device-managers/android/AndroidStreamService.ts","../src/device-managers/android/DeviceLockManager.ts","../src/device-managers/android/H264Multiplexer.ts","../src/device-managers/android/ScrcpyServerSession.ts","../src/device-managers/android/h264NalParser.ts","../src/device-managers/android/scrcpyVersion.ts","../src/device-managers/cloud/CapabilityManager.ts","../src/device-managers/cloud/Devices.ts","../src/device-managers/ios/IOSDiscoveryService.ts","../src/device-managers/ios/IOSStreamService.ts","../src/device-managers/ios/WDAClient.ts","../src/device-managers/ios/iosBattery.ts","../src/device-managers/ios/iosStreamDiagnostics.ts","../src/enums/Capabilities.ts","../src/enums/Cloud.ts","../src/enums/Platform.ts","../src/enums/SessionType.ts","../src/enums/SocketEvents.ts","../src/generated/client/default.d.ts","../src/generated/client/default.js","../src/generated/client/edge.d.ts","../src/generated/client/edge.js","../src/generated/client/index-browser.js","../src/generated/client/index.d.ts","../src/generated/client/index.js","../src/generated/client/wasm.d.ts","../src/generated/client/wasm.js","../src/generated/client/runtime/edge-esm.js","../src/generated/client/runtime/edge.js","../src/generated/client/runtime/index-browser.d.ts","../src/generated/client/runtime/index-browser.js","../src/generated/client/runtime/library.d.ts","../src/generated/client/runtime/library.js","../src/generated/client/runtime/react-native.js","../src/generated/client/runtime/wasm.js","../src/helpers/UniversalMjpegProxy.ts","../src/helpers/index.ts","../src/interceptors/CommandInterceptor.ts","../src/interfaces/ICloudManager.ts","../src/interfaces/IDevice.ts","../src/interfaces/IDeviceFilterOptions.ts","../src/interfaces/IDeviceManager.ts","../src/interfaces/IOptions.ts","../src/interfaces/IPluginArgs.ts","../src/interfaces/ISessionCapability.ts","../src/logging/sessionContext.ts","../src/middleware/authMiddleware.ts","../src/middleware/csrfMiddleware.ts","../src/middleware/leaseTokenMiddleware.ts","../src/middleware/loginRateLimiter.ts","../src/middleware/rateLimitMiddleware.ts","../src/middleware/roleGuard.ts","../src/middleware/scopeGuard.ts","../src/profiling/AndroidAppProfiler.ts","../src/proxy/wd-command-proxy.ts","../src/scripts/generate-database-migration.ts","../src/scripts/initialize-database.ts","../src/scripts/install-go-ios.ts","../src/scripts/prepare-prisma.ts","../src/scripts/run-migrations.ts","../src/services/AICommandService.ts","../src/services/AIService.ts","../src/services/ApiKeyService.ts","../src/services/CircuitBreaker.ts","../src/services/CleanupService.ts","../src/services/DeviceReconciler.ts","../src/services/EmailService.ts","../src/services/EventBus.ts","../src/services/EventLogService.ts","../src/services/InspectorService.ts","../src/services/InterceptorService.ts","../src/services/MetricsService.ts","../src/services/NetworkConditioningService.ts","../src/services/NotificationService.ts","../src/services/OrphanSweeper.ts","../src/services/PasswordResetService.ts","../src/services/PortAllocator.ts","../src/services/ProcessMetricsService.ts","../src/services/ProcessRegistry.ts","../src/services/RequestLogService.ts","../src/services/ResourceIsolationService.ts","../src/services/SecurityService.ts","../src/services/SelectorStateService.ts","../src/services/SelectorVerificationJob.ts","../src/services/ServerManager.ts","../src/services/SessionHeartbeatService.ts","../src/services/SessionLifecycleService.ts","../src/services/ShutdownCoordinator.ts","../src/services/SocketClient.ts","../src/services/SocketServer.ts","../src/services/TeamService.ts","../src/services/TracingService.ts","../src/services/UserService.ts","../src/services/UserSessionService.ts","../src/services/VideoPipelineService.ts","../src/services/sessionTokenGate.ts","../src/services/artifacts/ArtifactStore.ts","../src/services/autowait/AutowaitService.ts","../src/services/autowait/waitFor.ts","../src/services/bug-report/BugReportService.ts","../src/services/bug-report/archive.ts","../src/services/bug-report/har-collector.ts","../src/services/bug-report/manifest.ts","../src/services/bug-report/readme.ts","../src/services/bug-report/types.ts","../src/services/bug-report/video-slice.ts","../src/services/bug-report/window.ts","../src/services/healing/FuzzyXmlHealingProvider.ts","../src/services/healing/HealEtalonService.ts","../src/services/healing/HealedLocatorGenerator.ts","../src/services/healing/HealingMetrics.ts","../src/services/healing/HealingOrchestrator.ts","../src/services/healing/LlmHealingProvider.ts","../src/services/healing/OcrHealingProvider.ts","../src/services/healing/ResilioTreeHealingProvider.ts","../src/services/healing/VisualAiHealingProvider.ts","../src/services/healing/types.ts","../src/services/identity/bootstrap.ts","../src/services/identity/sessionCleanupCron.ts","../src/services/identity/userAuthorization.ts","../src/services/interceptor/AndroidProxyAdapter.ts","../src/services/interceptor/CertManager.ts","../src/services/interceptor/HarBuilder.ts","../src/services/interceptor/HostFilter.ts","../src/services/interceptor/MitmProxyHost.ts","../src/services/interceptor/MockEngine.ts","../src/services/interceptor/RequestBuffer.ts","../src/services/interceptor/SessionArchive.ts","../src/services/interceptor/types.ts","../src/services/lease/LeaseOrphanSweeper.ts","../src/services/lease/LeaseService.ts","../src/services/lease/buildCapabilityBag.ts","../src/services/lease/leaseToken.ts","../src/services/omni-vision/OmniVisionService.ts","../src/services/omni-vision/VisionAssertionService.ts","../src/services/ports/PortAllocatorClient.ts","../src/services/ports/PortAllocatorService.ts","../src/services/recording/RecordingOrchestrator.ts","../src/services/recording/annotation-render.ts","../src/services/recording/busy-precheck.ts","../src/services/recording/concurrency-gate.ts","../src/services/recording/manualLock.ts","../src/services/recording/proof-bundle.ts","../src/services/recording/recording-store.ts","../src/services/telemetry/attributes.ts","../src/services/token/JwtKeyService.ts","../src/services/token/StreamTicketService.ts","../src/services/token/mcpScopes.ts","../src/sessions/CloudSession.ts","../src/sessions/LocalSession.ts","../src/sessions/RemoteSession.ts","../src/sessions/SessionManager.ts","../src/sessions/XenonSession.ts","../src/types/CLIArgs.ts","../src/types/CloudArgs.ts","../src/types/CloudSchema.ts","../src/types/DeviceType.ts","../src/types/DeviceUpdate.ts","../src/types/IOSDevice.ts","../src/types/Platform.ts","../src/types/SessionStatus.ts","../src/types/express.d.ts","../src/types/healing.d.ts","../src/types/identity.ts","../src/validators/CapabilityValidator.ts","../test/e2e/e2ehelper.ts","../test/e2e/plugin-harness.ts","../test/e2e/plugin.spec.ts","../test/e2e/startup-verification.spec.ts","../test/e2e/telemetry_verification.spec.ts","../test/e2e/video-recording-test.spec.js","../test/e2e/android/conf.spec.js","../test/e2e/android/conf2.spec.js","../test/e2e/android/conf3.spec.js","../test/e2e/hubnode/forward-request.spec.ts","../test/e2e/hubnode/hubnode.spec.ts","../test/e2e/ios/conf1.spec.js","../test/e2e/ios/conf2.spec.js","../test/helpers/seedUser.ts","../test/helpers/test-container.ts","../test/integration/androidDevices.spec.ts","../test/integration/audit-ingest.spec.ts","../test/integration/auth-flow.spec.ts","../test/integration/auth-rate-limit.spec.ts","../test/integration/bug-report-route.spec.ts","../test/integration/cliArgs.js","../test/integration/cross-workflow.spec.ts","../test/integration/forgot-password-rate-limit.spec.ts","../test/integration/forgot-password.spec.ts","../test/integration/healing-events-sessionfilter.spec.ts","../test/integration/legacy-deprecation-headers.spec.ts","../test/integration/migration.spec.ts","../test/integration/node-pair-auth-rest.spec.ts","../test/integration/ports-router.spec.ts","../test/integration/profile-tokens.spec.ts","../test/integration/recordings-router.spec.ts","../test/integration/reset-revokes-sessions.spec.ts","../test/integration/role-matrix.spec.ts","../test/integration/sdk-leases-router.spec.ts","../test/integration/sdk-version.spec.ts","../test/integration/selector-health-endpoint.spec.ts","../test/integration/selector-state-flow.spec.ts","../test/integration/team-membership-migration.spec.ts","../test/integration/team-visibility-dashboard.spec.ts","../test/integration/team-visibility-grid.spec.ts","../test/integration/team-visibility-recordings.spec.ts","../test/integration/users-flow.spec.ts","../test/integration/users-lockout.spec.ts","../test/integration/ios/01iOSSimulator.spec.ts","../test/integration/ios/02iOSDevices.spec.ts","../test/unit/AndroidDeviceManager.spec.ts","../test/unit/ApiKeyService.test.ts","../test/unit/ArtifactStore.spec.ts","../test/unit/AutowaitService.spec.ts","../test/unit/CapabilitiesRouter.spec.ts","../test/unit/ChromeDriverManager.spec.js","../test/unit/CleanupService.spec.ts","../test/unit/CommandInterceptor.autowait.spec.ts","../test/unit/DeviceModel.spec.ts","../test/unit/EmailService.test.ts","../test/unit/EventLogService.spec.ts","../test/unit/FuzzyXmlHealingProvider.test.ts","../test/unit/GetAdbOriginal.js","../test/unit/HealingCascade.test.ts","../test/unit/IOSDeviceManager.spec.js","../test/unit/JwtKeyService.spec.ts","../test/unit/McpScopes.spec.ts","../test/unit/OrphanSweeper.test.ts","../test/unit/PasswordResetService.test.ts","../test/unit/PortAllocator.test.ts","../test/unit/ProcessRegistry.test.ts","../test/unit/ProjectsRouter.spec.ts","../test/unit/RemoteIOs.spec.js","../test/unit/ResilioTreeHealingProvider.test.ts","../test/unit/SessionHeartbeatWrite.test.ts","../test/unit/SessionLifecycleCaps.spec.ts","../test/unit/SessionTokenGate.spec.ts","../test/unit/StreamTicketService.spec.ts","../test/unit/TeamService.test.ts","../test/unit/TokenEndpoint.spec.ts","../test/unit/UserService.test.ts","../test/unit/UserSessionService.test.ts","../test/unit/XenonCapabilityManager.spec.ts","../test/unit/aggregate-hotspots-tuple-key.spec.ts","../test/unit/android-h264-config.spec.ts","../test/unit/android-h264-service.spec.ts","../test/unit/android-stream-adb-path.spec.ts","../test/unit/android-stream-encoder.spec.ts","../test/unit/android-stream-fast-start.spec.ts","../test/unit/android-stream-release.spec.ts","../test/unit/auth-me-auth-disabled.test.ts","../test/unit/authMiddleware.bearer.spec.ts","../test/unit/authMiddleware.mcpAudience.spec.ts","../test/unit/authMiddleware.streamTicket.spec.ts","../test/unit/authMiddleware.test.ts","../test/unit/bootstrap-identity.test.ts","../test/unit/build-export.spec.ts","../test/unit/busy-precheck.spec.ts","../test/unit/commands.spec.js","../test/unit/concurrency-gate.spec.ts","../test/unit/config.spec.js","../test/unit/device-service.spec.ts","../test/unit/device-utils.spec.ts","../test/unit/event-manager-regression-hook.spec.ts","../test/unit/extract-access-key-token-pair.test.ts","../test/unit/findAndLockDevice-leasemerge.spec.ts","../test/unit/h264-multiplexer.spec.ts","../test/unit/h264-nal-parser.spec.ts","../test/unit/h264-ws-frame.spec.ts","../test/unit/healing-instrumentation.spec.ts","../test/unit/healing-state-endpoints.spec.ts","../test/unit/healing-tier-gate.spec.ts","../test/unit/helpers.spec.ts","../test/unit/iosBattery.spec.ts","../test/unit/iosStreamDiagnostics.spec.ts","../test/unit/loginRateLimiter.test.ts","../test/unit/manualLock.test.ts","../test/unit/omni-vision.spec.ts","../test/unit/plugin.spec.ts","../test/unit/prisma.spec.ts","../test/unit/profile-router.test.ts","../test/unit/profileIdentity.spec.ts","../test/unit/proof-bundle.spec.ts","../test/unit/rateLimitMiddleware.test.ts","../test/unit/recording-instrumentation.spec.ts","../test/unit/recording-orchestrator.spec.ts","../test/unit/recording-store.spec.ts","../test/unit/roleGuard.test.ts","../test/unit/run-migrations-fail-fast.test.ts","../test/unit/run-migrations.test.ts","../test/unit/scrcpy-server-session.spec.ts","../test/unit/scrcpy-version.spec.ts","../test/unit/selector-state-service.spec.ts","../test/unit/selector-verification-job.spec.ts","../test/unit/socketServer.bearer.spec.ts","../test/unit/stream-status-type.spec.ts","../test/unit/team-filter.test.ts","../test/unit/tracing-service-init.spec.ts","../test/unit/userAuthorization.test.ts","../test/unit/users-router.test.ts","../test/unit/video-pipeline-args.spec.ts","../test/unit/video-pipeline-outputpath.spec.ts","../test/unit/bug-report/archive.spec.ts","../test/unit/bug-report/har-collector.spec.ts","../test/unit/bug-report/manifest.spec.ts","../test/unit/bug-report/readme.spec.ts","../test/unit/bug-report/route.spec.ts","../test/unit/bug-report/service.spec.ts","../test/unit/bug-report/video-slice.spec.ts","../test/unit/bug-report/window.spec.ts","../test/unit/fixtures/device.config.js","../test/unit/fixtures/devices.js","../test/unit/interceptor/AndroidProxyAdapter.spec.ts","../test/unit/interceptor/CertManager.spec.ts","../test/unit/interceptor/HarBuilder.spec.ts","../test/unit/interceptor/HostFilter.spec.ts","../test/unit/interceptor/MitmProxyHost.spec.ts","../test/unit/interceptor/MockEngine.spec.ts","../test/unit/interceptor/RequestBuffer.spec.ts","../test/unit/interceptor/SessionArchive.spec.ts","../test/unit/lease/LeaseOrphanSweeper.spec.ts","../test/unit/lease/LeaseService.spec.ts","../test/unit/lease/buildCapabilityBag.spec.ts","../test/unit/lease/leaseToken.spec.ts","../test/unit/lease/nodePairAuth.spec.ts","../test/unit/ports/PortAllocatorService.spec.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenon-device-management/xenon",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "Xenon - Intelligent Mobile Infrastructure. A self-healing device orchestration platform for Appium.",
5
5
  "main": "./lib/src/index.js",
6
6
  "exports": {
@@ -12,7 +12,7 @@
12
12
  "postinstall": "node scripts/generate-prisma.js",
13
13
  "build": "node scripts/generate-prisma.js && tsc -b && npm run build:copy",
14
14
  "build:all": "npm run build:xenon && npm run build",
15
- "build:copy": "rm -rf lib/public && mkdir -p lib/src && cp -R src/public lib/ && cp -R src/generated lib/src/ && cp schema.json lib/",
15
+ "build:copy": "rm -rf lib/public && mkdir -p lib/src && cp -R src/public lib/ && cp -R src/generated lib/src/ && cp schema.json lib/ && mkdir -p lib/src/device-managers/android/vendor && cp src/device-managers/android/vendor/*.jar lib/src/device-managers/android/vendor/",
16
16
  "build:schema": "node scripts/generate-types-from-schema.js",
17
17
  "build:xenon": "sh scripts/build-xenon.sh",
18
18
  "dev": "npm run db:migrate && npm run build && npm run plugin:reinstall && npm run server",
package/schema.json CHANGED
@@ -475,9 +475,16 @@
475
475
  "description": "Live-streaming options.",
476
476
  "properties": {
477
477
  "androidH264": {
478
- "type": "boolean",
479
478
  "default": false,
480
- "description": "Use scrcpy H.264 for Android live preview (WebCodecs in the browser) instead of the MJPEG screencap loop. Falls back to MJPEG when unsupported."
479
+ "description": "Android live preview: false/omitted = MJPEG; true = H.264 via scrcpy; { \"source\": \"scrcpy\" | \"screenrecord\" } to pick the capture source explicitly. Falls back to MJPEG when unsupported.",
480
+ "oneOf": [
481
+ { "type": "boolean" },
482
+ {
483
+ "type": "object",
484
+ "additionalProperties": false,
485
+ "properties": { "source": { "type": "string", "enum": ["scrcpy", "screenrecord"] } }
486
+ }
487
+ ]
481
488
  }
482
489
  }
483
490
  }