@xenon-device-management/xenon 1.2.0 → 1.3.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/README.md +74 -0
- package/lib/package.json +1 -1
- package/lib/public/assets/{Layouts-D0WSzKOh.js → Layouts-D6IPfwoe.js} +1 -1
- package/lib/public/assets/{ai-settings-DQWDdNd7.js → ai-settings-CflyFKan.js} +1 -1
- package/lib/public/assets/{apps-1sLWHOGO.js → apps-Da4dvQ1J.js} +1 -1
- package/lib/public/assets/{badge-BiR1gmMm.js → badge-BNR9umdu.js} +1 -1
- package/lib/public/assets/{button-BVazt4Z1.js → button-hZFV1ypT.js} +1 -1
- package/lib/public/assets/{calendar-yMyP2_Nc.js → calendar-fehdBtun.js} +1 -1
- package/lib/public/assets/{clock-CsVplnJ2.js → clock-DrpxSvCL.js} +1 -1
- package/lib/public/assets/{cpu-DNC8n7kK.js → cpu-tuyMVZ4I.js} +1 -1
- package/lib/public/assets/{device-explorer-DFu8Gxj4.js → device-explorer-DOfRH3zm.js} +1 -1
- package/lib/public/assets/{index-S71J2rWg.js → index-BaTiUCeH.js} +18 -18
- package/lib/public/assets/{lock-BstCxnX6.js → lock-C6CoqSr2.js} +1 -1
- package/lib/public/assets/{maintenance-settings-BwfG9cu2.js → maintenance-settings-CM2oC7-i.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-CSn_Wnc9.js → mouse-pointer-2-CXdnjXIg.js} +1 -1
- package/lib/public/assets/{plus-DfjM7G6e.js → plus-B4B1Hukt.js} +1 -1
- package/lib/public/assets/{session-dashboard-C6ek4z65.js → session-dashboard-B5OPMTz5.js} +1 -1
- package/lib/public/assets/{settings-BDYP8ULf.js → settings-BTHP7fj3.js} +1 -1
- package/lib/public/assets/{trash-2-CZWUMK5b.js → trash-2-NJMZJ2Ol.js} +1 -1
- package/lib/public/assets/{useSocket-CliVeWS3.js → useSocket-Ct2wo7P2.js} +2 -2
- package/lib/public/assets/{webhook-settings-tPiwWf8y.js → webhook-settings-Cz35-QJ7.js} +1 -1
- package/lib/public/assets/{zap-ZrK5B58i.js → zap-CssSMAN5.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +85 -38
- package/lib/src/InternalHttpClient.js +69 -14
- package/lib/src/app/index.js +92 -24
- package/lib/src/app/routers/apikeys.js +33 -0
- package/lib/src/app/routers/apps.js +4 -0
- package/lib/src/app/routers/auth.js +36 -0
- package/lib/src/app/routers/config.js +4 -0
- package/lib/src/app/routers/control.js +61 -10
- package/lib/src/app/routers/dashboard.js +5 -6
- package/lib/src/app/routers/grid.js +30 -12
- package/lib/src/app/routers/processes.js +24 -0
- package/lib/src/app/routers/reservation.js +15 -0
- package/lib/src/app/routers/webhook.js +6 -3
- package/lib/src/auth/nodeSecret.js +33 -0
- package/lib/src/config.js +5 -0
- package/lib/src/data-service/prisma-store.js +17 -1
- package/lib/src/device-managers/AndroidDeviceManager.js +2 -2
- package/lib/src/device-managers/NodeDevices.js +8 -1
- package/lib/src/device-managers/ios/IOSDiscoveryService.js +7 -4
- package/lib/src/device-managers/ios/IOSStreamService.js +7 -0
- package/lib/src/device-managers/ios/WDAClient.js +2 -0
- package/lib/src/device-utils.js +29 -4
- package/lib/src/generated/client/edge.js +2 -2
- package/lib/src/generated/client/index.js +2 -2
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +3 -0
- package/lib/src/helpers/UniversalMjpegProxy.js +23 -0
- package/lib/src/index.js +10 -2
- package/lib/src/interceptors/CommandInterceptor.js +29 -0
- package/lib/src/interfaces/IPluginArgs.js +0 -1
- package/lib/src/logger.js +30 -2
- package/lib/src/logging/sessionContext.js +28 -0
- package/lib/src/middleware/apiKeyMiddleware.js +49 -0
- package/lib/src/middleware/csrfMiddleware.js +73 -0
- package/lib/src/middleware/nodeSecretMiddleware.js +38 -0
- package/lib/src/middleware/rateLimitMiddleware.js +68 -0
- package/lib/src/middleware/scopeGuard.js +41 -0
- package/lib/src/plugin.js +1 -1
- package/lib/src/services/AIService.js +43 -8
- package/lib/src/services/ApiKeyService.js +102 -0
- package/lib/src/services/CircuitBreaker.js +158 -0
- package/lib/src/services/CleanupService.js +137 -39
- package/lib/src/services/DeviceReconciler.js +102 -0
- package/lib/src/services/MetricsService.js +78 -0
- package/lib/src/services/PortAllocator.js +13 -0
- package/lib/src/services/ProcessMetricsService.js +99 -0
- package/lib/src/services/ProcessRegistry.js +123 -0
- package/lib/src/services/ServerManager.js +14 -2
- package/lib/src/services/SessionLifecycleService.js +80 -23
- package/lib/src/services/ShutdownCoordinator.js +89 -0
- package/lib/src/services/SocketClient.js +11 -0
- package/lib/src/services/SocketServer.js +109 -6
- package/lib/src/services/VideoPipelineService.js +2 -0
- package/lib/src/services/healing/HealingMetrics.js +63 -0
- package/lib/src/services/healing/HealingOrchestrator.js +32 -4
- package/lib/src/services/healing/OcrHealingProvider.js +7 -0
- package/lib/test/unit/ApiKeyService.test.js +101 -0
- package/lib/test/unit/PortAllocator.test.js +14 -0
- package/lib/test/unit/ProcessRegistry.test.js +70 -0
- package/lib/test/unit/apiKeyMiddleware.test.js +58 -0
- package/lib/test/unit/nodeSecretMiddleware.test.js +38 -0
- package/lib/test/unit/rateLimitMiddleware.test.js +37 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/prisma/migrations/20260423081701_add_session_indexes/migration.sql +8 -0
- package/prisma/schema.prisma +3 -0
- package/schema.json +85 -38
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const chai_1 = require("chai");
|
|
13
|
+
const ProcessRegistry_1 = require("../../src/services/ProcessRegistry");
|
|
14
|
+
const events_1 = require("events");
|
|
15
|
+
class FakeChild extends events_1.EventEmitter {
|
|
16
|
+
constructor(pid) {
|
|
17
|
+
super();
|
|
18
|
+
this.killed = false;
|
|
19
|
+
this.pid = pid;
|
|
20
|
+
}
|
|
21
|
+
kill(signal) {
|
|
22
|
+
this.killed = signal === 'SIGKILL' ? true : this.killed;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
describe('ProcessRegistry', () => {
|
|
27
|
+
it('tracks and untracks processes', () => {
|
|
28
|
+
const reg = new ProcessRegistry_1.ProcessRegistry();
|
|
29
|
+
const child = new FakeChild(12345);
|
|
30
|
+
const id = reg.track({ kind: 'wda', udid: 'u1', process: child });
|
|
31
|
+
(0, chai_1.expect)(reg.snapshot()).to.have.length(1);
|
|
32
|
+
reg.untrack(id);
|
|
33
|
+
(0, chai_1.expect)(reg.snapshot()).to.have.length(0);
|
|
34
|
+
});
|
|
35
|
+
it('sends SIGTERM then SIGKILL when a child ignores SIGTERM', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
|
+
const reg = new ProcessRegistry_1.ProcessRegistry();
|
|
37
|
+
const child = new FakeChild(12345);
|
|
38
|
+
const sent = [];
|
|
39
|
+
child.kill = (sig) => {
|
|
40
|
+
sent.push(sig);
|
|
41
|
+
return true;
|
|
42
|
+
};
|
|
43
|
+
const id = reg.track({ kind: 'wda', udid: 'u1', process: child });
|
|
44
|
+
const p = reg.terminate(id, { gracefulMs: 50 });
|
|
45
|
+
yield p;
|
|
46
|
+
(0, chai_1.expect)(sent[0]).to.equal('SIGTERM');
|
|
47
|
+
(0, chai_1.expect)(sent[sent.length - 1]).to.equal('SIGKILL');
|
|
48
|
+
}));
|
|
49
|
+
it('terminateForUdid kills only matching tracked processes', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
const reg = new ProcessRegistry_1.ProcessRegistry();
|
|
51
|
+
const a = new FakeChild(1);
|
|
52
|
+
const b = new FakeChild(2);
|
|
53
|
+
const signalsA = [];
|
|
54
|
+
const signalsB = [];
|
|
55
|
+
a.kill = (s) => {
|
|
56
|
+
signalsA.push(s);
|
|
57
|
+
a.emit('exit', 0);
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
b.kill = (s) => {
|
|
61
|
+
signalsB.push(s);
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
reg.track({ kind: 'wda', udid: 'u1', process: a });
|
|
65
|
+
reg.track({ kind: 'wda', udid: 'u2', process: b });
|
|
66
|
+
yield reg.terminateForUdid('u1', { gracefulMs: 10 });
|
|
67
|
+
(0, chai_1.expect)(signalsA.length).to.be.greaterThan(0);
|
|
68
|
+
(0, chai_1.expect)(signalsB.length).to.equal(0);
|
|
69
|
+
}));
|
|
70
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
require("reflect-metadata");
|
|
16
|
+
const chai_1 = require("chai");
|
|
17
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
18
|
+
const typedi_1 = require("typedi");
|
|
19
|
+
const apiKeyMiddleware_1 = require("../../src/middleware/apiKeyMiddleware");
|
|
20
|
+
const ApiKeyService_1 = require("../../src/services/ApiKeyService");
|
|
21
|
+
function mockReq(headers = {}, query = {}) {
|
|
22
|
+
return { headers, query };
|
|
23
|
+
}
|
|
24
|
+
function mockRes() {
|
|
25
|
+
const json = sinon_1.default.stub();
|
|
26
|
+
const status = sinon_1.default.stub().returnsThis();
|
|
27
|
+
return { status, json, locals: {} };
|
|
28
|
+
}
|
|
29
|
+
describe('apiKeyMiddleware', () => {
|
|
30
|
+
afterEach(() => sinon_1.default.restore());
|
|
31
|
+
it('401 when header missing', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
|
+
const req = mockReq();
|
|
33
|
+
const res = mockRes();
|
|
34
|
+
const next = sinon_1.default.stub();
|
|
35
|
+
yield (0, apiKeyMiddleware_1.apiKeyMiddleware)(req, res, next);
|
|
36
|
+
(0, chai_1.expect)(res.status.calledWith(401)).to.be.true;
|
|
37
|
+
(0, chai_1.expect)(next.called).to.be.false;
|
|
38
|
+
}));
|
|
39
|
+
it('401 when key invalid', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
+
sinon_1.default.stub(typedi_1.Container.get(ApiKeyService_1.ApiKeyService), 'verify').resolves(null);
|
|
41
|
+
const req = mockReq({ 'x-xenon-api-key': 'bad' });
|
|
42
|
+
const res = mockRes();
|
|
43
|
+
const next = sinon_1.default.stub();
|
|
44
|
+
yield (0, apiKeyMiddleware_1.apiKeyMiddleware)(req, res, next);
|
|
45
|
+
(0, chai_1.expect)(res.status.calledWith(401)).to.be.true;
|
|
46
|
+
}));
|
|
47
|
+
it('calls next and attaches apiKey on success', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
48
|
+
sinon_1.default
|
|
49
|
+
.stub(typedi_1.Container.get(ApiKeyService_1.ApiKeyService), 'verify')
|
|
50
|
+
.resolves({ id: 'k1', scopes: 'read', rateLimit: 300 });
|
|
51
|
+
const req = mockReq({ 'x-xenon-api-key': 'good' });
|
|
52
|
+
const res = mockRes();
|
|
53
|
+
const next = sinon_1.default.stub();
|
|
54
|
+
yield (0, apiKeyMiddleware_1.apiKeyMiddleware)(req, res, next);
|
|
55
|
+
(0, chai_1.expect)(next.calledOnce).to.be.true;
|
|
56
|
+
(0, chai_1.expect)(req.apiKey.id).to.equal('k1');
|
|
57
|
+
}));
|
|
58
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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 sinon_1 = __importDefault(require("sinon"));
|
|
8
|
+
const nodeSecretMiddleware_1 = require("../../src/middleware/nodeSecretMiddleware");
|
|
9
|
+
function mockReq(headers = {}) {
|
|
10
|
+
return { headers };
|
|
11
|
+
}
|
|
12
|
+
function mockRes() {
|
|
13
|
+
const json = sinon_1.default.stub();
|
|
14
|
+
const status = sinon_1.default.stub().returnsThis();
|
|
15
|
+
return { status, json };
|
|
16
|
+
}
|
|
17
|
+
describe('nodeSecretMiddleware', () => {
|
|
18
|
+
it('401 on mismatch when secret is configured', () => {
|
|
19
|
+
const mw = (0, nodeSecretMiddleware_1.nodeSecretMiddleware)('expected');
|
|
20
|
+
const req = mockReq({ 'x-xenon-node-secret': 'wrong' });
|
|
21
|
+
const res = mockRes();
|
|
22
|
+
const next = sinon_1.default.stub();
|
|
23
|
+
mw(req, res, next);
|
|
24
|
+
(0, chai_1.expect)(res.status.calledWith(401)).to.be.true;
|
|
25
|
+
});
|
|
26
|
+
it('calls next on match', () => {
|
|
27
|
+
const mw = (0, nodeSecretMiddleware_1.nodeSecretMiddleware)('shared');
|
|
28
|
+
const next = sinon_1.default.stub();
|
|
29
|
+
mw(mockReq({ 'x-xenon-node-secret': 'shared' }), mockRes(), next);
|
|
30
|
+
(0, chai_1.expect)(next.calledOnce).to.be.true;
|
|
31
|
+
});
|
|
32
|
+
it('permits + warns when secret unset', () => {
|
|
33
|
+
const mw = (0, nodeSecretMiddleware_1.nodeSecretMiddleware)(undefined);
|
|
34
|
+
const next = sinon_1.default.stub();
|
|
35
|
+
mw(mockReq(), mockRes(), next);
|
|
36
|
+
(0, chai_1.expect)(next.calledOnce).to.be.true;
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
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 sinon_1 = __importDefault(require("sinon"));
|
|
8
|
+
const rateLimitMiddleware_1 = require("../../src/middleware/rateLimitMiddleware");
|
|
9
|
+
function mockReq(keyId, rateLimit) {
|
|
10
|
+
return { apiKey: { id: keyId, rateLimit } };
|
|
11
|
+
}
|
|
12
|
+
function mockRes() {
|
|
13
|
+
const json = sinon_1.default.stub();
|
|
14
|
+
const status = sinon_1.default.stub().returnsThis();
|
|
15
|
+
const set = sinon_1.default.stub();
|
|
16
|
+
return { status, json, set };
|
|
17
|
+
}
|
|
18
|
+
describe('rateLimitMiddleware', () => {
|
|
19
|
+
beforeEach(() => (0, rateLimitMiddleware_1.__resetBucketsForTests)());
|
|
20
|
+
it('allows traffic within the limit', () => {
|
|
21
|
+
const next = sinon_1.default.stub();
|
|
22
|
+
const mw = (0, rateLimitMiddleware_1.rateLimitMiddleware)();
|
|
23
|
+
for (let i = 0; i < 5; i++)
|
|
24
|
+
mw(mockReq('k1', 60), mockRes(), next);
|
|
25
|
+
(0, chai_1.expect)(next.callCount).to.equal(5);
|
|
26
|
+
});
|
|
27
|
+
it('429 when bucket exhausted', () => {
|
|
28
|
+
const next = sinon_1.default.stub();
|
|
29
|
+
const mw = (0, rateLimitMiddleware_1.rateLimitMiddleware)();
|
|
30
|
+
const req = mockReq('k2', 3);
|
|
31
|
+
const res = mockRes();
|
|
32
|
+
for (let i = 0; i < 3; i++)
|
|
33
|
+
mw(req, res, next);
|
|
34
|
+
mw(req, res, next);
|
|
35
|
+
(0, chai_1.expect)(res.status.calledWith(429)).to.be.true;
|
|
36
|
+
});
|
|
37
|
+
});
|
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/apps.ts","../src/app/routers/config.ts","../src/app/routers/control.ts","../src/app/routers/dashboard.ts","../src/app/routers/grid.ts","../src/app/routers/reservation.ts","../src/app/routers/webhook.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/AndroidStreamService.ts","../src/device-managers/android/DeviceLockManager.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/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/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/CleanupService.ts","../src/services/EventBus.ts","../src/services/InspectorService.ts","../src/services/MetricsService.ts","../src/services/NetworkConditioningService.ts","../src/services/NotificationService.ts","../src/services/OrphanSweeper.ts","../src/services/PortAllocator.ts","../src/services/RequestLogService.ts","../src/services/ResourceIsolationService.ts","../src/services/SecurityService.ts","../src/services/ServerManager.ts","../src/services/SessionHeartbeatService.ts","../src/services/SessionLifecycleService.ts","../src/services/SocketClient.ts","../src/services/SocketServer.ts","../src/services/TracingService.ts","../src/services/VideoPipelineService.ts","../src/services/healing/FuzzyXmlHealingProvider.ts","../src/services/healing/HealEtalonService.ts","../src/services/healing/HealedLocatorGenerator.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/omni-vision/OmniVisionService.ts","../src/services/omni-vision/VisionAssertionService.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/healing.d.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/test-container.ts","../test/integration/androidDevices.spec.ts","../test/integration/cliArgs.js","../test/integration/testHelpers.js","../test/integration/ios/01iOSSimulator.spec.ts","../test/integration/ios/02iOSDevices.spec.ts","../test/unit/AndroidDeviceManager.spec.ts","../test/unit/ChromeDriverManager.spec.js","../test/unit/CleanupService.spec.ts","../test/unit/DeviceModel.spec.ts","../test/unit/FuzzyXmlHealingProvider.test.ts","../test/unit/GetAdbOriginal.js","../test/unit/HealingCascade.test.ts","../test/unit/IOSDeviceManager.spec.js","../test/unit/OrphanSweeper.test.ts","../test/unit/PortAllocator.test.ts","../test/unit/RemoteIOs.spec.js","../test/unit/ResilioTreeHealingProvider.test.ts","../test/unit/SessionHeartbeatWrite.test.ts","../test/unit/commands.spec.js","../test/unit/config.spec.js","../test/unit/device-service.spec.ts","../test/unit/device-utils.spec.ts","../test/unit/helpers.spec.ts","../test/unit/omni-vision.spec.ts","../test/unit/plugin.spec.ts","../test/unit/prisma.spec.ts","../test/unit/fixtures/device.config.js","../test/unit/fixtures/devices.js"],"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/apikeys.ts","../src/app/routers/apps.ts","../src/app/routers/auth.ts","../src/app/routers/config.ts","../src/app/routers/control.ts","../src/app/routers/dashboard.ts","../src/app/routers/grid.ts","../src/app/routers/processes.ts","../src/app/routers/reservation.ts","../src/app/routers/webhook.ts","../src/auth/nodeSecret.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/AndroidStreamService.ts","../src/device-managers/android/DeviceLockManager.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/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/apiKeyMiddleware.ts","../src/middleware/csrfMiddleware.ts","../src/middleware/nodeSecretMiddleware.ts","../src/middleware/rateLimitMiddleware.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/EventBus.ts","../src/services/InspectorService.ts","../src/services/MetricsService.ts","../src/services/NetworkConditioningService.ts","../src/services/NotificationService.ts","../src/services/OrphanSweeper.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/ServerManager.ts","../src/services/SessionHeartbeatService.ts","../src/services/SessionLifecycleService.ts","../src/services/ShutdownCoordinator.ts","../src/services/SocketClient.ts","../src/services/SocketServer.ts","../src/services/TracingService.ts","../src/services/VideoPipelineService.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/omni-vision/OmniVisionService.ts","../src/services/omni-vision/VisionAssertionService.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/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/test-container.ts","../test/integration/androidDevices.spec.ts","../test/integration/cliArgs.js","../test/integration/testHelpers.js","../test/integration/ios/01iOSSimulator.spec.ts","../test/integration/ios/02iOSDevices.spec.ts","../test/unit/AndroidDeviceManager.spec.ts","../test/unit/ApiKeyService.test.ts","../test/unit/ChromeDriverManager.spec.js","../test/unit/CleanupService.spec.ts","../test/unit/DeviceModel.spec.ts","../test/unit/FuzzyXmlHealingProvider.test.ts","../test/unit/GetAdbOriginal.js","../test/unit/HealingCascade.test.ts","../test/unit/IOSDeviceManager.spec.js","../test/unit/OrphanSweeper.test.ts","../test/unit/PortAllocator.test.ts","../test/unit/ProcessRegistry.test.ts","../test/unit/RemoteIOs.spec.js","../test/unit/ResilioTreeHealingProvider.test.ts","../test/unit/SessionHeartbeatWrite.test.ts","../test/unit/apiKeyMiddleware.test.ts","../test/unit/commands.spec.js","../test/unit/config.spec.js","../test/unit/device-service.spec.ts","../test/unit/device-utils.spec.ts","../test/unit/helpers.spec.ts","../test/unit/nodeSecretMiddleware.test.ts","../test/unit/omni-vision.spec.ts","../test/unit/plugin.spec.ts","../test/unit/prisma.spec.ts","../test/unit/rateLimitMiddleware.test.ts","../test/unit/fixtures/device.config.js","../test/unit/fixtures/devices.js"],"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.3.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": {
|
|
@@ -214,4 +214,4 @@
|
|
|
214
214
|
"typedoc": {
|
|
215
215
|
"entryPoint": "lib/src/index.js"
|
|
216
216
|
}
|
|
217
|
-
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- CreateIndex
|
|
2
|
+
CREATE INDEX "Session_build_id_idx" ON "Session"("build_id");
|
|
3
|
+
|
|
4
|
+
-- CreateIndex
|
|
5
|
+
CREATE INDEX "Session_device_udid_idx" ON "Session"("device_udid");
|
|
6
|
+
|
|
7
|
+
-- CreateIndex
|
|
8
|
+
CREATE INDEX "Session_device_platform_status_idx" ON "Session"("device_platform", "status");
|
package/prisma/schema.prisma
CHANGED
package/schema.json
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"android",
|
|
11
11
|
"both"
|
|
12
12
|
],
|
|
13
|
-
"default": "both"
|
|
13
|
+
"default": "both",
|
|
14
|
+
"description": "Which mobile platform(s) Xenon should discover and orchestrate."
|
|
14
15
|
},
|
|
15
16
|
"androidDeviceType": {
|
|
16
17
|
"title": "DeviceTypeToInclude",
|
|
@@ -20,14 +21,16 @@
|
|
|
20
21
|
"real",
|
|
21
22
|
"simulated"
|
|
22
23
|
],
|
|
23
|
-
"default": "both"
|
|
24
|
+
"default": "both",
|
|
25
|
+
"description": "Which Android device kinds to include: physical devices, emulators, or both."
|
|
24
26
|
},
|
|
25
27
|
"simulators": {
|
|
26
28
|
"type": "array",
|
|
27
29
|
"items": {
|
|
28
30
|
"$ref": "#/definitions/SimulatorConfig"
|
|
29
31
|
},
|
|
30
|
-
"default": []
|
|
32
|
+
"default": [],
|
|
33
|
+
"description": "Allow-list of iOS simulators (by name + sdk) to expose. Empty array means expose all discoverable simulators."
|
|
31
34
|
},
|
|
32
35
|
"iosDeviceType": {
|
|
33
36
|
"title": "DeviceTypeToInclude1",
|
|
@@ -37,28 +40,34 @@
|
|
|
37
40
|
"real",
|
|
38
41
|
"simulated"
|
|
39
42
|
],
|
|
40
|
-
"default": "both"
|
|
43
|
+
"default": "both",
|
|
44
|
+
"description": "Which iOS device kinds to include: physical devices, simulators, or both."
|
|
41
45
|
},
|
|
42
46
|
"hub": {
|
|
43
|
-
"type": "string"
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "URL of the Xenon hub this instance should register with as a node (e.g. http://hub.example:4723). Omit to run as a standalone hub."
|
|
44
49
|
},
|
|
45
50
|
"remoteMachineProxyIP": {
|
|
46
|
-
"type": "string"
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Public host/URL that clients should use to reach this node when running behind a reverse proxy or NAT."
|
|
47
53
|
},
|
|
48
54
|
"adbRemote": {
|
|
49
55
|
"type": "array",
|
|
50
56
|
"items": {
|
|
51
57
|
"type": "string"
|
|
52
58
|
},
|
|
53
|
-
"default": []
|
|
59
|
+
"default": [],
|
|
60
|
+
"description": "List of remote ADB hosts in host:port form (e.g. '192.168.1.50:5037') to discover Android devices on other machines."
|
|
54
61
|
},
|
|
55
62
|
"skipChromeDownload": {
|
|
56
63
|
"type": "boolean",
|
|
57
|
-
"default": true
|
|
64
|
+
"default": true,
|
|
65
|
+
"description": "Skip the automatic ChromeDriver download performed by uiautomator2. Leave true unless you specifically need Xenon to manage Chrome binaries."
|
|
58
66
|
},
|
|
59
67
|
"maxSessions": {
|
|
60
68
|
"type": "number",
|
|
61
|
-
"default": 8
|
|
69
|
+
"default": 8,
|
|
70
|
+
"description": "Maximum number of Appium sessions this node will run concurrently. Additional requests queue until a slot frees."
|
|
62
71
|
},
|
|
63
72
|
"cloud": {
|
|
64
73
|
"type": "object",
|
|
@@ -77,7 +86,8 @@
|
|
|
77
86
|
"items": {
|
|
78
87
|
"$ref": "#/definitions/EmulatorConfig"
|
|
79
88
|
},
|
|
80
|
-
"default": []
|
|
89
|
+
"default": [],
|
|
90
|
+
"description": "Allow-list of Android emulator AVDs to expose. Empty array means expose all discoverable emulators."
|
|
81
91
|
},
|
|
82
92
|
"proxy": {
|
|
83
93
|
"type": "object",
|
|
@@ -85,64 +95,79 @@
|
|
|
85
95
|
},
|
|
86
96
|
"deviceAvailabilityTimeoutMs": {
|
|
87
97
|
"type": "number",
|
|
88
|
-
"default": 300000
|
|
98
|
+
"default": 300000,
|
|
99
|
+
"description": "How long (ms) a session request waits for a free device before failing."
|
|
89
100
|
},
|
|
90
101
|
"deviceAvailabilityQueryIntervalMs": {
|
|
91
102
|
"type": "number",
|
|
92
|
-
"default": 10000
|
|
103
|
+
"default": 10000,
|
|
104
|
+
"description": "How often (ms) the session queue polls for a free device while waiting."
|
|
93
105
|
},
|
|
94
106
|
"sendNodeDevicesToHubIntervalMs": {
|
|
95
107
|
"type": "number",
|
|
96
|
-
"default": 30000
|
|
108
|
+
"default": 30000,
|
|
109
|
+
"description": "How often (ms) a node pushes its current device list to the hub. Only used when `hub` is set."
|
|
97
110
|
},
|
|
98
111
|
"checkStaleDevicesIntervalMs": {
|
|
99
112
|
"type": "number",
|
|
100
|
-
"default": 30000
|
|
113
|
+
"default": 30000,
|
|
114
|
+
"description": "How often (ms) the hub prunes devices from nodes that have stopped heartbeating."
|
|
101
115
|
},
|
|
102
116
|
"checkBlockedDevicesIntervalMs": {
|
|
103
117
|
"type": "number",
|
|
104
|
-
"default": 30000
|
|
118
|
+
"default": 30000,
|
|
119
|
+
"description": "How often (ms) to re-evaluate manually-blocked devices and the session reconciler that frees orphaned busy devices."
|
|
105
120
|
},
|
|
106
121
|
"newCommandTimeoutSec": {
|
|
107
122
|
"type": "number",
|
|
108
|
-
"default": 60
|
|
123
|
+
"default": 60,
|
|
124
|
+
"description": "Default Appium newCommandTimeout (seconds) applied when a client does not send one. Also drives the reconciler that releases devices idle past this threshold."
|
|
109
125
|
},
|
|
110
126
|
"bindHostOrIp": {
|
|
111
127
|
"type": "string",
|
|
112
|
-
"default": "127.0.0.1"
|
|
128
|
+
"default": "127.0.0.1",
|
|
129
|
+
"description": "Host/IP the Xenon REST and WebSocket server binds to. Set to 0.0.0.0 to expose on all interfaces."
|
|
113
130
|
},
|
|
114
131
|
"enableDashboard": {
|
|
115
132
|
"type": "boolean",
|
|
116
|
-
"default": false
|
|
133
|
+
"default": false,
|
|
134
|
+
"description": "Serve the React dashboard at /xenon/ and the Socket.io event stream."
|
|
117
135
|
},
|
|
118
136
|
"bootedSimulators": {
|
|
119
137
|
"type": "boolean",
|
|
120
|
-
"default": false
|
|
138
|
+
"default": false,
|
|
139
|
+
"description": "Only discover iOS simulators that are already booted. Recommended on machines with many installed simulators — avoids allocating WDA/MJPEG ports for shutdown sims (the WDA pool is 8100-8199, 100 ports)."
|
|
121
140
|
},
|
|
122
141
|
"bootedEmulators": {
|
|
123
142
|
"type": "boolean",
|
|
124
|
-
"default": false
|
|
143
|
+
"default": false,
|
|
144
|
+
"description": "Only discover Android emulators that are already booted."
|
|
125
145
|
},
|
|
126
146
|
"removeDevicesFromDatabaseBeforeRunningThePlugin": {
|
|
127
147
|
"type": "boolean",
|
|
128
|
-
"default": false
|
|
148
|
+
"default": false,
|
|
149
|
+
"description": "Wipe the persisted Device table at startup so discovery begins from a clean slate. Useful after hardware changes."
|
|
129
150
|
},
|
|
130
151
|
"healthCheckIntervalMs": {
|
|
131
152
|
"type": "number",
|
|
132
|
-
"default": 86400000
|
|
153
|
+
"default": 86400000,
|
|
154
|
+
"description": "Default interval (ms) between background device health checks. Overridden when `healthCheckSchedule` is set."
|
|
133
155
|
},
|
|
134
156
|
"healthCheckSchedule": {
|
|
135
|
-
"type": "string"
|
|
157
|
+
"type": "string",
|
|
158
|
+
"description": "Cron expression for the device health-check job (e.g. '0 * * * *' for hourly). When set, takes precedence over `healthCheckIntervalMs`."
|
|
136
159
|
},
|
|
137
160
|
"databaseProvider": {
|
|
138
161
|
"type": "string",
|
|
139
162
|
"enum": [
|
|
140
163
|
"sqlite",
|
|
141
164
|
"postgresql"
|
|
142
|
-
]
|
|
165
|
+
],
|
|
166
|
+
"description": "Database backend. Defaults to sqlite (file under ~/.cache/xenon). Use postgresql for multi-node hub deployments."
|
|
143
167
|
},
|
|
144
168
|
"databaseUrl": {
|
|
145
|
-
"type": "string"
|
|
169
|
+
"type": "string",
|
|
170
|
+
"description": "Prisma-style database URL. For sqlite: `file:/path/to/xenon.db`. For postgres: `postgresql://user:pass@host/db`. Falls back to the DATABASE_URL env var."
|
|
146
171
|
},
|
|
147
172
|
"aiProvider": {
|
|
148
173
|
"type": "string",
|
|
@@ -151,55 +176,77 @@
|
|
|
151
176
|
"openai",
|
|
152
177
|
"anthropic",
|
|
153
178
|
"ollama"
|
|
154
|
-
]
|
|
179
|
+
],
|
|
180
|
+
"description": "AI provider for the LLM healing tier and visual analysis. Also controlled by XENON_AI_PROVIDER."
|
|
155
181
|
},
|
|
156
182
|
"aiModel": {
|
|
157
|
-
"type": "string"
|
|
183
|
+
"type": "string",
|
|
184
|
+
"description": "Override the default model for the selected `aiProvider` (e.g. 'gemini-1.5-pro', 'gpt-4o', 'claude-sonnet-4-6'). Falls back to XENON_AI_MODEL."
|
|
158
185
|
},
|
|
159
186
|
"aiBaseUrl": {
|
|
160
|
-
"type": "string"
|
|
187
|
+
"type": "string",
|
|
188
|
+
"description": "Custom base URL for the AI provider (e.g. a local Ollama server or an OpenAI-compatible gateway). Falls back to XENON_AI_BASE_URL."
|
|
161
189
|
},
|
|
162
190
|
"geminiApiKey": {
|
|
163
|
-
"type": "string"
|
|
191
|
+
"type": "string",
|
|
192
|
+
"description": "Gemini API key. Prefer setting XENON_GEMINI_API_KEY (or GEMINI_API_KEY) via environment instead of committing it to a config file."
|
|
164
193
|
},
|
|
165
194
|
"openaiApiKey": {
|
|
166
|
-
"type": "string"
|
|
195
|
+
"type": "string",
|
|
196
|
+
"description": "OpenAI API key. Prefer setting XENON_OPENAI_API_KEY (or OPENAI_API_KEY) via environment."
|
|
167
197
|
},
|
|
168
198
|
"anthropicApiKey": {
|
|
169
|
-
"type": "string"
|
|
199
|
+
"type": "string",
|
|
200
|
+
"description": "Anthropic API key. Prefer setting XENON_ANTHROPIC_API_KEY (or ANTHROPIC_API_KEY) via environment."
|
|
170
201
|
},
|
|
171
202
|
"enableSelfHealing": {
|
|
172
203
|
"type": "boolean",
|
|
173
|
-
"default": true
|
|
204
|
+
"default": true,
|
|
205
|
+
"description": "Enable the 5-tier self-healing pipeline (Native → Fuzzy XML → OCR → Visual AI → LLM) for failed findElement calls. Can also be toggled at runtime from the dashboard."
|
|
174
206
|
},
|
|
175
207
|
"buildCleanupDays": {
|
|
176
208
|
"type": "number",
|
|
177
|
-
"default": 30
|
|
209
|
+
"default": 30,
|
|
210
|
+
"description": "Builds/sessions older than this many days are purged by the cleanup job."
|
|
178
211
|
},
|
|
179
212
|
"buildCleanupMaxCount": {
|
|
180
213
|
"type": "number",
|
|
181
|
-
"default": 100
|
|
214
|
+
"default": 100,
|
|
215
|
+
"description": "Maximum number of builds to retain. Oldest-first eviction beyond this cap regardless of `buildCleanupDays`."
|
|
182
216
|
},
|
|
183
217
|
"buildCleanupSchedule": {
|
|
184
218
|
"type": "string",
|
|
185
|
-
"default": "0 0 * * *"
|
|
219
|
+
"default": "0 0 * * *",
|
|
220
|
+
"description": "Cron expression for the retention job. Default '0 0 * * *' runs at midnight."
|
|
186
221
|
},
|
|
187
222
|
"deleteBuildAssets": {
|
|
188
223
|
"type": "boolean",
|
|
189
|
-
"default": true
|
|
224
|
+
"default": true,
|
|
225
|
+
"description": "When true, the cleanup job also deletes session video recordings and screenshots from disk (not just DB rows)."
|
|
190
226
|
},
|
|
191
227
|
"sessionHeartbeatIntervalMs": {
|
|
192
228
|
"type": "number",
|
|
193
|
-
"default": 30000
|
|
229
|
+
"default": 30000,
|
|
230
|
+
"description": "How often (ms) each active session writes a heartbeat. The orphan sweeper uses ~3× this interval to detect abandoned sessions."
|
|
194
231
|
},
|
|
195
232
|
"enableJsonLogging": {
|
|
196
233
|
"type": "boolean",
|
|
197
|
-
"default": false
|
|
234
|
+
"default": false,
|
|
235
|
+
"description": "Emit structured JSON log lines instead of human-readable text. Recommended for shipping logs to a log aggregator."
|
|
198
236
|
},
|
|
199
237
|
"tlsRejectUnauthorized": {
|
|
200
238
|
"type": "boolean",
|
|
201
239
|
"default": true,
|
|
202
240
|
"description": "Whether to verify TLS certificates for internal outgoing requests. Default is true. Set to false only for dev/test."
|
|
241
|
+
},
|
|
242
|
+
"authDisabled": {
|
|
243
|
+
"type": "boolean",
|
|
244
|
+
"default": false,
|
|
245
|
+
"description": "Disable API key authentication for all /xenon/api/* endpoints. Use only in local dev environments."
|
|
246
|
+
},
|
|
247
|
+
"nodeSecret": {
|
|
248
|
+
"type": "string",
|
|
249
|
+
"description": "Shared secret for hub-node channel authentication. Nodes must send this value in X-Xenon-Node-Secret header."
|
|
203
250
|
}
|
|
204
251
|
},
|
|
205
252
|
"required": [
|