@xenon-device-management/xenon 1.9.2 → 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 +2 -2
- package/lib/schema.json +9 -2
- package/lib/src/app/routers/androidH264Config.js +16 -0
- package/lib/src/app/routers/control.js +5 -3
- package/lib/src/device-managers/android/AndroidH264StreamService.js +53 -3
- package/lib/src/device-managers/android/ScrcpyServerSession.js +269 -0
- package/lib/src/device-managers/android/scrcpyVersion.js +22 -0
- package/lib/src/device-managers/android/vendor/scrcpy-server-3.3.4.jar +0 -0
- package/lib/src/services/ServerManager.js +3 -0
- package/lib/test/unit/android-h264-config.spec.js +20 -0
- package/lib/test/unit/android-h264-service.spec.js +70 -0
- package/lib/test/unit/scrcpy-server-session.spec.js +59 -0
- package/lib/test/unit/scrcpy-version.spec.js +16 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/schema.json +9 -2
package/lib/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenon-device-management/xenon",
|
|
3
|
-
"version": "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": "
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
Binary file
|
|
@@ -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
|
+
});
|
package/lib/tsconfig.tsbuildinfo
CHANGED
|
@@ -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.
|
|
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": "
|
|
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
|
}
|