@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.
Files changed (228) hide show
  1. package/README.md +446 -0
  2. package/lib/package.json +207 -0
  3. package/lib/public/assets/Layouts-7IT8aFLI.js +11 -0
  4. package/lib/public/assets/Layouts-DPMls9vh.css +1 -0
  5. package/lib/public/assets/ai-settings-BbnfgdEx.js +11 -0
  6. package/lib/public/assets/apps-CRMrI4_p.js +16 -0
  7. package/lib/public/assets/apps-CcM77dgg.css +1 -0
  8. package/lib/public/assets/badge-B1nKs8zj.css +1 -0
  9. package/lib/public/assets/badge-CSvl5xIU.js +11 -0
  10. package/lib/public/assets/button-CJlKn4PZ.css +1 -0
  11. package/lib/public/assets/button-CvLaGFYj.js +26 -0
  12. package/lib/public/assets/calendar-6w-D6Oaw.js +6 -0
  13. package/lib/public/assets/clock-DcdeWBPr.js +6 -0
  14. package/lib/public/assets/cpu-DiSoXT9n.js +6 -0
  15. package/lib/public/assets/device-explorer-CajM63OJ.js +193 -0
  16. package/lib/public/assets/device-explorer-CxdUAoTL.css +1 -0
  17. package/lib/public/assets/index-ByQwMN5T.js +174 -0
  18. package/lib/public/assets/index-C1DBaoSh.js +1 -0
  19. package/lib/public/assets/index-qzCez_kk.css +1 -0
  20. package/lib/public/assets/lock-B23ibZmo.js +6 -0
  21. package/lib/public/assets/maintenance-settings-CirzA6yG.js +6 -0
  22. package/lib/public/assets/mouse-pointer-2-Cz76SHFb.js +6 -0
  23. package/lib/public/assets/plus-BBwlIevt.js +6 -0
  24. package/lib/public/assets/session-dashboard-C2k7FFv_.css +1 -0
  25. package/lib/public/assets/session-dashboard-HPDtwPOZ.js +62 -0
  26. package/lib/public/assets/settings-DrZsZwdc.js +1 -0
  27. package/lib/public/assets/trash-2-DQpvzJec.js +6 -0
  28. package/lib/public/assets/useSocket-Dxsqae2a.js +16 -0
  29. package/lib/public/assets/webhook-settings-CDPgsgkb.css +1 -0
  30. package/lib/public/assets/webhook-settings-Cp-B4Nrw.js +1 -0
  31. package/lib/public/assets/zap-DovP6iow.js +6 -0
  32. package/lib/public/favicon.ico +0 -0
  33. package/lib/public/favicon.png +0 -0
  34. package/lib/public/favicon.svg +9 -0
  35. package/lib/public/index.html +46 -0
  36. package/lib/public/logo.svg +17 -0
  37. package/lib/public/logo192.png +0 -0
  38. package/lib/public/logo512.png +0 -0
  39. package/lib/public/manifest.json +25 -0
  40. package/lib/public/robots.txt +3 -0
  41. package/lib/schema.json +348 -0
  42. package/lib/src/InternalHttpClient.js +212 -0
  43. package/lib/src/PluginContext.js +29 -0
  44. package/lib/src/XenonCapabilityManager.js +199 -0
  45. package/lib/src/app/index.js +167 -0
  46. package/lib/src/app/routers/apps.js +79 -0
  47. package/lib/src/app/routers/config.js +131 -0
  48. package/lib/src/app/routers/control.js +835 -0
  49. package/lib/src/app/routers/dashboard.js +301 -0
  50. package/lib/src/app/routers/grid.js +352 -0
  51. package/lib/src/app/routers/reservation.js +190 -0
  52. package/lib/src/app/routers/webhook.js +83 -0
  53. package/lib/src/app/swagger-docs.js +203 -0
  54. package/lib/src/app/swagger.js +366 -0
  55. package/lib/src/chromeUtils.js +148 -0
  56. package/lib/src/commands/handle.js +19 -0
  57. package/lib/src/commands/index.js +8 -0
  58. package/lib/src/config.js +73 -0
  59. package/lib/src/dashboard/asset-manager.js +84 -0
  60. package/lib/src/dashboard/commands.js +284 -0
  61. package/lib/src/dashboard/event-manager.js +699 -0
  62. package/lib/src/dashboard/services/app-service.js +134 -0
  63. package/lib/src/dashboard/services/failure-analysis-service.js +173 -0
  64. package/lib/src/dashboard/services/session-service.js +113 -0
  65. package/lib/src/data-service/CircuitBreaker.js +83 -0
  66. package/lib/src/data-service/config-service.js +155 -0
  67. package/lib/src/data-service/db.js +122 -0
  68. package/lib/src/data-service/device-service.js +320 -0
  69. package/lib/src/data-service/device-store.interface.js +2 -0
  70. package/lib/src/data-service/device-store.js +345 -0
  71. package/lib/src/data-service/pending-sessions-service.js +25 -0
  72. package/lib/src/data-service/pluginArgs.js +25 -0
  73. package/lib/src/data-service/prisma-service.js +31 -0
  74. package/lib/src/data-service/prisma-store.js +385 -0
  75. package/lib/src/data-service/queue-service.js +150 -0
  76. package/lib/src/data-service/web-config-service.js +130 -0
  77. package/lib/src/device-managers/AndroidDeviceManager.js +1155 -0
  78. package/lib/src/device-managers/ChromeDriverManager.js +68 -0
  79. package/lib/src/device-managers/HealthMonitorService.js +325 -0
  80. package/lib/src/device-managers/IOSDeviceManager.js +351 -0
  81. package/lib/src/device-managers/NodeDevices.js +82 -0
  82. package/lib/src/device-managers/android/AndroidStreamService.js +370 -0
  83. package/lib/src/device-managers/android/DeviceLockManager.js +45 -0
  84. package/lib/src/device-managers/cloud/CapabilityManager.js +26 -0
  85. package/lib/src/device-managers/cloud/Devices.js +86 -0
  86. package/lib/src/device-managers/iOSTracker.js +44 -0
  87. package/lib/src/device-managers/index.js +89 -0
  88. package/lib/src/device-managers/ios/IOSDiscoveryService.js +268 -0
  89. package/lib/src/device-managers/ios/IOSStreamService.js +893 -0
  90. package/lib/src/device-managers/ios/WDAClient.js +866 -0
  91. package/lib/src/device-utils.js +663 -0
  92. package/lib/src/enums/Capabilities.js +8 -0
  93. package/lib/src/enums/Cloud.js +11 -0
  94. package/lib/src/enums/Platform.js +9 -0
  95. package/lib/src/enums/SessionType.js +9 -0
  96. package/lib/src/enums/SocketEvents.js +15 -0
  97. package/lib/src/helpers/UniversalMjpegProxy.js +273 -0
  98. package/lib/src/helpers/index.js +229 -0
  99. package/lib/src/index.js +95 -0
  100. package/lib/src/interceptors/CommandInterceptor.js +524 -0
  101. package/lib/src/interfaces/ICloudManager.js +2 -0
  102. package/lib/src/interfaces/IDevice.js +2 -0
  103. package/lib/src/interfaces/IDeviceFilterOptions.js +2 -0
  104. package/lib/src/interfaces/IDeviceManager.js +2 -0
  105. package/lib/src/interfaces/IOptions.js +2 -0
  106. package/lib/src/interfaces/IPluginArgs.js +55 -0
  107. package/lib/src/interfaces/ISessionCapability.js +2 -0
  108. package/lib/src/logger.js +225 -0
  109. package/lib/src/plugin.js +244 -0
  110. package/lib/src/prisma.js +12 -0
  111. package/lib/src/profiling/AndroidAppProfiler.js +213 -0
  112. package/lib/src/proxy/wd-command-proxy.js +221 -0
  113. package/lib/src/scripts/generate-database-migration.js +59 -0
  114. package/lib/src/scripts/initialize-database.js +55 -0
  115. package/lib/src/scripts/install-go-ios.js +66 -0
  116. package/lib/src/scripts/prepare-prisma.js +89 -0
  117. package/lib/src/services/AICommandService.js +143 -0
  118. package/lib/src/services/AIService.js +466 -0
  119. package/lib/src/services/CleanupService.js +141 -0
  120. package/lib/src/services/EventBus.js +74 -0
  121. package/lib/src/services/InspectorService.js +395 -0
  122. package/lib/src/services/MetricsService.js +134 -0
  123. package/lib/src/services/NetworkConditioningService.js +173 -0
  124. package/lib/src/services/NotificationService.js +163 -0
  125. package/lib/src/services/RequestLogService.js +252 -0
  126. package/lib/src/services/ResourceIsolationService.js +122 -0
  127. package/lib/src/services/SecurityService.js +120 -0
  128. package/lib/src/services/ServerManager.js +284 -0
  129. package/lib/src/services/SessionHeartbeatService.js +158 -0
  130. package/lib/src/services/SessionLifecycleService.js +572 -0
  131. package/lib/src/services/SocketClient.js +71 -0
  132. package/lib/src/services/SocketServer.js +87 -0
  133. package/lib/src/services/TracingService.js +132 -0
  134. package/lib/src/services/VideoPipelineService.js +220 -0
  135. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +333 -0
  136. package/lib/src/services/healing/HealEtalonService.js +98 -0
  137. package/lib/src/services/healing/HealedLocatorGenerator.js +132 -0
  138. package/lib/src/services/healing/HealingOrchestrator.js +165 -0
  139. package/lib/src/services/healing/LlmHealingProvider.js +77 -0
  140. package/lib/src/services/healing/OcrHealingProvider.js +119 -0
  141. package/lib/src/services/healing/ResilioTreeHealingProvider.js +100 -0
  142. package/lib/src/services/healing/VisualAiHealingProvider.js +90 -0
  143. package/lib/src/services/healing/types.js +12 -0
  144. package/lib/src/services/omni-vision/OmniVisionService.js +718 -0
  145. package/lib/src/services/omni-vision/VisionAssertionService.js +68 -0
  146. package/lib/src/sessions/CloudSession.js +42 -0
  147. package/lib/src/sessions/LocalSession.js +313 -0
  148. package/lib/src/sessions/RemoteSession.js +287 -0
  149. package/lib/src/sessions/SessionManager.js +238 -0
  150. package/lib/src/sessions/XenonSession.js +44 -0
  151. package/lib/src/types/CLIArgs.js +2 -0
  152. package/lib/src/types/CloudArgs.js +2 -0
  153. package/lib/src/types/CloudSchema.js +131 -0
  154. package/lib/src/types/DeviceType.js +2 -0
  155. package/lib/src/types/DeviceUpdate.js +2 -0
  156. package/lib/src/types/IOSDevice.js +2 -0
  157. package/lib/src/types/Platform.js +2 -0
  158. package/lib/src/types/SessionStatus.js +11 -0
  159. package/lib/src/validators/CapabilityValidator.js +93 -0
  160. package/lib/test/e2e/android/conf.spec.js +43 -0
  161. package/lib/test/e2e/android/conf2.spec.js +44 -0
  162. package/lib/test/e2e/android/conf3.spec.js +44 -0
  163. package/lib/test/e2e/e2ehelper.js +113 -0
  164. package/lib/test/e2e/hubnode/forward-request.spec.js +224 -0
  165. package/lib/test/e2e/hubnode/hubnode.spec.js +214 -0
  166. package/lib/test/e2e/ios/conf1.spec.js +39 -0
  167. package/lib/test/e2e/ios/conf2.spec.js +39 -0
  168. package/lib/test/e2e/plugin-harness.js +236 -0
  169. package/lib/test/e2e/plugin.spec.js +97 -0
  170. package/lib/test/e2e/telemetry_verification.spec.js +83 -0
  171. package/lib/test/e2e/video-recording-test.spec.js +63 -0
  172. package/lib/test/helpers/test-container.js +112 -0
  173. package/lib/test/integration/androidDevices.spec.js +137 -0
  174. package/lib/test/integration/cliArgs.js +73 -0
  175. package/lib/test/integration/ios/01iOSSimulator.spec.js +291 -0
  176. package/lib/test/integration/ios/02iOSDevices.spec.js +75 -0
  177. package/lib/test/integration/testHelpers.js +74 -0
  178. package/lib/test/unit/AndroidDeviceManager.spec.js +178 -0
  179. package/lib/test/unit/ChromeDriverManager.spec.js +26 -0
  180. package/lib/test/unit/CleanupService.spec.js +21 -0
  181. package/lib/test/unit/DeviceModel.spec.js +157 -0
  182. package/lib/test/unit/FuzzyXmlHealingProvider.test.js +294 -0
  183. package/lib/test/unit/GetAdbOriginal.js +42 -0
  184. package/lib/test/unit/HealingCascade.test.js +128 -0
  185. package/lib/test/unit/IOSDeviceManager.spec.js +261 -0
  186. package/lib/test/unit/RemoteIOs.spec.js +78 -0
  187. package/lib/test/unit/ResilioTreeHealingProvider.test.js +96 -0
  188. package/lib/test/unit/commands.spec.js +27 -0
  189. package/lib/test/unit/config.spec.js +27 -0
  190. package/lib/test/unit/device-service.spec.js +307 -0
  191. package/lib/test/unit/device-utils.spec.js +313 -0
  192. package/lib/test/unit/fixtures/device.config.js +4 -0
  193. package/lib/test/unit/fixtures/devices.js +89 -0
  194. package/lib/test/unit/helpers.spec.js +62 -0
  195. package/lib/test/unit/omni-vision.spec.js +100 -0
  196. package/lib/test/unit/plugin.spec.js +133 -0
  197. package/lib/tsconfig.tsbuildinfo +1 -0
  198. package/package.json +207 -0
  199. package/prisma/data.db +0 -0
  200. package/prisma/dev.db +0 -0
  201. package/prisma/dev.db-journal +0 -0
  202. package/prisma/migrations/20231011074725_initial_tables/migration.sql +47 -0
  203. package/prisma/migrations/20231226115334_update_session_log/migration.sql +2 -0
  204. package/prisma/migrations/20251204113710_add_video_recording_enabled/migration.sql +29 -0
  205. package/prisma/migrations/20251204132449_add_log_table/migration.sql +11 -0
  206. package/prisma/migrations/20251205050111_add_profiling_support/migration.sql +47 -0
  207. package/prisma/migrations/20251205050947_add_is_error_field/migration.sql +24 -0
  208. package/prisma/migrations/20260126201337_add_app_model/migration.sql +18 -0
  209. package/prisma/migrations/20260130115722_add_performance_trace_and_xenon_sync/migration.sql +2 -0
  210. package/prisma/migrations/20260130135114_add_device_models/migration.sql +57 -0
  211. package/prisma/migrations/20260130140655_make_systemport_optional/migration.sql +45 -0
  212. package/prisma/migrations/20260130140932_make_device_fields_optional/migration.sql +45 -0
  213. package/prisma/migrations/20260130141040_final_schema_fix/migration.sql +45 -0
  214. package/prisma/migrations/20260130143234_add_device_health_fields/migration.sql +4 -0
  215. package/prisma/migrations/20260130144921_add_failure_category/migration.sql +2 -0
  216. package/prisma/migrations/20260131151456_add_webhook_config/migration.sql +10 -0
  217. package/prisma/migrations/20260201094507_add_device_tags/migration.sql +11 -0
  218. package/prisma/migrations/20260201103410_add_managed_process/migration.sql +15 -0
  219. package/prisma/migrations/20260201140637_add_web_config/migration.sql +22 -0
  220. package/prisma/migrations/20260201162232_add_session_progress/migration.sql +2 -0
  221. package/prisma/migrations/20260201174231_add_total_healed_count/migration.sql +2 -0
  222. package/prisma/migrations/migration_lock.toml +3 -0
  223. package/prisma/schema.prisma +210 -0
  224. package/schema.json +348 -0
  225. package/scripts/build-xenon.sh +32 -0
  226. package/scripts/dev/debug-gemini.ts +44 -0
  227. package/scripts/generate-types-from-schema.js +86 -0
  228. 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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });