@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,122 @@
|
|
|
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
|
+
exports.XenonDatabase = void 0;
|
|
16
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
17
|
+
const lokijs_1 = __importDefault(require("lokijs"));
|
|
18
|
+
// database class singleton
|
|
19
|
+
class XenonDatabase {
|
|
20
|
+
static get DeviceModel() {
|
|
21
|
+
return XenonDatabase.getDeviceModel();
|
|
22
|
+
}
|
|
23
|
+
static get PendingSessionsModel() {
|
|
24
|
+
return XenonDatabase.getPendingSessionsModel();
|
|
25
|
+
}
|
|
26
|
+
static get CLIArgs() {
|
|
27
|
+
return XenonDatabase.getCLIArgs();
|
|
28
|
+
}
|
|
29
|
+
static get db() {
|
|
30
|
+
return XenonDatabase.getDB();
|
|
31
|
+
}
|
|
32
|
+
constructor() {
|
|
33
|
+
this._dbList = [];
|
|
34
|
+
logger_1.default.info('Initializing database');
|
|
35
|
+
XenonDatabase._instance = this;
|
|
36
|
+
}
|
|
37
|
+
static instance() {
|
|
38
|
+
return XenonDatabase._instance || new XenonDatabase();
|
|
39
|
+
}
|
|
40
|
+
static dbname() {
|
|
41
|
+
const appium_home = process.env.APPIUM_HOME || './temp-appium';
|
|
42
|
+
const isTest = process.env.NODE_ENV === 'test';
|
|
43
|
+
const dbFile = isTest ? `xenon-test-${process.pid}.json` : 'xenon-db.json';
|
|
44
|
+
return `${appium_home}/${dbFile}`;
|
|
45
|
+
}
|
|
46
|
+
static getDeviceModel() {
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
const db = yield XenonDatabase.getDB();
|
|
49
|
+
return db.getCollection('devices') || db.addCollection('devices');
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
static getPendingSessionsModel() {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
const db = yield XenonDatabase.getDB();
|
|
55
|
+
return db.getCollection('pending-sessions') || db.addCollection('pending-sessions');
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
static getCLIArgs() {
|
|
59
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
const db = yield XenonDatabase.getDB();
|
|
61
|
+
return db.getCollection('cliArgs') || db.addCollection('cliArgs');
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
static initCollections(db) {
|
|
65
|
+
db.addCollection('devices');
|
|
66
|
+
db.addCollection('pending-sessions');
|
|
67
|
+
db.addCollection('cliArgs');
|
|
68
|
+
}
|
|
69
|
+
static getDB() {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const existingDb = XenonDatabase.instance()._dbList.find((db) => db.dbname === XenonDatabase.dbname());
|
|
72
|
+
if (existingDb)
|
|
73
|
+
return existingDb.db;
|
|
74
|
+
logger_1.default.debug(`Creating new database: ${XenonDatabase.dbname()}`);
|
|
75
|
+
const db = yield new Promise((resolve, reject) => {
|
|
76
|
+
const isTest = process.env.NODE_ENV === 'test';
|
|
77
|
+
const db = new lokijs_1.default(XenonDatabase.dbname(), {
|
|
78
|
+
autoload: !isTest,
|
|
79
|
+
});
|
|
80
|
+
if (isTest) {
|
|
81
|
+
XenonDatabase.initCollections(db);
|
|
82
|
+
resolve(db);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
db.on('autoload', () => {
|
|
86
|
+
logger_1.default.info('Database autoloaded');
|
|
87
|
+
});
|
|
88
|
+
db.on('error', (err) => {
|
|
89
|
+
logger_1.default.error(`Error in database: ${err}`);
|
|
90
|
+
reject(err);
|
|
91
|
+
});
|
|
92
|
+
db.on('loaded', () => {
|
|
93
|
+
logger_1.default.info('Database loaded');
|
|
94
|
+
XenonDatabase.initCollections(db);
|
|
95
|
+
resolve(db);
|
|
96
|
+
});
|
|
97
|
+
db.on('flushChanges', () => {
|
|
98
|
+
logger_1.default.info('Database changes flushed');
|
|
99
|
+
});
|
|
100
|
+
db.on('close', () => {
|
|
101
|
+
logger_1.default.info('Database closed');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
XenonDatabase.instance()._dbList.push({ dbname: XenonDatabase.dbname(), db });
|
|
105
|
+
return db;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
static reset() {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
if (XenonDatabase._instance) {
|
|
111
|
+
console.log(`[XenonDatabase] Resetting ${XenonDatabase._instance._dbList.length} databases`);
|
|
112
|
+
for (const dbInfo of XenonDatabase._instance._dbList) {
|
|
113
|
+
dbInfo.db.collections.forEach((c) => {
|
|
114
|
+
console.log(`[XenonDatabase] Removing data from collection: ${c.name}`);
|
|
115
|
+
c.removeDataOnly();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.XenonDatabase = XenonDatabase;
|
|
@@ -0,0 +1,320 @@
|
|
|
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
|
+
exports.removeDevice = removeDevice;
|
|
16
|
+
exports.removeDevicesByHost = removeDevicesByHost;
|
|
17
|
+
exports.addNewDevice = addNewDevice;
|
|
18
|
+
exports.setSimulatorState = setSimulatorState;
|
|
19
|
+
exports.getAllDevices = getAllDevices;
|
|
20
|
+
exports.getDevices = getDevices;
|
|
21
|
+
exports.getDevice = getDevice;
|
|
22
|
+
exports.updatedAllocatedDevice = updatedAllocatedDevice;
|
|
23
|
+
exports.updateDeviceProgress = updateDeviceProgress;
|
|
24
|
+
exports.updateCmdExecutedTime = updateCmdExecutedTime;
|
|
25
|
+
exports.userBlockDevice = userBlockDevice;
|
|
26
|
+
exports.userUnblockDevice = userUnblockDevice;
|
|
27
|
+
exports.blockDevice = blockDevice;
|
|
28
|
+
exports.unblockDevice = unblockDevice;
|
|
29
|
+
exports.unblockDeviceMatchingFilter = unblockDeviceMatchingFilter;
|
|
30
|
+
exports.reserveDevice = reserveDevice;
|
|
31
|
+
exports.releaseReservation = releaseReservation;
|
|
32
|
+
exports.isDeviceReserved = isDeviceReserved;
|
|
33
|
+
exports.getReservedDevices = getReservedDevices;
|
|
34
|
+
exports.cleanExpiredReservations = cleanExpiredReservations;
|
|
35
|
+
exports.updateDeviceTags = updateDeviceTags;
|
|
36
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
37
|
+
const device_utils_1 = require("../device-utils");
|
|
38
|
+
const device_store_1 = require("./device-store");
|
|
39
|
+
const typedi_1 = require("typedi");
|
|
40
|
+
const CircuitBreaker_1 = require("./CircuitBreaker");
|
|
41
|
+
const NotificationService_1 = require("../services/NotificationService");
|
|
42
|
+
const SocketServer_1 = require("../services/SocketServer");
|
|
43
|
+
// Use a Proxy to ensure we're always using the latest store from the factory,
|
|
44
|
+
// which is critical for test isolation when the factory cache is cleared.
|
|
45
|
+
const store = new Proxy({}, {
|
|
46
|
+
get: (target, prop) => {
|
|
47
|
+
return device_store_1.DeviceStoreFactory.getStore()[prop];
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
function removeDevice(devices) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
for (const device of devices) {
|
|
53
|
+
logger_1.default.info(`Removing device ${device.udid} from host ${device.host}`);
|
|
54
|
+
yield store.removeDevices({ udid: device.udid, host: device.host });
|
|
55
|
+
typedi_1.Container.get(NotificationService_1.NotificationService).dispatchEvent('device_offline', device);
|
|
56
|
+
typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboard('device_removed', device);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function removeDevicesByHost(host) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
logger_1.default.info(`Removing all devices from host ${host}`);
|
|
63
|
+
// We can't easily dispatch events here without fetching first,
|
|
64
|
+
// but for now we'll stick to single device removal alerting
|
|
65
|
+
yield store.removeDevices({ host });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function addNewDevice(devices, host) {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
const normalizedDevices = devices.map((device) => {
|
|
71
|
+
const d = Object.assign({}, device);
|
|
72
|
+
if (d.host === undefined && host !== undefined)
|
|
73
|
+
d.host = host;
|
|
74
|
+
return Object.assign({ userBlocked: false, offline: false }, d);
|
|
75
|
+
});
|
|
76
|
+
const added = yield store.addDevices(normalizedDevices);
|
|
77
|
+
// Notify for new devices
|
|
78
|
+
for (const device of added) {
|
|
79
|
+
typedi_1.Container.get(NotificationService_1.NotificationService).dispatchEvent('device_new', device);
|
|
80
|
+
typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboard('device_added', device);
|
|
81
|
+
}
|
|
82
|
+
logger_1.default.debug(`Sync: Added ${added.length} new devices to store`);
|
|
83
|
+
return added;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function setSimulatorState(devices) {
|
|
87
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
const allInStore = yield store.getAllDevices();
|
|
89
|
+
const simMap = new Map(allInStore.filter((d) => d.deviceType === 'simulator').map((d) => [d.udid, d]));
|
|
90
|
+
for (const device of devices) {
|
|
91
|
+
if (device.deviceType !== 'simulator')
|
|
92
|
+
continue;
|
|
93
|
+
const found = simMap.get(device.udid);
|
|
94
|
+
if (found && found.state !== device.state) {
|
|
95
|
+
logger_1.default.info(`Updating Simulator ${device.udid} state: ${found.state} -> ${device.state}`);
|
|
96
|
+
yield store.updateDevice(device.udid, device.host, { state: device.state });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function getAllDevices() {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
103
|
+
return yield store.getAllDevices();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function getDevices(filterOptions) {
|
|
107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
const devices = yield store.getDevices(filterOptions);
|
|
109
|
+
// Principal Intelligence: Multi-layered Reliability Filter
|
|
110
|
+
const breaker = typedi_1.Container.get(CircuitBreaker_1.CircuitBreaker);
|
|
111
|
+
return devices.filter((device) => {
|
|
112
|
+
// 1. Host Stability (Circuit Breaker)
|
|
113
|
+
if (breaker.isOpen(device.host))
|
|
114
|
+
return false;
|
|
115
|
+
// 2. Device Health (Proactive Status)
|
|
116
|
+
// Only exclude if healthStatus is explicitly defined and not 'Healthy'
|
|
117
|
+
if (device.healthStatus && device.healthStatus !== 'Healthy') {
|
|
118
|
+
logger_1.default.debug(`[DeviceService] Skipping unhealthy device ${device.udid}: ${device.healthCheckError}`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Find device matching the filter options
|
|
127
|
+
* @param filterOptions IDeviceFilterOptions
|
|
128
|
+
* @returns IDevice | undefined
|
|
129
|
+
*/
|
|
130
|
+
function getDevice(filterOptions) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
const devices = yield getDevices(filterOptions);
|
|
133
|
+
// log.debug(`getDevice devices: ${JSON.stringify(devices)}`);
|
|
134
|
+
if (devices.length === 0) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
return devices[0];
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function updatedAllocatedDevice(device, updateData) {
|
|
143
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
logger_1.default.info(`Updating allocated device: ${device.udid}`);
|
|
145
|
+
yield store.updateDevice(device.udid, device.host, updateData);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function updateDeviceProgress(udid_1, host_1, progress_1) {
|
|
149
|
+
return __awaiter(this, arguments, void 0, function* (udid, host, progress, extra = {}) {
|
|
150
|
+
logger_1.default.debug(`[${udid}] progress: ${progress}`);
|
|
151
|
+
yield store.updateDevice(udid, host, Object.assign({ sessionProgress: progress }, extra));
|
|
152
|
+
// Emit progress update via socket
|
|
153
|
+
typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboard('device_progress', Object.assign({ udid,
|
|
154
|
+
host,
|
|
155
|
+
progress }, extra));
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function updateCmdExecutedTime(sessionId) {
|
|
159
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
160
|
+
yield store.updateDevices({ session_id: sessionId }, (device) => {
|
|
161
|
+
device.lastCmdExecutedAt = new Date().getTime();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Apply user blocking device. Device busy status will not be affected.
|
|
167
|
+
* @param udid string
|
|
168
|
+
* @param host string
|
|
169
|
+
*/
|
|
170
|
+
function userBlockDevice(udid, host) {
|
|
171
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
172
|
+
yield store.updateDevice(udid, host, { userBlocked: true });
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function userUnblockDevice(udid, host) {
|
|
176
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
177
|
+
yield store.updateDevice(udid, host, { userBlocked: false });
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Block device from being allocated to a session. Device busy status will be set to true.
|
|
182
|
+
* @param udid
|
|
183
|
+
* @param host
|
|
184
|
+
*/
|
|
185
|
+
function blockDevice(udid, host, sessionId) {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
yield store.updateDevice(udid, host, {
|
|
188
|
+
busy: true,
|
|
189
|
+
lastCmdExecutedAt: undefined,
|
|
190
|
+
sessionProgress: '',
|
|
191
|
+
session_id: sessionId || null,
|
|
192
|
+
});
|
|
193
|
+
typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboard('device_blocked', {
|
|
194
|
+
udid,
|
|
195
|
+
host,
|
|
196
|
+
session_id: sessionId,
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function unblockDevice(udid, host) {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
202
|
+
yield unblockDeviceMatchingFilter({ udid, host });
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function unblockDeviceMatchingFilter(filter) {
|
|
206
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
207
|
+
const devices = yield store.getDevices(filter);
|
|
208
|
+
if (devices.length > 0) {
|
|
209
|
+
yield Promise.all(devices.map((device) => __awaiter(this, void 0, void 0, function* () {
|
|
210
|
+
const sessionStart = device.sessionStartTime;
|
|
211
|
+
const currentTime = new Date().getTime();
|
|
212
|
+
let utilization = currentTime - sessionStart;
|
|
213
|
+
if (sessionStart === 0)
|
|
214
|
+
utilization = 0;
|
|
215
|
+
const totalUtilization = device.totalUtilizationTimeMilliSec + utilization;
|
|
216
|
+
yield (0, device_utils_1.setUtilizationTime)(device.udid, totalUtilization);
|
|
217
|
+
yield store.updateDevice(device.udid, device.host, {
|
|
218
|
+
session_id: null,
|
|
219
|
+
busy: false,
|
|
220
|
+
lastCmdExecutedAt: null,
|
|
221
|
+
sessionStartTime: 0,
|
|
222
|
+
totalUtilizationTimeMilliSec: totalUtilization,
|
|
223
|
+
newCommandTimeout: null,
|
|
224
|
+
sessionProgress: '',
|
|
225
|
+
});
|
|
226
|
+
logger_1.default.debug(`Unblocked device ${device.udid}`);
|
|
227
|
+
typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboard('device_unblocked', {
|
|
228
|
+
udid: device.udid,
|
|
229
|
+
host: device.host,
|
|
230
|
+
});
|
|
231
|
+
}))).catch((error) => {
|
|
232
|
+
logger_1.default.error(`Unable to unblock device: ${error}`);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Reserve a device for exclusive manual use
|
|
239
|
+
* @param udid Device UDID
|
|
240
|
+
* @param host Device host
|
|
241
|
+
* @param reservedBy Username or identifier
|
|
242
|
+
* @param durationMs Duration in milliseconds
|
|
243
|
+
* @param reason Optional reservation reason
|
|
244
|
+
*/
|
|
245
|
+
function reserveDevice(udid, host, reservedBy, durationMs, reason) {
|
|
246
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
247
|
+
const reservedUntil = Date.now() + durationMs;
|
|
248
|
+
logger_1.default.info(`Reserving device ${udid} for ${reservedBy} until ${new Date(reservedUntil).toISOString()}`);
|
|
249
|
+
yield store.updateDevice(udid, host, {
|
|
250
|
+
reservedBy,
|
|
251
|
+
reservedUntil,
|
|
252
|
+
reservationReason: reason,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Release a device reservation
|
|
258
|
+
* @param udid Device UDID
|
|
259
|
+
* @param host Device host
|
|
260
|
+
*/
|
|
261
|
+
function releaseReservation(udid, host) {
|
|
262
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
263
|
+
logger_1.default.info(`Releasing reservation for device ${udid}`);
|
|
264
|
+
yield store.updateDevice(udid, host, {
|
|
265
|
+
reservedBy: null,
|
|
266
|
+
reservedUntil: null,
|
|
267
|
+
reservationReason: null,
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Check if a device is currently reserved
|
|
273
|
+
* @param device The device to check
|
|
274
|
+
* @returns true if device is reserved and reservation has not expired
|
|
275
|
+
*/
|
|
276
|
+
function isDeviceReserved(device) {
|
|
277
|
+
if (!device.reservedUntil)
|
|
278
|
+
return false;
|
|
279
|
+
return Date.now() < device.reservedUntil;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get all currently reserved devices
|
|
283
|
+
* @returns Array of reserved devices
|
|
284
|
+
*/
|
|
285
|
+
function getReservedDevices() {
|
|
286
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
287
|
+
const allDevices = yield store.getAllDevices();
|
|
288
|
+
return allDevices.filter(isDeviceReserved);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Clean up expired reservations
|
|
293
|
+
*/
|
|
294
|
+
function cleanExpiredReservations() {
|
|
295
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
const allDevices = yield store.getAllDevices();
|
|
297
|
+
const expiredReservations = allDevices.filter((device) => {
|
|
298
|
+
return device.reservedUntil && Date.now() >= device.reservedUntil;
|
|
299
|
+
});
|
|
300
|
+
for (const device of expiredReservations) {
|
|
301
|
+
logger_1.default.info(`Reservation expired for device ${device.udid}, releasing...`);
|
|
302
|
+
yield releaseReservation(device.udid, device.host);
|
|
303
|
+
}
|
|
304
|
+
if (expiredReservations.length > 0) {
|
|
305
|
+
logger_1.default.info(`Cleaned ${expiredReservations.length} expired reservations`);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Update tags for a device
|
|
311
|
+
* @param udid string
|
|
312
|
+
* @param host string
|
|
313
|
+
* @param tags string[]
|
|
314
|
+
*/
|
|
315
|
+
function updateDeviceTags(udid, host, tags) {
|
|
316
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
317
|
+
logger_1.default.info(`Updating tags for device ${udid}: ${tags.join(', ')}`);
|
|
318
|
+
yield store.updateDevice(udid, host, { tags });
|
|
319
|
+
});
|
|
320
|
+
}
|