@xenon-device-management/xenon 1.1.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 +446 -0
- package/lib/package.json +207 -0
- package/lib/public/assets/Layouts-7IT8aFLI.js +11 -0
- package/lib/public/assets/Layouts-DPMls9vh.css +1 -0
- package/lib/public/assets/ai-settings-BbnfgdEx.js +11 -0
- package/lib/public/assets/apps-CRMrI4_p.js +16 -0
- package/lib/public/assets/apps-CcM77dgg.css +1 -0
- package/lib/public/assets/badge-B1nKs8zj.css +1 -0
- package/lib/public/assets/badge-CSvl5xIU.js +11 -0
- package/lib/public/assets/button-CJlKn4PZ.css +1 -0
- package/lib/public/assets/button-CvLaGFYj.js +26 -0
- package/lib/public/assets/calendar-6w-D6Oaw.js +6 -0
- package/lib/public/assets/clock-DcdeWBPr.js +6 -0
- package/lib/public/assets/cpu-DiSoXT9n.js +6 -0
- package/lib/public/assets/device-explorer-CajM63OJ.js +193 -0
- package/lib/public/assets/device-explorer-CxdUAoTL.css +1 -0
- package/lib/public/assets/index-ByQwMN5T.js +174 -0
- package/lib/public/assets/index-C1DBaoSh.js +1 -0
- package/lib/public/assets/index-qzCez_kk.css +1 -0
- package/lib/public/assets/lock-B23ibZmo.js +6 -0
- package/lib/public/assets/maintenance-settings-CirzA6yG.js +6 -0
- package/lib/public/assets/mouse-pointer-2-Cz76SHFb.js +6 -0
- package/lib/public/assets/plus-BBwlIevt.js +6 -0
- package/lib/public/assets/session-dashboard-C2k7FFv_.css +1 -0
- package/lib/public/assets/session-dashboard-HPDtwPOZ.js +62 -0
- package/lib/public/assets/settings-DrZsZwdc.js +1 -0
- package/lib/public/assets/trash-2-DQpvzJec.js +6 -0
- package/lib/public/assets/useSocket-Dxsqae2a.js +16 -0
- package/lib/public/assets/webhook-settings-CDPgsgkb.css +1 -0
- package/lib/public/assets/webhook-settings-Cp-B4Nrw.js +1 -0
- package/lib/public/assets/zap-DovP6iow.js +6 -0
- package/lib/public/favicon.ico +0 -0
- package/lib/public/favicon.png +0 -0
- package/lib/public/favicon.svg +9 -0
- package/lib/public/index.html +46 -0
- package/lib/public/logo.svg +17 -0
- package/lib/public/logo192.png +0 -0
- package/lib/public/logo512.png +0 -0
- package/lib/public/manifest.json +25 -0
- package/lib/public/robots.txt +3 -0
- package/lib/schema.json +348 -0
- package/lib/src/InternalHttpClient.js +212 -0
- package/lib/src/PluginContext.js +29 -0
- package/lib/src/XenonCapabilityManager.js +199 -0
- package/lib/src/app/index.js +167 -0
- package/lib/src/app/routers/apps.js +79 -0
- package/lib/src/app/routers/config.js +131 -0
- package/lib/src/app/routers/control.js +835 -0
- package/lib/src/app/routers/dashboard.js +301 -0
- package/lib/src/app/routers/grid.js +352 -0
- package/lib/src/app/routers/reservation.js +190 -0
- package/lib/src/app/routers/webhook.js +83 -0
- package/lib/src/app/swagger-docs.js +203 -0
- package/lib/src/app/swagger.js +366 -0
- package/lib/src/chromeUtils.js +148 -0
- package/lib/src/commands/handle.js +19 -0
- package/lib/src/commands/index.js +8 -0
- package/lib/src/config.js +73 -0
- package/lib/src/dashboard/asset-manager.js +84 -0
- package/lib/src/dashboard/commands.js +284 -0
- package/lib/src/dashboard/event-manager.js +699 -0
- package/lib/src/dashboard/services/app-service.js +134 -0
- package/lib/src/dashboard/services/failure-analysis-service.js +173 -0
- package/lib/src/dashboard/services/session-service.js +113 -0
- package/lib/src/data-service/CircuitBreaker.js +83 -0
- package/lib/src/data-service/config-service.js +155 -0
- package/lib/src/data-service/db.js +122 -0
- package/lib/src/data-service/device-service.js +320 -0
- package/lib/src/data-service/device-store.interface.js +2 -0
- package/lib/src/data-service/device-store.js +345 -0
- package/lib/src/data-service/pending-sessions-service.js +25 -0
- package/lib/src/data-service/pluginArgs.js +25 -0
- package/lib/src/data-service/prisma-service.js +31 -0
- package/lib/src/data-service/prisma-store.js +385 -0
- package/lib/src/data-service/queue-service.js +150 -0
- package/lib/src/data-service/web-config-service.js +130 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +1155 -0
- package/lib/src/device-managers/ChromeDriverManager.js +68 -0
- package/lib/src/device-managers/HealthMonitorService.js +325 -0
- package/lib/src/device-managers/IOSDeviceManager.js +351 -0
- package/lib/src/device-managers/NodeDevices.js +82 -0
- package/lib/src/device-managers/android/AndroidStreamService.js +370 -0
- package/lib/src/device-managers/android/DeviceLockManager.js +45 -0
- package/lib/src/device-managers/cloud/CapabilityManager.js +26 -0
- package/lib/src/device-managers/cloud/Devices.js +86 -0
- package/lib/src/device-managers/iOSTracker.js +44 -0
- package/lib/src/device-managers/index.js +89 -0
- package/lib/src/device-managers/ios/IOSDiscoveryService.js +268 -0
- package/lib/src/device-managers/ios/IOSStreamService.js +893 -0
- package/lib/src/device-managers/ios/WDAClient.js +866 -0
- package/lib/src/device-utils.js +663 -0
- package/lib/src/enums/Capabilities.js +8 -0
- package/lib/src/enums/Cloud.js +11 -0
- package/lib/src/enums/Platform.js +9 -0
- package/lib/src/enums/SessionType.js +9 -0
- package/lib/src/enums/SocketEvents.js +15 -0
- package/lib/src/helpers/UniversalMjpegProxy.js +273 -0
- package/lib/src/helpers/index.js +229 -0
- package/lib/src/index.js +95 -0
- package/lib/src/interceptors/CommandInterceptor.js +524 -0
- package/lib/src/interfaces/ICloudManager.js +2 -0
- package/lib/src/interfaces/IDevice.js +2 -0
- package/lib/src/interfaces/IDeviceFilterOptions.js +2 -0
- package/lib/src/interfaces/IDeviceManager.js +2 -0
- package/lib/src/interfaces/IOptions.js +2 -0
- package/lib/src/interfaces/IPluginArgs.js +55 -0
- package/lib/src/interfaces/ISessionCapability.js +2 -0
- package/lib/src/logger.js +225 -0
- package/lib/src/plugin.js +244 -0
- package/lib/src/prisma.js +12 -0
- package/lib/src/profiling/AndroidAppProfiler.js +213 -0
- package/lib/src/proxy/wd-command-proxy.js +221 -0
- package/lib/src/scripts/generate-database-migration.js +59 -0
- package/lib/src/scripts/initialize-database.js +55 -0
- package/lib/src/scripts/install-go-ios.js +66 -0
- package/lib/src/scripts/prepare-prisma.js +89 -0
- package/lib/src/services/AICommandService.js +143 -0
- package/lib/src/services/AIService.js +466 -0
- package/lib/src/services/CleanupService.js +141 -0
- package/lib/src/services/EventBus.js +74 -0
- package/lib/src/services/InspectorService.js +395 -0
- package/lib/src/services/MetricsService.js +134 -0
- package/lib/src/services/NetworkConditioningService.js +173 -0
- package/lib/src/services/NotificationService.js +163 -0
- package/lib/src/services/RequestLogService.js +252 -0
- package/lib/src/services/ResourceIsolationService.js +122 -0
- package/lib/src/services/SecurityService.js +120 -0
- package/lib/src/services/ServerManager.js +284 -0
- package/lib/src/services/SessionHeartbeatService.js +158 -0
- package/lib/src/services/SessionLifecycleService.js +572 -0
- package/lib/src/services/SocketClient.js +71 -0
- package/lib/src/services/SocketServer.js +87 -0
- package/lib/src/services/TracingService.js +132 -0
- package/lib/src/services/VideoPipelineService.js +220 -0
- package/lib/src/services/healing/FuzzyXmlHealingProvider.js +333 -0
- package/lib/src/services/healing/HealEtalonService.js +98 -0
- package/lib/src/services/healing/HealedLocatorGenerator.js +132 -0
- package/lib/src/services/healing/HealingOrchestrator.js +165 -0
- package/lib/src/services/healing/LlmHealingProvider.js +77 -0
- package/lib/src/services/healing/OcrHealingProvider.js +119 -0
- package/lib/src/services/healing/ResilioTreeHealingProvider.js +100 -0
- package/lib/src/services/healing/VisualAiHealingProvider.js +90 -0
- package/lib/src/services/healing/types.js +12 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +718 -0
- package/lib/src/services/omni-vision/VisionAssertionService.js +68 -0
- package/lib/src/sessions/CloudSession.js +42 -0
- package/lib/src/sessions/LocalSession.js +313 -0
- package/lib/src/sessions/RemoteSession.js +287 -0
- package/lib/src/sessions/SessionManager.js +238 -0
- package/lib/src/sessions/XenonSession.js +44 -0
- package/lib/src/types/CLIArgs.js +2 -0
- package/lib/src/types/CloudArgs.js +2 -0
- package/lib/src/types/CloudSchema.js +131 -0
- package/lib/src/types/DeviceType.js +2 -0
- package/lib/src/types/DeviceUpdate.js +2 -0
- package/lib/src/types/IOSDevice.js +2 -0
- package/lib/src/types/Platform.js +2 -0
- package/lib/src/types/SessionStatus.js +11 -0
- package/lib/src/validators/CapabilityValidator.js +93 -0
- package/lib/test/e2e/android/conf.spec.js +43 -0
- package/lib/test/e2e/android/conf2.spec.js +44 -0
- package/lib/test/e2e/android/conf3.spec.js +44 -0
- package/lib/test/e2e/e2ehelper.js +113 -0
- package/lib/test/e2e/hubnode/forward-request.spec.js +224 -0
- package/lib/test/e2e/hubnode/hubnode.spec.js +214 -0
- package/lib/test/e2e/ios/conf1.spec.js +39 -0
- package/lib/test/e2e/ios/conf2.spec.js +39 -0
- package/lib/test/e2e/plugin-harness.js +236 -0
- package/lib/test/e2e/plugin.spec.js +97 -0
- package/lib/test/e2e/telemetry_verification.spec.js +83 -0
- package/lib/test/e2e/video-recording-test.spec.js +63 -0
- package/lib/test/helpers/test-container.js +112 -0
- package/lib/test/integration/androidDevices.spec.js +137 -0
- package/lib/test/integration/cliArgs.js +73 -0
- package/lib/test/integration/ios/01iOSSimulator.spec.js +291 -0
- package/lib/test/integration/ios/02iOSDevices.spec.js +75 -0
- package/lib/test/integration/testHelpers.js +74 -0
- package/lib/test/unit/AndroidDeviceManager.spec.js +178 -0
- package/lib/test/unit/ChromeDriverManager.spec.js +26 -0
- package/lib/test/unit/CleanupService.spec.js +21 -0
- package/lib/test/unit/DeviceModel.spec.js +157 -0
- package/lib/test/unit/FuzzyXmlHealingProvider.test.js +294 -0
- package/lib/test/unit/GetAdbOriginal.js +42 -0
- package/lib/test/unit/HealingCascade.test.js +128 -0
- package/lib/test/unit/IOSDeviceManager.spec.js +261 -0
- package/lib/test/unit/RemoteIOs.spec.js +78 -0
- package/lib/test/unit/ResilioTreeHealingProvider.test.js +96 -0
- package/lib/test/unit/commands.spec.js +27 -0
- package/lib/test/unit/config.spec.js +27 -0
- package/lib/test/unit/device-service.spec.js +307 -0
- package/lib/test/unit/device-utils.spec.js +313 -0
- package/lib/test/unit/fixtures/device.config.js +4 -0
- package/lib/test/unit/fixtures/devices.js +89 -0
- package/lib/test/unit/helpers.spec.js +62 -0
- package/lib/test/unit/omni-vision.spec.js +100 -0
- package/lib/test/unit/plugin.spec.js +133 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +207 -0
- package/prisma/data.db +0 -0
- package/prisma/dev.db +0 -0
- package/prisma/dev.db-journal +0 -0
- package/prisma/migrations/20231011074725_initial_tables/migration.sql +47 -0
- package/prisma/migrations/20231226115334_update_session_log/migration.sql +2 -0
- package/prisma/migrations/20251204113710_add_video_recording_enabled/migration.sql +29 -0
- package/prisma/migrations/20251204132449_add_log_table/migration.sql +11 -0
- package/prisma/migrations/20251205050111_add_profiling_support/migration.sql +47 -0
- package/prisma/migrations/20251205050947_add_is_error_field/migration.sql +24 -0
- package/prisma/migrations/20260126201337_add_app_model/migration.sql +18 -0
- package/prisma/migrations/20260130115722_add_performance_trace_and_xenon_sync/migration.sql +2 -0
- package/prisma/migrations/20260130135114_add_device_models/migration.sql +57 -0
- package/prisma/migrations/20260130140655_make_systemport_optional/migration.sql +45 -0
- package/prisma/migrations/20260130140932_make_device_fields_optional/migration.sql +45 -0
- package/prisma/migrations/20260130141040_final_schema_fix/migration.sql +45 -0
- package/prisma/migrations/20260130143234_add_device_health_fields/migration.sql +4 -0
- package/prisma/migrations/20260130144921_add_failure_category/migration.sql +2 -0
- package/prisma/migrations/20260131151456_add_webhook_config/migration.sql +10 -0
- package/prisma/migrations/20260201094507_add_device_tags/migration.sql +11 -0
- package/prisma/migrations/20260201103410_add_managed_process/migration.sql +15 -0
- package/prisma/migrations/20260201140637_add_web_config/migration.sql +22 -0
- package/prisma/migrations/20260201162232_add_session_progress/migration.sql +2 -0
- package/prisma/migrations/20260201174231_add_total_healed_count/migration.sql +2 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +210 -0
- package/schema.json +348 -0
- package/scripts/build-xenon.sh +32 -0
- package/scripts/dev/debug-gemini.ts +44 -0
- package/scripts/generate-types-from-schema.js +86 -0
- package/scripts/install-compatible-driver.js +39 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var ChromeDriverManager_1;
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
const chromeUtils_1 = require("../chromeUtils");
|
|
23
|
+
const appium_chromedriver_1 = require("appium-chromedriver");
|
|
24
|
+
const typedi_1 = require("typedi");
|
|
25
|
+
let ChromeDriverManager = ChromeDriverManager_1 = class ChromeDriverManager {
|
|
26
|
+
constructor(client, osInfo, mapping, tempDirectory) {
|
|
27
|
+
this.client = client;
|
|
28
|
+
this.osInfo = osInfo;
|
|
29
|
+
this.mapping = mapping;
|
|
30
|
+
this.tempDirectory = tempDirectory;
|
|
31
|
+
}
|
|
32
|
+
static create() {
|
|
33
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
+
const shouldParseNotes = true;
|
|
35
|
+
const tmpRoot = (0, chromeUtils_1.getModuleRoot)();
|
|
36
|
+
const osInfo = yield (0, chromeUtils_1.getOsInfo)();
|
|
37
|
+
const client = new appium_chromedriver_1.ChromedriverStorageClient({
|
|
38
|
+
chromedriverDir: yield (0, chromeUtils_1.getChromedriverBinaryPath)(tmpRoot),
|
|
39
|
+
});
|
|
40
|
+
const mapping = yield client.retrieveMapping(shouldParseNotes);
|
|
41
|
+
return new ChromeDriverManager_1(client, osInfo, mapping, tmpRoot);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
downloadChromeDriver(version) {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
const osInfo1 = this.osInfo;
|
|
47
|
+
const synchronizedDrivers = yield this.client.syncDrivers({
|
|
48
|
+
osInfo1,
|
|
49
|
+
minBrowserVersion: [yield (0, chromeUtils_1.formatCdVersion)(version)],
|
|
50
|
+
});
|
|
51
|
+
const synchronizedDriversMapping = synchronizedDrivers.reduce((acc, x) => {
|
|
52
|
+
const { version, minBrowserVersion } = this.mapping[x];
|
|
53
|
+
acc[version] = minBrowserVersion;
|
|
54
|
+
return acc;
|
|
55
|
+
}, {});
|
|
56
|
+
const versions = Object.keys(synchronizedDriversMapping);
|
|
57
|
+
const fallBackVersion = `v${versions[versions.length - 1]}`;
|
|
58
|
+
const newVersion = Object.keys(synchronizedDriversMapping).find((k) => synchronizedDriversMapping[k] === version);
|
|
59
|
+
const latestVersion = newVersion !== null && newVersion !== undefined ? `v${newVersion}` : `v${fallBackVersion}`;
|
|
60
|
+
return `${yield (0, chromeUtils_1.getChromedriverBinaryPath)(this.tempDirectory)}/chromedriver_${this.osInfo.name}${this.osInfo.arch}_${latestVersion}`;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
ChromeDriverManager = ChromeDriverManager_1 = __decorate([
|
|
65
|
+
(0, typedi_1.Service)(),
|
|
66
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object])
|
|
67
|
+
], ChromeDriverManager);
|
|
68
|
+
exports.default = ChromeDriverManager;
|
|
@@ -0,0 +1,325 @@
|
|
|
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 __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
45
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
46
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
47
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
48
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
49
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
50
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
54
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
55
|
+
};
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.HealthMonitorService = void 0;
|
|
58
|
+
const typedi_1 = require("typedi");
|
|
59
|
+
const index_1 = require("./index");
|
|
60
|
+
const device_store_1 = require("../data-service/device-store");
|
|
61
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
62
|
+
const schedule = __importStar(require("node-schedule"));
|
|
63
|
+
const web_config_service_1 = require("../data-service/web-config-service");
|
|
64
|
+
const SessionManager_1 = require("../sessions/SessionManager");
|
|
65
|
+
const EventBus_1 = require("../services/EventBus");
|
|
66
|
+
let HealthMonitorService = class HealthMonitorService {
|
|
67
|
+
constructor(webConfigService) {
|
|
68
|
+
this.webConfigService = webConfigService;
|
|
69
|
+
this.log = logger_1.default.scope('HealthMonitor');
|
|
70
|
+
this.recoveringDevices = new Set();
|
|
71
|
+
this.lastWebConfig = {};
|
|
72
|
+
this.healthHistory = new Map();
|
|
73
|
+
}
|
|
74
|
+
start(pluginArgs) {
|
|
75
|
+
this.pluginArgs = pluginArgs;
|
|
76
|
+
this.stop();
|
|
77
|
+
// Do NOT run checkAllDevices immediately on startup.
|
|
78
|
+
// Devices are freshly discovered and presumed healthy.
|
|
79
|
+
// The first check will fire after the configured interval (default: 24h).
|
|
80
|
+
this.setupMonitor(pluginArgs, false);
|
|
81
|
+
// Delay config polling to avoid restarting the monitor during startup
|
|
82
|
+
this.configPollInterval = setInterval(() => this.pollWebConfig(), 60000); // Poll every minute
|
|
83
|
+
}
|
|
84
|
+
pollWebConfig() {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
try {
|
|
87
|
+
const webConfig = yield this.webConfigService.getConfig();
|
|
88
|
+
// Merge web config with plugin args to get current effective settings
|
|
89
|
+
const currentEffectiveArgs = Object.assign(Object.assign({}, this.pluginArgs), webConfig);
|
|
90
|
+
// Check if interval-related settings actually changed
|
|
91
|
+
const lastEffectiveArgs = Object.assign(Object.assign({}, this.pluginArgs), this.lastWebConfig);
|
|
92
|
+
const hasChanged = currentEffectiveArgs.healthCheckIntervalMs !== lastEffectiveArgs.healthCheckIntervalMs ||
|
|
93
|
+
currentEffectiveArgs.healthCheckSchedule !== lastEffectiveArgs.healthCheckSchedule;
|
|
94
|
+
if (hasChanged) {
|
|
95
|
+
this.log.info('Detected health check configuration changes, restarting monitor...');
|
|
96
|
+
this.setupMonitor(currentEffectiveArgs, false); // Don't run immediately on config refresh
|
|
97
|
+
}
|
|
98
|
+
this.lastWebConfig = webConfig;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
this.log.error(`Failed to poll web config: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
setupMonitor(args, runImmediately = true) {
|
|
106
|
+
const intervalMs = args.healthCheckIntervalMs || 86400000;
|
|
107
|
+
const scheduleStr = args.healthCheckSchedule;
|
|
108
|
+
if (this.interval)
|
|
109
|
+
clearInterval(this.interval);
|
|
110
|
+
if (this.job)
|
|
111
|
+
this.job.cancel();
|
|
112
|
+
if (scheduleStr) {
|
|
113
|
+
this.log.info(`Scheduling Health Monitor (Cron: ${scheduleStr})`);
|
|
114
|
+
this.job = schedule.scheduleJob(scheduleStr, () => this.checkAllDevices());
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this.log.info(`Starting Health Monitor Service (Interval: ${intervalMs}ms)`);
|
|
118
|
+
this.interval = setInterval(() => this.checkAllDevices(), intervalMs);
|
|
119
|
+
}
|
|
120
|
+
if (runImmediately) {
|
|
121
|
+
this.checkAllDevices();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
stop() {
|
|
125
|
+
if (this.interval) {
|
|
126
|
+
clearInterval(this.interval);
|
|
127
|
+
this.interval = undefined;
|
|
128
|
+
}
|
|
129
|
+
if (this.job) {
|
|
130
|
+
this.job.cancel();
|
|
131
|
+
this.job = undefined;
|
|
132
|
+
}
|
|
133
|
+
if (this.configPollInterval) {
|
|
134
|
+
clearInterval(this.configPollInterval);
|
|
135
|
+
this.configPollInterval = undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
checkAllDevices() {
|
|
139
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
try {
|
|
142
|
+
const store = device_store_1.DeviceStoreFactory.getStore();
|
|
143
|
+
const devices = yield store.getAllDevices();
|
|
144
|
+
const manager = typedi_1.Container.get(index_1.XenonManager);
|
|
145
|
+
const instances = yield manager.deviceInstances();
|
|
146
|
+
for (const device of devices) {
|
|
147
|
+
// Only check devices managed by this node
|
|
148
|
+
if (device.cloud)
|
|
149
|
+
continue;
|
|
150
|
+
// Principal Intelligence: Health monitor must detect "Zombie Busy" devices.
|
|
151
|
+
// A device is a Zombie if it's marked busy in DB but NOT found in Hub's session map
|
|
152
|
+
// AND not currently streaming to a manual viewer.
|
|
153
|
+
if (device.busy) {
|
|
154
|
+
const hasActiveSession = SessionManager_1.SESSION_MANAGER.isValidSession(device.session_id || '');
|
|
155
|
+
const isManualStream = (_a = device.session_id) === null || _a === void 0 ? void 0 : _a.startsWith('manual_');
|
|
156
|
+
// NEW: Check for active manual streams (dashboard viewers)
|
|
157
|
+
let hasActiveManualStream = false;
|
|
158
|
+
try {
|
|
159
|
+
if (['ios', 'tvos'].includes(device.platform)) {
|
|
160
|
+
yield Promise.resolve().then(() => __importStar(require('./ios/IOSStreamService')));
|
|
161
|
+
const iosStream = typedi_1.Container.get('IOSStreamService');
|
|
162
|
+
const streamStatus = iosStream.getStreamStatus(device.udid);
|
|
163
|
+
hasActiveManualStream = !!(streamStatus &&
|
|
164
|
+
(streamStatus.status === 'running' || streamStatus.status === 'starting'));
|
|
165
|
+
}
|
|
166
|
+
else if (device.platform === 'android') {
|
|
167
|
+
yield Promise.resolve().then(() => __importStar(require('./android/AndroidStreamService')));
|
|
168
|
+
const androidStream = typedi_1.Container.get('AndroidStreamService');
|
|
169
|
+
const streamStatus = androidStream.getStreamStatus(device.udid);
|
|
170
|
+
hasActiveManualStream = !!(streamStatus &&
|
|
171
|
+
(streamStatus.status === 'running' || streamStatus.status === 'starting'));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
/* Stream service not available */
|
|
176
|
+
}
|
|
177
|
+
if (hasActiveSession || hasActiveManualStream || isManualStream) {
|
|
178
|
+
logger_1.default.debug(`[HealthMonitor] Skipping check for busy device ${device.udid} (Active: Session=${!!hasActiveSession}, Manual=${isManualStream}, Stream=${hasActiveManualStream})`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.log.info(`[HealthMonitor] 🧟 Zombie busy device detected ${device.udid}. Last session ${device.session_id} is not in memory and no manual stream found. Proceeding with health check.`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const deviceManager = instances.find((inst) => {
|
|
186
|
+
if (device.platform === 'android')
|
|
187
|
+
return inst.constructor.name === 'AndroidDeviceManager';
|
|
188
|
+
if (['ios', 'tvos'].includes(device.platform))
|
|
189
|
+
return inst.constructor.name === 'IOSDeviceManager';
|
|
190
|
+
return false;
|
|
191
|
+
});
|
|
192
|
+
if (deviceManager && deviceManager.checkHealth) {
|
|
193
|
+
try {
|
|
194
|
+
const health = yield deviceManager.checkHealth(device);
|
|
195
|
+
const updateData = Object.assign(Object.assign({}, health), { lastHealthCheckAt: Date.now() });
|
|
196
|
+
// Principal Fix: If this was a zombie busy device, reset its busy status
|
|
197
|
+
// so it can be recovered and utilized again.
|
|
198
|
+
// Double-check: Must NOT be an active session AND NOT an active stream/manual control
|
|
199
|
+
const hasSessionNow = SessionManager_1.SESSION_MANAGER.isValidSession(device.session_id || '');
|
|
200
|
+
const isManualNow = (_b = device.session_id) === null || _b === void 0 ? void 0 : _b.startsWith('manual_');
|
|
201
|
+
if (device.busy && !hasSessionNow && !isManualNow) {
|
|
202
|
+
// Re-check stream status immediately before reclaiming
|
|
203
|
+
let hasStreamNow = false;
|
|
204
|
+
try {
|
|
205
|
+
if (['ios', 'tvos'].includes(device.platform)) {
|
|
206
|
+
hasStreamNow = !!typedi_1.Container.get('IOSStreamService').getStreamStatus(device.udid);
|
|
207
|
+
}
|
|
208
|
+
else if (device.platform === 'android') {
|
|
209
|
+
hasStreamNow = !!typedi_1.Container.get('AndroidStreamService').getStreamStatus(device.udid);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
/* ignore */
|
|
214
|
+
}
|
|
215
|
+
if (!hasStreamNow) {
|
|
216
|
+
this.log.info(`[HealthMonitor] Reclaiming zombie device ${device.udid}`);
|
|
217
|
+
updateData.busy = false;
|
|
218
|
+
updateData.session_id = undefined;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
this.log.debug(`[HealthMonitor] Cancelled reclamation for ${device.udid} - stream appeared.`);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
yield store.updateDevice(device.udid, device.host, updateData);
|
|
226
|
+
// Principal Intelligence: Predictive Failure Analysis
|
|
227
|
+
// Track trends and emit anomalies before they become critical failures.
|
|
228
|
+
this.trackAndAnalyzeAnomaly(device, health);
|
|
229
|
+
if (health.healthStatus !== 'Healthy') {
|
|
230
|
+
this.log.warn(`Device ${device.udid} is UNHEALTHY: ${health.healthCheckError}`);
|
|
231
|
+
const key = `${device.udid}-${device.host}`;
|
|
232
|
+
if (!this.recoveringDevices.has(key)) {
|
|
233
|
+
this.recoveringDevices.add(key);
|
|
234
|
+
this.log.info(`🛠️ Triggering background recovery for ${device.udid}...`);
|
|
235
|
+
// Fire and forget recovery to not block the monitor loop
|
|
236
|
+
if (deviceManager.recoverHealth) {
|
|
237
|
+
deviceManager
|
|
238
|
+
.recoverHealth(device)
|
|
239
|
+
.then((success) => {
|
|
240
|
+
if (success) {
|
|
241
|
+
this.log.info(`✅ Recovery successful for ${device.udid}`);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this.log.error(`❌ Recovery failed for ${device.udid}`);
|
|
245
|
+
}
|
|
246
|
+
this.recoveringDevices.delete(key);
|
|
247
|
+
})
|
|
248
|
+
.catch((e) => {
|
|
249
|
+
this.log.error(`Critical error during recovery for ${device.udid}: ${e.message}`);
|
|
250
|
+
this.recoveringDevices.delete(key);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
this.log.warn(`No recovery method implemented for ${device.udid}`);
|
|
255
|
+
this.recoveringDevices.delete(key);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
this.log.error(`Health check failed for ${device.udid}: ${err.message}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
this.log.error(`Global health check loop failed: ${err.message}`);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Predictive Intelligence: Analyzes health trends to detect anomalies.
|
|
273
|
+
* Emits 'device:anomaly' event if a potential failure is predicted.
|
|
274
|
+
*/
|
|
275
|
+
trackAndAnalyzeAnomaly(device, currentHealth) {
|
|
276
|
+
const key = `${device.udid}-${device.host}`;
|
|
277
|
+
const history = this.healthHistory.get(key) || [];
|
|
278
|
+
// Add current entry to history
|
|
279
|
+
history.push({
|
|
280
|
+
time: Date.now(),
|
|
281
|
+
battery: currentHealth.batteryLevel,
|
|
282
|
+
thermal: currentHealth.thermalStatus,
|
|
283
|
+
});
|
|
284
|
+
// Keep last 10 entries (~5 minutes if checking every 30s)
|
|
285
|
+
if (history.length > 10)
|
|
286
|
+
history.shift();
|
|
287
|
+
this.healthHistory.set(key, history);
|
|
288
|
+
if (history.length < 2)
|
|
289
|
+
return;
|
|
290
|
+
const previous = history[history.length - 2];
|
|
291
|
+
const current = history[history.length - 1];
|
|
292
|
+
// 1. Thermal Anomaly: Normal -> Hot or Hot -> Critical
|
|
293
|
+
if (previous.thermal !== current.thermal && current.thermal !== 'Normal') {
|
|
294
|
+
this.log.warn(`⚠️ [Anomaly] Thermal spike detected on ${device.udid}: ${previous.thermal} -> ${current.thermal}`);
|
|
295
|
+
EventBus_1.EVENT_BUS.emit('device:anomaly', {
|
|
296
|
+
udid: device.udid,
|
|
297
|
+
host: device.host,
|
|
298
|
+
type: 'thermal_spike',
|
|
299
|
+
previous: previous.thermal,
|
|
300
|
+
current: current.thermal,
|
|
301
|
+
severity: current.thermal === 'Critical' ? 'high' : 'medium',
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
// 2. Battery Anomaly: Rapid drain (>5% since last check)
|
|
305
|
+
if (previous.battery !== undefined && current.battery !== undefined) {
|
|
306
|
+
const drain = previous.battery - current.battery;
|
|
307
|
+
if (drain >= 5) {
|
|
308
|
+
this.log.warn(`⚠️ [Anomaly] Rapid battery drain on ${device.udid}: -${drain}% since last check`);
|
|
309
|
+
EventBus_1.EVENT_BUS.emit('device:anomaly', {
|
|
310
|
+
udid: device.udid,
|
|
311
|
+
host: device.host,
|
|
312
|
+
type: 'battery_drain',
|
|
313
|
+
drainValue: drain,
|
|
314
|
+
currentLevel: current.battery,
|
|
315
|
+
severity: current.battery < 20 ? 'high' : 'medium',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
exports.HealthMonitorService = HealthMonitorService;
|
|
322
|
+
exports.HealthMonitorService = HealthMonitorService = __decorate([
|
|
323
|
+
(0, typedi_1.Service)(),
|
|
324
|
+
__metadata("design:paramtypes", [web_config_service_1.WebConfigService])
|
|
325
|
+
], HealthMonitorService);
|