@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,385 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.PrismaHealEtalonStore = exports.PrismaCLIArgsStore = exports.PrismaPendingSessionStore = exports.PrismaDeviceStore = void 0;
49
+ const prisma_service_1 = require("./prisma-service");
50
+ const typedi_1 = require("typedi");
51
+ const semver = __importStar(require("semver"));
52
+ const logger_1 = __importDefault(require("../logger"));
53
+ class PrismaDeviceStore {
54
+ constructor() {
55
+ this.prisma = typedi_1.Container.get(prisma_service_1.PrismaService);
56
+ }
57
+ toIDevice(device) {
58
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
59
+ return Object.assign(Object.assign({}, device), { cloud: device.cloud ? JSON.parse(device.cloud) : undefined, capability: device.capability ? JSON.parse(device.capability) : undefined, chromeDriverPath: device.chromeDriverPath ? JSON.parse(device.chromeDriverPath) : undefined, tags: device.tags ? JSON.parse(device.tags) : undefined, platform: (device.platform || 'android'), name: device.name || 'unknown', state: device.state || 'available', sdk: device.sdk || 'unknown', deviceType: device.deviceType || 'real', busy: (_a = device.busy) !== null && _a !== void 0 ? _a : false, userBlocked: (_b = device.userBlocked) !== null && _b !== void 0 ? _b : false, realDevice: (_c = device.realDevice) !== null && _c !== void 0 ? _c : true, lastHealthCheckAt: (_d = device.lastHealthCheckAt) !== null && _d !== void 0 ? _d : undefined, healthStatus: (_e = device.healthStatus) !== null && _e !== void 0 ? _e : 'Healthy', healthCheckError: (_f = device.healthCheckError) !== null && _f !== void 0 ? _f : undefined, batteryLevel: (_g = device.batteryLevel) !== null && _g !== void 0 ? _g : undefined, thermalStatus: (_h = device.thermalStatus) !== null && _h !== void 0 ? _h : undefined, storageFree: (_j = device.storageFree) !== null && _j !== void 0 ? _j : undefined, sessionProgress: (_k = device.sessionProgress) !== null && _k !== void 0 ? _k : '', totalHealedCount: (_l = device.totalHealedCount) !== null && _l !== void 0 ? _l : 0 });
60
+ }
61
+ fromIDevice(device) {
62
+ const data = Object.assign({}, device);
63
+ if (data.cloud && typeof data.cloud === 'object')
64
+ data.cloud = JSON.stringify(data.cloud);
65
+ if (data.capability && typeof data.capability === 'object')
66
+ data.capability = JSON.stringify(data.capability);
67
+ if (data.chromeDriverPath && typeof data.chromeDriverPath === 'object')
68
+ data.chromeDriverPath = JSON.stringify(data.chromeDriverPath);
69
+ if (data.tags && Array.isArray(data.tags))
70
+ data.tags = JSON.stringify(data.tags);
71
+ return data;
72
+ }
73
+ getAllDevices() {
74
+ return __awaiter(this, void 0, void 0, function* () {
75
+ const devices = yield this.prisma.device.findMany();
76
+ return devices.map((d) => this.toIDevice(d));
77
+ });
78
+ }
79
+ getDevices(filterOptions) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ const where = {};
82
+ if (filterOptions.platform) {
83
+ where.platform = filterOptions.platform;
84
+ }
85
+ if (filterOptions.busy !== undefined) {
86
+ where.busy = filterOptions.busy;
87
+ }
88
+ if (filterOptions.offline !== undefined) {
89
+ where.offline = filterOptions.offline;
90
+ }
91
+ if (filterOptions.userBlocked !== undefined) {
92
+ where.userBlocked = filterOptions.userBlocked;
93
+ }
94
+ if (filterOptions.udid) {
95
+ if (Array.isArray(filterOptions.udid)) {
96
+ if (filterOptions.udid.length > 0) {
97
+ where.udid = { in: filterOptions.udid };
98
+ }
99
+ }
100
+ else {
101
+ where.udid = filterOptions.udid;
102
+ }
103
+ }
104
+ if (filterOptions.deviceType) {
105
+ where.deviceType = filterOptions.deviceType;
106
+ }
107
+ if (filterOptions.session_id && typeof filterOptions.session_id === 'string') {
108
+ where.session_id = filterOptions.session_id;
109
+ }
110
+ if (filterOptions.filterByHost) {
111
+ where.host = { contains: filterOptions.filterByHost };
112
+ }
113
+ // 1. Fetch narrowed result set from Database
114
+ const devices = yield this.prisma.device.findMany({ where });
115
+ let results = devices.map((d) => this.toIDevice(d));
116
+ // 2. Apply complex filters (Semver, Tags) in memory on the narrowed set
117
+ if (filterOptions.platformVersion) {
118
+ const coercedPlatformVersion = semver.coerce(filterOptions.platformVersion);
119
+ results = results.filter((obj) => {
120
+ const coercedSDK = semver.coerce(obj.sdk);
121
+ return !!(coercedSDK &&
122
+ coercedPlatformVersion &&
123
+ semver.eq(coercedSDK, coercedPlatformVersion));
124
+ });
125
+ }
126
+ if (filterOptions.minSDK) {
127
+ const coercedMinSDK = semver.coerce(filterOptions.minSDK);
128
+ if (coercedMinSDK) {
129
+ results = results.filter((obj) => {
130
+ const coercedSDK = semver.coerce(obj.sdk);
131
+ return !!(coercedSDK && semver.gte(coercedSDK, coercedMinSDK));
132
+ });
133
+ }
134
+ }
135
+ if (filterOptions.maxSDK) {
136
+ const coercedMaxSDK = semver.coerce(filterOptions.maxSDK);
137
+ if (coercedMaxSDK) {
138
+ results = results.filter((obj) => {
139
+ const coercedSDK = semver.coerce(obj.sdk);
140
+ return !!(coercedSDK && semver.lte(coercedSDK, coercedMaxSDK));
141
+ });
142
+ }
143
+ }
144
+ if (filterOptions.tags && filterOptions.tags.length > 0) {
145
+ results = results.filter((d) => {
146
+ if (!d.tags)
147
+ return false;
148
+ return filterOptions.tags.every((tag) => d.tags.includes(tag));
149
+ });
150
+ }
151
+ return results;
152
+ });
153
+ }
154
+ updatedAllocatedDevice(device, updateData) {
155
+ return __awaiter(this, void 0, void 0, function* () {
156
+ yield this.updateDevice(device.udid, device.host, updateData);
157
+ });
158
+ }
159
+ updateDevice(udid, host, update) {
160
+ return __awaiter(this, void 0, void 0, function* () {
161
+ const data = this.fromIDevice(update);
162
+ const result = yield this.prisma.device.updateMany({
163
+ where: { udid, host },
164
+ data,
165
+ });
166
+ logger_1.default.debug(`[PrismaStore] Update device ${udid} at ${host}: ${result.count} records affected`);
167
+ });
168
+ }
169
+ updateDevices(filter, updateFn) {
170
+ return __awaiter(this, void 0, void 0, function* () {
171
+ const devices = yield this.prisma.device.findMany({ where: filter });
172
+ for (const d of devices) {
173
+ const idv = this.toIDevice(d);
174
+ updateFn(idv);
175
+ yield this.prisma.device.update({
176
+ where: { udid_host: { udid: d.udid, host: d.host } },
177
+ data: this.fromIDevice(idv),
178
+ });
179
+ }
180
+ });
181
+ }
182
+ addDevices(devices) {
183
+ return __awaiter(this, void 0, void 0, function* () {
184
+ const added = [];
185
+ for (const device of devices) {
186
+ const data = this.fromIDevice(device);
187
+ // Use upsert to avoid race conditions and unique constraint errors
188
+ const d = yield this.prisma.device.upsert({
189
+ where: { udid_host: { udid: device.udid, host: device.host } },
190
+ update: data,
191
+ create: data,
192
+ });
193
+ added.push(this.toIDevice(d));
194
+ }
195
+ return added;
196
+ });
197
+ }
198
+ removeDevices(filter) {
199
+ return __awaiter(this, void 0, void 0, function* () {
200
+ // Principal Fix: Support partial host matching (e.g., "192.168.0.100" should match "http://192.168.0.100:4723")
201
+ // This is necessary because the device tracker passes the raw IP, not the full URL.
202
+ const whereClause = {};
203
+ if (filter.udid) {
204
+ whereClause.udid = filter.udid;
205
+ }
206
+ if (filter.host) {
207
+ // Use Prisma's `contains` for partial matching if host doesn't look like a full URL
208
+ if (filter.host.startsWith('http://') || filter.host.startsWith('https://')) {
209
+ whereClause.host = filter.host;
210
+ }
211
+ else {
212
+ whereClause.host = { contains: filter.host };
213
+ }
214
+ }
215
+ yield this.prisma.device.deleteMany({ where: whereClause });
216
+ });
217
+ }
218
+ clearStorage() {
219
+ return __awaiter(this, void 0, void 0, function* () {
220
+ yield this.prisma.device.deleteMany();
221
+ });
222
+ }
223
+ findDevice(filter) {
224
+ return __awaiter(this, void 0, void 0, function* () {
225
+ const device = yield this.prisma.device.findFirst({ where: filter });
226
+ return device ? this.toIDevice(device) : null;
227
+ });
228
+ }
229
+ findDevices(filter) {
230
+ return __awaiter(this, void 0, void 0, function* () {
231
+ const devices = yield this.prisma.device.findMany({ where: filter });
232
+ return devices.map((d) => this.toIDevice(d));
233
+ });
234
+ }
235
+ findAndLockDevice(filterOptions) {
236
+ return __awaiter(this, void 0, void 0, function* () {
237
+ // 1. Get candidate devices that match the filters and are NOT busy
238
+ const candidates = yield this.getDevices(Object.assign(Object.assign({}, filterOptions), { busy: false }));
239
+ // 2. Attempt to atomically lock them one by one
240
+ for (const device of candidates) {
241
+ const result = yield this.prisma.device.updateMany({
242
+ where: {
243
+ udid: device.udid,
244
+ host: device.host,
245
+ busy: false, // Double check it's still free
246
+ offline: false,
247
+ },
248
+ data: {
249
+ busy: true,
250
+ lastCmdExecutedAt: Date.now(),
251
+ sessionStartTime: Date.now(),
252
+ },
253
+ });
254
+ if (result.count === 1) {
255
+ logger_1.default.info(`[PrismaStore] Successfully locked device ${device.udid} atomically`);
256
+ // Fetch the latest state to return
257
+ const lockedDevice = yield this.prisma.device.findUnique({
258
+ where: { udid_host: { udid: device.udid, host: device.host } },
259
+ });
260
+ return lockedDevice ? this.toIDevice(lockedDevice) : null;
261
+ }
262
+ }
263
+ return null;
264
+ });
265
+ }
266
+ resetMetrics() {
267
+ return __awaiter(this, void 0, void 0, function* () {
268
+ yield this.prisma.device.updateMany({
269
+ data: {
270
+ totalHealedCount: 0,
271
+ },
272
+ });
273
+ });
274
+ }
275
+ }
276
+ exports.PrismaDeviceStore = PrismaDeviceStore;
277
+ class PrismaPendingSessionStore {
278
+ constructor() {
279
+ this.prisma = typedi_1.Container.get(prisma_service_1.PrismaService);
280
+ }
281
+ addPendingSession(capability) {
282
+ return __awaiter(this, void 0, void 0, function* () {
283
+ yield this.prisma.pendingSession.upsert({
284
+ where: { capability_id: capability.capability_id },
285
+ update: {
286
+ capability: JSON.stringify(capability),
287
+ createdAt: capability.createdAt || Date.now(),
288
+ },
289
+ create: {
290
+ capability_id: capability.capability_id,
291
+ capability: JSON.stringify(capability),
292
+ createdAt: capability.createdAt || Date.now(),
293
+ },
294
+ });
295
+ });
296
+ }
297
+ removePendingSession(sessionCapabilityId) {
298
+ return __awaiter(this, void 0, void 0, function* () {
299
+ try {
300
+ yield this.prisma.pendingSession.delete({
301
+ where: { capability_id: sessionCapabilityId },
302
+ });
303
+ }
304
+ catch (e) {
305
+ // ignore if not found
306
+ }
307
+ });
308
+ }
309
+ getAllPendingSessions() {
310
+ return __awaiter(this, void 0, void 0, function* () {
311
+ const sessions = yield this.prisma.pendingSession.findMany();
312
+ return sessions.map((s) => JSON.parse(s.capability));
313
+ });
314
+ }
315
+ remove(session) {
316
+ return __awaiter(this, void 0, void 0, function* () {
317
+ try {
318
+ yield this.prisma.pendingSession.delete({
319
+ where: { capability_id: session.capability_id },
320
+ });
321
+ }
322
+ catch (e) {
323
+ // ignore if not found
324
+ }
325
+ });
326
+ }
327
+ }
328
+ exports.PrismaPendingSessionStore = PrismaPendingSessionStore;
329
+ class PrismaCLIArgsStore {
330
+ constructor() {
331
+ this.prisma = typedi_1.Container.get(prisma_service_1.PrismaService);
332
+ }
333
+ addCLIArgs(args) {
334
+ return __awaiter(this, void 0, void 0, function* () {
335
+ yield this.prisma.cLIArgs.create({
336
+ data: {
337
+ args: JSON.stringify(args),
338
+ },
339
+ });
340
+ });
341
+ }
342
+ getCLIArgs() {
343
+ return __awaiter(this, void 0, void 0, function* () {
344
+ const entries = yield this.prisma.cLIArgs.findMany();
345
+ return entries.map((e) => JSON.parse(e.args));
346
+ });
347
+ }
348
+ }
349
+ exports.PrismaCLIArgsStore = PrismaCLIArgsStore;
350
+ class PrismaHealEtalonStore {
351
+ constructor() {
352
+ this.prisma = typedi_1.Container.get(prisma_service_1.PrismaService);
353
+ }
354
+ saveSignature(etalon) {
355
+ return __awaiter(this, void 0, void 0, function* () {
356
+ yield this.prisma.locatorEtalon.upsert({
357
+ where: { selector: etalon.selector },
358
+ update: {
359
+ strategy: etalon.strategy,
360
+ attributes: JSON.stringify(etalon.attributes),
361
+ nodeName: etalon.nodeName,
362
+ lastSeen: new Date(),
363
+ },
364
+ create: {
365
+ selector: etalon.selector,
366
+ strategy: etalon.strategy,
367
+ attributes: JSON.stringify(etalon.attributes),
368
+ nodeName: etalon.nodeName,
369
+ lastSeen: new Date(),
370
+ },
371
+ });
372
+ });
373
+ }
374
+ getSignature(selector) {
375
+ return __awaiter(this, void 0, void 0, function* () {
376
+ const etalon = yield this.prisma.locatorEtalon.findUnique({
377
+ where: { selector },
378
+ });
379
+ if (!etalon)
380
+ return null;
381
+ return Object.assign(Object.assign({}, etalon), { attributes: JSON.parse(etalon.attributes), lastSeen: etalon.lastSeen.getTime() });
382
+ });
383
+ }
384
+ }
385
+ exports.PrismaHealEtalonStore = PrismaHealEtalonStore;
@@ -0,0 +1,150 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.QueueService = void 0;
25
+ const device_store_1 = require("./device-store");
26
+ const prisma_service_1 = require("./prisma-service");
27
+ const logger_1 = __importDefault(require("../logger"));
28
+ const typedi_1 = require("typedi");
29
+ let QueueService = class QueueService {
30
+ constructor(prisma) {
31
+ this.prisma = prisma;
32
+ this.deviceStore = device_store_1.DeviceStoreFactory.getStore();
33
+ this.pendingStore = device_store_1.DeviceStoreFactory.getPendingSessionStore();
34
+ }
35
+ /**
36
+ * Calculates the average session duration for a platform in milliseconds
37
+ * Defaults to 5 minutes (300,000 ms) if no history exists
38
+ */
39
+ getAverageSessionDuration(platform) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ try {
42
+ const recentSessions = yield this.prisma.session.findMany({
43
+ where: {
44
+ device_platform: platform.toLowerCase(),
45
+ endTime: { not: null },
46
+ },
47
+ orderBy: {
48
+ endTime: 'desc',
49
+ },
50
+ take: 20,
51
+ select: {
52
+ startTime: true,
53
+ endTime: true,
54
+ },
55
+ });
56
+ if (recentSessions.length === 0) {
57
+ return 300000; // 5 minutes default
58
+ }
59
+ const totalDuration = recentSessions.reduce((acc, session) => {
60
+ const duration = session.endTime.getTime() - session.startTime.getTime();
61
+ return acc + duration;
62
+ }, 0);
63
+ return Math.floor(totalDuration / recentSessions.length);
64
+ }
65
+ catch (e) {
66
+ logger_1.default.error(`Error calculating average session duration: ${e}`);
67
+ return 300000;
68
+ }
69
+ });
70
+ }
71
+ /**
72
+ * Returns queue status for a specific pending capability
73
+ */
74
+ getQueueStatus(capabilityId) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ var _a;
77
+ logger_1.default.info(`[QueueService] Fetching queue status for capability ID: ${capabilityId}`);
78
+ const allPending = yield this.pendingStore.getAllPendingSessions();
79
+ const sortedPending = allPending.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
80
+ const index = sortedPending.findIndex((s) => s.capability_id === capabilityId);
81
+ if (index === -1)
82
+ return null;
83
+ const request = sortedPending[index];
84
+ const platform = (((_a = request.alwaysMatch) === null || _a === void 0 ? void 0 : _a.platformName) ||
85
+ request.platformName ||
86
+ 'any').toLowerCase();
87
+ // Find devices that could potentially handle this request
88
+ const allDevices = yield this.deviceStore.getAllDevices();
89
+ const matchedDevices = allDevices.filter((d) => platform === 'any' || d.platform.toLowerCase() === platform);
90
+ const availableDevices = matchedDevices.filter((d) => !d.busy && !d.userBlocked && !d.offline);
91
+ // Position in terms of platform-specific queue
92
+ const platformSortedPending = sortedPending.filter((s) => {
93
+ var _a;
94
+ const sPlatform = (((_a = s.alwaysMatch) === null || _a === void 0 ? void 0 : _a.platformName) || s.platformName || 'any').toLowerCase();
95
+ return platform === 'any' || sPlatform === platform || sPlatform === 'any';
96
+ });
97
+ const platformIndex = platformSortedPending.findIndex((s) => s.capability_id === capabilityId);
98
+ const position = platformIndex + 1;
99
+ // ETA Calculation:
100
+ // We assume devices are occupied and will become free at a rate determined by AvgDuration.
101
+ const avgDuration = yield this.getAverageSessionDuration(platform);
102
+ // Simple heuristic: Wait time = (Position / Matched Devices) * AvgDuration
103
+ // If we have 10 devices and user is #10, wait time is ~AvgDuration / 10 if all were busy.
104
+ // However, if some are free, wait time is 0.
105
+ // For simplicity when queued: (Position / Max(1, MatchedDevices)) * AvgDuration
106
+ const matchedCount = Math.max(1, matchedDevices.length);
107
+ const etaInMs = availableDevices.length > 0 && position === 1
108
+ ? 0
109
+ : Math.floor((position / matchedCount) * avgDuration);
110
+ return {
111
+ position,
112
+ totalInQueue: platformSortedPending.length,
113
+ etaInMs,
114
+ platform,
115
+ matchedDevicesCount: matchedDevices.length,
116
+ availableDevicesCount: availableDevices.length,
117
+ };
118
+ });
119
+ }
120
+ /**
121
+ * Returns a high-level summary of the entire queue
122
+ */
123
+ getQueueSummary() {
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ logger_1.default.info('[QueueService] Calculating global queue summary');
126
+ const allPending = yield this.pendingStore.getAllPendingSessions();
127
+ const platforms = [
128
+ ...new Set(allPending.map((s) => { var _a; return (((_a = s.alwaysMatch) === null || _a === void 0 ? void 0 : _a.platformName) || s.platformName || 'any').toLowerCase(); })),
129
+ ];
130
+ const summary = {
131
+ total: allPending.length,
132
+ byPlatform: {},
133
+ };
134
+ for (const p of platforms) {
135
+ const pPending = allPending.filter((s) => { var _a; return (((_a = s.alwaysMatch) === null || _a === void 0 ? void 0 : _a.platformName) || s.platformName || 'any').toLowerCase() === p; });
136
+ const avgDuration = yield this.getAverageSessionDuration(p);
137
+ summary.byPlatform[p] = {
138
+ count: pPending.length,
139
+ avgDurationMs: avgDuration,
140
+ };
141
+ }
142
+ return summary;
143
+ });
144
+ }
145
+ };
146
+ exports.QueueService = QueueService;
147
+ exports.QueueService = QueueService = __decorate([
148
+ (0, typedi_1.Service)(),
149
+ __metadata("design:paramtypes", [prisma_service_1.PrismaService])
150
+ ], QueueService);
@@ -0,0 +1,130 @@
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.WebConfigService = void 0;
19
+ const prisma_1 = require("../prisma");
20
+ const typedi_1 = require("typedi");
21
+ let WebConfigService = class WebConfigService {
22
+ constructor() {
23
+ this.CONFIG_ID = 'global';
24
+ }
25
+ getConfig() {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ const configs = yield prisma_1.prisma.webConfig.findMany({
28
+ where: { id: this.CONFIG_ID },
29
+ });
30
+ const result = {};
31
+ for (const config of configs) {
32
+ if (config.name === 'healthCheckIntervalMs') {
33
+ result.healthCheckIntervalMs = parseInt(config.value);
34
+ }
35
+ else if (config.name === 'healthCheckSchedule') {
36
+ result.healthCheckSchedule = config.value;
37
+ }
38
+ else if (config.name === 'buildCleanupDays') {
39
+ result.buildCleanupDays = parseInt(config.value);
40
+ }
41
+ else if (config.name === 'buildCleanupMaxCount') {
42
+ result.buildCleanupMaxCount = parseInt(config.value);
43
+ }
44
+ else if (config.name === 'buildCleanupSchedule') {
45
+ result.buildCleanupSchedule = config.value;
46
+ }
47
+ else if (config.name === 'deleteBuildAssets') {
48
+ result.deleteBuildAssets = config.value === 'true';
49
+ }
50
+ }
51
+ return result;
52
+ });
53
+ }
54
+ setConfig(config) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ const promises = [];
57
+ if (config.healthCheckIntervalMs !== undefined) {
58
+ promises.push(prisma_1.prisma.webConfig.upsert({
59
+ where: { name: 'healthCheckIntervalMs' },
60
+ update: { value: config.healthCheckIntervalMs.toString() },
61
+ create: {
62
+ id: this.CONFIG_ID,
63
+ name: 'healthCheckIntervalMs',
64
+ value: config.healthCheckIntervalMs.toString(),
65
+ },
66
+ }));
67
+ }
68
+ if (config.healthCheckSchedule !== undefined) {
69
+ promises.push(prisma_1.prisma.webConfig.upsert({
70
+ where: { name: 'healthCheckSchedule' },
71
+ update: { value: config.healthCheckSchedule },
72
+ create: {
73
+ id: this.CONFIG_ID,
74
+ name: 'healthCheckSchedule',
75
+ value: config.healthCheckSchedule,
76
+ },
77
+ }));
78
+ }
79
+ if (config.buildCleanupDays !== undefined) {
80
+ promises.push(prisma_1.prisma.webConfig.upsert({
81
+ where: { name: 'buildCleanupDays' },
82
+ update: { value: config.buildCleanupDays.toString() },
83
+ create: {
84
+ id: this.CONFIG_ID,
85
+ name: 'buildCleanupDays',
86
+ value: config.buildCleanupDays.toString(),
87
+ },
88
+ }));
89
+ }
90
+ if (config.buildCleanupMaxCount !== undefined) {
91
+ promises.push(prisma_1.prisma.webConfig.upsert({
92
+ where: { name: 'buildCleanupMaxCount' },
93
+ update: { value: config.buildCleanupMaxCount.toString() },
94
+ create: {
95
+ id: this.CONFIG_ID,
96
+ name: 'buildCleanupMaxCount',
97
+ value: config.buildCleanupMaxCount.toString(),
98
+ },
99
+ }));
100
+ }
101
+ if (config.buildCleanupSchedule !== undefined) {
102
+ promises.push(prisma_1.prisma.webConfig.upsert({
103
+ where: { name: 'buildCleanupSchedule' },
104
+ update: { value: config.buildCleanupSchedule },
105
+ create: {
106
+ id: this.CONFIG_ID,
107
+ name: 'buildCleanupSchedule',
108
+ value: config.buildCleanupSchedule,
109
+ },
110
+ }));
111
+ }
112
+ if (config.deleteBuildAssets !== undefined) {
113
+ promises.push(prisma_1.prisma.webConfig.upsert({
114
+ where: { name: 'deleteBuildAssets' },
115
+ update: { value: config.deleteBuildAssets.toString() },
116
+ create: {
117
+ id: this.CONFIG_ID,
118
+ name: 'deleteBuildAssets',
119
+ value: config.deleteBuildAssets.toString(),
120
+ },
121
+ }));
122
+ }
123
+ yield Promise.all(promises);
124
+ });
125
+ }
126
+ };
127
+ exports.WebConfigService = WebConfigService;
128
+ exports.WebConfigService = WebConfigService = __decorate([
129
+ (0, typedi_1.Service)()
130
+ ], WebConfigService);