@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,663 @@
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.getDeviceTypeFromApp = void 0;
49
+ exports.isAndroid = isAndroid;
50
+ exports.deviceType = deviceType;
51
+ exports.isIOS = isIOS;
52
+ exports.isAndroidAndIOS = isAndroidAndIOS;
53
+ exports.isDeviceConfigPathAbsolute = isDeviceConfigPathAbsolute;
54
+ exports.allocateDeviceForSession = allocateDeviceForSession;
55
+ exports.updateCapabilityForDevice = updateCapabilityForDevice;
56
+ exports.initializeStorage = initializeStorage;
57
+ exports.getUtilizationTime = getUtilizationTime;
58
+ exports.setUtilizationTime = setUtilizationTime;
59
+ exports.getDeviceFiltersFromCapability = getDeviceFiltersFromCapability;
60
+ exports.getBusyDevicesCount = getBusyDevicesCount;
61
+ exports.updateDeviceList = updateDeviceList;
62
+ exports.refreshSimulatorState = refreshSimulatorState;
63
+ exports.setupCronCheckStaleDevices = setupCronCheckStaleDevices;
64
+ exports.removeStaleDevices = removeStaleDevices;
65
+ exports.unblockCandidateDevices = unblockCandidateDevices;
66
+ exports.releaseBlockedDevices = releaseBlockedDevices;
67
+ exports.setupCronReleaseBlockedDevices = setupCronReleaseBlockedDevices;
68
+ exports.setupCronUpdateDeviceList = setupCronUpdateDeviceList;
69
+ exports.cleanPendingSessions = cleanPendingSessions;
70
+ exports.setupCronCleanPendingSessions = setupCronCleanPendingSessions;
71
+ exports.setupCronCleanExpiredReservations = setupCronCleanExpiredReservations;
72
+ exports.setupCronCleanupBuilds = setupCronCleanupBuilds;
73
+ exports.stopAllTimers = stopAllTimers;
74
+ /* eslint-disable no-prototype-builtins */
75
+ const helpers_1 = require("./helpers");
76
+ const XenonCapabilityManager_1 = require("./XenonCapabilityManager");
77
+ const async_wait_until_1 = __importDefault(require("async-wait-until"));
78
+ const typedi_1 = require("typedi");
79
+ const device_managers_1 = require("./device-managers");
80
+ const device_service_1 = require("./data-service/device-service");
81
+ const logger_1 = __importDefault(require("./logger"));
82
+ const Platform_1 = __importDefault(require("./enums/Platform"));
83
+ const lodash_1 = __importDefault(require("lodash"));
84
+ const fs_1 = __importDefault(require("fs"));
85
+ const CapabilityManager_1 = __importDefault(require("./device-managers/cloud/CapabilityManager"));
86
+ const AndroidDeviceManager_1 = __importDefault(require("./device-managers/AndroidDeviceManager"));
87
+ const IOSDeviceManager_1 = __importDefault(require("./device-managers/IOSDeviceManager"));
88
+ const IOSDiscoveryService_1 = require("./device-managers/ios/IOSDiscoveryService");
89
+ const NodeDevices_1 = __importDefault(require("./device-managers/NodeDevices"));
90
+ const device_store_1 = require("./data-service/device-store");
91
+ // Use a Proxy to ensure we're always using the latest store from the factory,
92
+ // which is critical for test isolation when the factory cache is cleared.
93
+ const pendingStore = new Proxy({}, {
94
+ get: (target, prop) => {
95
+ return device_store_1.DeviceStoreFactory.getPendingSessionStore()[prop];
96
+ },
97
+ });
98
+ const customCapability = {
99
+ deviceTimeOut: 'appium:deviceAvailabilityTimeout',
100
+ deviceQueryInterval: 'appium:deviceRetryInterval',
101
+ iphoneOnly: 'appium:iPhoneOnly',
102
+ ipadOnly: 'appium:iPadOnly',
103
+ udids: 'appium:udids',
104
+ minSDK: 'appium:minSDK',
105
+ maxSDK: 'appium:maxSDK',
106
+ filterByHost: 'appium:filterByHost',
107
+ tags: 'appium:tags',
108
+ };
109
+ let timer;
110
+ let cronTimerToReleaseBlockedDevices;
111
+ let cronTimerToUpdateDevices;
112
+ let cronTimerToCleanPendingSessions;
113
+ let cronTimerToCleanupBuilds;
114
+ const getDeviceTypeFromApp = (app) => {
115
+ /* If the test is targeting safarim, then app capability will be empty */
116
+ if (!app) {
117
+ return;
118
+ }
119
+ return app.endsWith('.app') || app.endsWith('.zip') ? 'simulator' : 'real';
120
+ };
121
+ exports.getDeviceTypeFromApp = getDeviceTypeFromApp;
122
+ function isAndroid(cliArgs) {
123
+ return cliArgs.Platform.toLowerCase() === Platform_1.default.ANDROID;
124
+ }
125
+ function deviceType(pluginArgs, device) {
126
+ const iosDeviceType = pluginArgs.iosDeviceType;
127
+ return iosDeviceType === device || iosDeviceType === 'both';
128
+ }
129
+ function isIOS(pluginArgs) {
130
+ return (0, helpers_1.isMac)() && pluginArgs.platform.toLowerCase() === Platform_1.default.IOS;
131
+ }
132
+ function isAndroidAndIOS(pluginArgs) {
133
+ return (0, helpers_1.isMac)() && pluginArgs.platform.toLowerCase() === Platform_1.default.BOTH;
134
+ }
135
+ function isDeviceConfigPathAbsolute(path) {
136
+ if ((0, helpers_1.checkIfPathIsAbsolute)(path)) {
137
+ return true;
138
+ }
139
+ else {
140
+ throw new Error(`Device Config Path ${path} should be absolute`);
141
+ }
142
+ }
143
+ /**
144
+ * For given capability, wait untill a free device is available from the database
145
+ * and update the capability json with required device informations
146
+ * @param capability
147
+ * @returns
148
+ */
149
+ function allocateDeviceForSession(capability, deviceTimeOutMs, deviceQueryIntervalMs, pluginArgs) {
150
+ return __awaiter(this, void 0, void 0, function* () {
151
+ const firstMatch = Object.assign({}, capability.firstMatch[0], capability.alwaysMatch);
152
+ const filters = getDeviceFiltersFromCapability(firstMatch, pluginArgs);
153
+ const timeout = firstMatch[customCapability.deviceTimeOut] || deviceTimeOutMs;
154
+ const intervalBetweenAttempts = firstMatch[customCapability.deviceQueryInterval] || deviceQueryIntervalMs;
155
+ let device = null;
156
+ const store = device_store_1.DeviceStoreFactory.getStore();
157
+ try {
158
+ yield (0, async_wait_until_1.default)(() => __awaiter(this, void 0, void 0, function* () {
159
+ const maxSessions = getDeviceManager().getMaxSessionCount();
160
+ const busyDevicesCount = yield getBusyDevicesCount();
161
+ if (maxSessions !== undefined && busyDevicesCount === maxSessions) {
162
+ logger_1.default.info(`Waiting for session available, already at max session count of: ${maxSessions}`);
163
+ return false;
164
+ }
165
+ // Get matching devices and find one that is not reserved
166
+ const matchingDevices = yield (0, device_service_1.getDevices)(filters);
167
+ const availableCandidate = matchingDevices.find((d) => !(0, device_service_1.isDeviceReserved)(d));
168
+ if (availableCandidate) {
169
+ // Principal Intelligence: Multi-node consistent locking
170
+ // Attempt to atomically claim this specific device
171
+ device = yield store.findAndLockDevice(Object.assign(Object.assign({}, filters), { udid: [availableCandidate.udid] }));
172
+ return device !== null;
173
+ }
174
+ logger_1.default.info(`Waiting for free device. Filter: ${JSON.stringify(filters)}}`);
175
+ return false;
176
+ }), { timeout, intervalBetweenAttempts });
177
+ }
178
+ catch (err) {
179
+ // figure out whether the device is simply busy or non-existent
180
+ const filterCopy = Object.assign({}, filters);
181
+ delete filterCopy.busy;
182
+ delete filterCopy.userBlocked;
183
+ const possibleDevice = yield (0, device_service_1.getDevice)(filterCopy);
184
+ let failureReason = 'No device matching request.';
185
+ if ((possibleDevice === null || possibleDevice === void 0 ? void 0 : possibleDevice.busy) || (possibleDevice === null || possibleDevice === void 0 ? void 0 : possibleDevice.userBlocked)) {
186
+ failureReason = 'Device is busy or blocked.';
187
+ }
188
+ else if (possibleDevice && (0, device_service_1.isDeviceReserved)(possibleDevice)) {
189
+ failureReason = `Device is reserved by ${possibleDevice.reservedBy}.`;
190
+ }
191
+ throw new Error(`${failureReason}. Device request: ${JSON.stringify(filterCopy)}`);
192
+ }
193
+ // We have a locked device here
194
+ if (device !== null) {
195
+ const lockedDevice = device;
196
+ // Principal Health Check Integration: Ensure device is READY before allocation
197
+ if (!lockedDevice.cloud) {
198
+ const platform = lockedDevice.platform.toLowerCase();
199
+ const managers = yield getDeviceManager().deviceInstances();
200
+ const manager = managers.find((m) => {
201
+ if (platform === Platform_1.default.ANDROID)
202
+ return m instanceof AndroidDeviceManager_1.default;
203
+ if (platform === Platform_1.default.IOS || platform === 'tvos')
204
+ return m instanceof IOSDeviceManager_1.default;
205
+ return false;
206
+ });
207
+ if (manager && manager.readyForSession) {
208
+ const isReady = yield manager.readyForSession(lockedDevice);
209
+ if (!isReady) {
210
+ logger_1.default.error(`❌ [Allocation] Device ${lockedDevice.udid} failed pre-session health check. Unblocking.`);
211
+ yield (0, device_service_1.unblockDevice)(lockedDevice.udid, lockedDevice.host);
212
+ throw new Error(`Device ${lockedDevice.udid} is unhealthy and could not be autonomously recovered.`);
213
+ }
214
+ }
215
+ }
216
+ // Since findAndLockDevice already sets busy: true, we don't need blockDevice(device.udid, device.host);
217
+ logger_1.default.info(`📱 Locked device ${lockedDevice.udid} at host ${lockedDevice.host} for new session`);
218
+ yield updateCapabilityForDevice(capability, lockedDevice);
219
+ let newCommandTimeout = firstMatch['appium:newCommandTimeout'];
220
+ if (!newCommandTimeout) {
221
+ newCommandTimeout = pluginArgs.newCommandTimeoutSec;
222
+ }
223
+ yield (0, device_service_1.updatedAllocatedDevice)(lockedDevice, { newCommandTimeout });
224
+ return lockedDevice;
225
+ }
226
+ else {
227
+ // This should theoretically not be reached if waitUntil succeeded
228
+ throw new Error(`Device allocation failed unexpectedly for filters: ${JSON.stringify(filters)}`);
229
+ }
230
+ });
231
+ }
232
+ /**
233
+ * Adjust the capability for the device
234
+ * @param capability
235
+ * @param device
236
+ * @returns
237
+ */
238
+ function updateCapabilityForDevice(capability, device) {
239
+ return __awaiter(this, void 0, void 0, function* () {
240
+ if (!device.cloud) {
241
+ // Fetch additional info lazily if method exists on manager
242
+ const platform = device.platform.toLowerCase();
243
+ const manager = (yield getDeviceManager().deviceInstances()).find((m) => {
244
+ if (platform === Platform_1.default.ANDROID)
245
+ return m instanceof AndroidDeviceManager_1.default;
246
+ if (platform === Platform_1.default.IOS || platform === 'tvos')
247
+ return m instanceof IOSDeviceManager_1.default;
248
+ return false;
249
+ });
250
+ if (manager && manager.getAdditionalDeviceInfo) {
251
+ const additionalInfo = yield manager.getAdditionalDeviceInfo(device);
252
+ Object.assign(device, additionalInfo);
253
+ }
254
+ if (platform == Platform_1.default.ANDROID) {
255
+ yield (0, XenonCapabilityManager_1.androidCapabilities)(capability, device);
256
+ }
257
+ else {
258
+ yield (0, XenonCapabilityManager_1.iOSCapabilities)(capability, device);
259
+ }
260
+ }
261
+ else {
262
+ logger_1.default.info('Updating cloud capability for Device');
263
+ return new CapabilityManager_1.default(capability, device).getCapability();
264
+ }
265
+ });
266
+ }
267
+ /**
268
+ * Sets up node-persist storage in local cache
269
+ * @returns storage
270
+ */
271
+ function initializeStorage() {
272
+ return __awaiter(this, void 0, void 0, function* () {
273
+ logger_1.default.info('Initializing storage');
274
+ const basePath = (0, helpers_1.cachePath)('storage');
275
+ yield fs_1.default.promises.mkdir(basePath, { recursive: true });
276
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
277
+ const storage = require('node-persist');
278
+ const localStorage = storage.create({ dir: basePath });
279
+ yield localStorage.init();
280
+ typedi_1.Container.set('LocalStorage', localStorage);
281
+ });
282
+ }
283
+ function getStorage() {
284
+ return __awaiter(this, void 0, void 0, function* () {
285
+ try {
286
+ typedi_1.Container.get('LocalStorage');
287
+ }
288
+ catch (err) {
289
+ logger_1.default.error(`Failed to get LocalStorage: Error ${err}`);
290
+ yield initializeStorage();
291
+ }
292
+ return typedi_1.Container.get('LocalStorage');
293
+ });
294
+ }
295
+ /**
296
+ * Gets utlization time for a device from storage
297
+ * Returns 0 if the device has not been used an thus utilization time has not been saved
298
+ * @param udid
299
+ * @returns number
300
+ */
301
+ function getUtilizationTime(udid) {
302
+ return __awaiter(this, void 0, void 0, function* () {
303
+ try {
304
+ const value = (yield getStorage()).getItem(udid);
305
+ if (value !== undefined && value && !isNaN(yield value)) {
306
+ return value;
307
+ }
308
+ else {
309
+ //log.error(`Custom Exception: Utilizaiton time in cache is corrupted. Value = '${value}'.`);
310
+ }
311
+ }
312
+ catch (err) {
313
+ logger_1.default.error(`Failed to fetch Utilization Time \n ${err}`);
314
+ }
315
+ return 0;
316
+ });
317
+ }
318
+ /**
319
+ * Sets utilization time for a device to storage
320
+ * @param udid
321
+ * @param utilizationTime
322
+ */
323
+ function setUtilizationTime(udid, utilizationTime) {
324
+ return __awaiter(this, void 0, void 0, function* () {
325
+ yield (yield getStorage()).setItem(udid, utilizationTime);
326
+ });
327
+ }
328
+ /**
329
+ * Method to get the device filters from the custom session capability
330
+ * This filter will be used as in the query to find the free device from the databse
331
+ * @param capability
332
+ * @returns IDeviceFilterOptions
333
+ */
334
+ function getDeviceFiltersFromCapability(capability, pluginArgs) {
335
+ var _a;
336
+ const platformName = capability['platformName'] || capability['appium:platformName'];
337
+ const platform = platformName ? platformName.toLowerCase() : undefined;
338
+ const udids = capability[customCapability.udids]
339
+ ? capability[customCapability.udids].split(',').map(lodash_1.default.trim)
340
+ : (_a = process.env.UDIDS) === null || _a === void 0 ? void 0 : _a.split(',').map(lodash_1.default.trim);
341
+ /* Based on the app file extension, we will decide whether to run the
342
+ * test on real device or simulator.
343
+ *
344
+ * Applicaple only for ios.
345
+ */
346
+ const deviceType = platform == Platform_1.default.IOS
347
+ ? (0, exports.getDeviceTypeFromApp)(capability['appium:app'])
348
+ : undefined;
349
+ if ((deviceType === null || deviceType === void 0 ? void 0 : deviceType.startsWith('sim')) && pluginArgs.iosDeviceType === 'real') {
350
+ throw new Error('iosDeviceType value is set to "real" but app provided is not suitable for real device.');
351
+ }
352
+ if ((deviceType === null || deviceType === void 0 ? void 0 : deviceType.startsWith('real')) && pluginArgs.iosDeviceType == 'simulated') {
353
+ throw new Error('iosDeviceType value is set to "simulated" but app provided is not suitable for simulator device.');
354
+ }
355
+ let name = undefined;
356
+ if (capability[customCapability.ipadOnly]) {
357
+ name = 'iPad';
358
+ }
359
+ else if (capability[customCapability.iphoneOnly]) {
360
+ name = 'iPhone';
361
+ }
362
+ // Ensure udid is always an array of strings for the filter
363
+ let udidFilter = [];
364
+ if (udids === null || udids === void 0 ? void 0 : udids.length) {
365
+ udidFilter = udids;
366
+ }
367
+ else if (capability['appium:udid']) {
368
+ udidFilter = [capability['appium:udid']];
369
+ }
370
+ else if (capability['udid']) {
371
+ udidFilter = [capability['udid']];
372
+ }
373
+ let caps = {
374
+ platform,
375
+ platformVersion: capability['appium:platformVersion']
376
+ ? capability['appium:platformVersion']
377
+ : undefined,
378
+ name,
379
+ deviceType,
380
+ udid: udidFilter,
381
+ busy: false,
382
+ userBlocked: false,
383
+ filterByHost: capability[customCapability.filterByHost],
384
+ minSDK: capability[customCapability.minSDK] ? capability[customCapability.minSDK] : undefined,
385
+ maxSDK: capability[customCapability.maxSDK] ? capability[customCapability.maxSDK] : undefined,
386
+ tags: capability[customCapability.tags]
387
+ ? capability[customCapability.tags].split(',').map(lodash_1.default.trim)
388
+ : undefined,
389
+ };
390
+ if (name !== undefined) {
391
+ caps = Object.assign(Object.assign({}, caps), { name });
392
+ }
393
+ return caps;
394
+ }
395
+ /**
396
+ * Helper methods to manage devices
397
+ */
398
+ function getDeviceManager() {
399
+ return typedi_1.Container.get(device_managers_1.XenonManager);
400
+ }
401
+ function getBusyDevicesCount() {
402
+ return __awaiter(this, void 0, void 0, function* () {
403
+ const allDevices = yield (0, device_service_1.getAllDevices)();
404
+ return allDevices.filter((device) => {
405
+ return device.busy;
406
+ }).length;
407
+ });
408
+ }
409
+ function updateDeviceList(host, hubArgument, tlsRejectUnauthorized) {
410
+ return __awaiter(this, void 0, void 0, function* () {
411
+ const allExistingDevices = yield (0, device_service_1.getAllDevices)();
412
+ const devices = yield getDeviceManager().getDevices(allExistingDevices);
413
+ if (devices.length === 0) {
414
+ logger_1.default.warn('No devices found');
415
+ return [];
416
+ }
417
+ // log.debug(`Updating device list with ${JSON.stringify(devices)} devices`);
418
+ // first thing first. Update device list in local list
419
+ yield (0, device_service_1.addNewDevice)(devices, host);
420
+ // Prune any devices that are in our local DB for this host but NO LONGER discovered.
421
+ // This automatically cleans up disconnected Android devices safely, or filtered
422
+ // iOS simulators (e.g. when booted-simulators is turned on and a simulator shuts down)
423
+ const discoveredUdids = new Set(devices.map((d) => d.udid));
424
+ const staleLocalDevices = allExistingDevices.filter((d) => d.host === host && !discoveredUdids.has(d.udid));
425
+ if (staleLocalDevices.length > 0) {
426
+ logger_1.default.info(`Removing ${staleLocalDevices.length} stale devices/simulators no longer discovered on this host.`);
427
+ yield (0, device_service_1.removeDevice)(staleLocalDevices.map((d) => ({ udid: d.udid, host: d.host })));
428
+ }
429
+ if (hubArgument) {
430
+ if (yield (0, helpers_1.isXenonRunning)(hubArgument, tlsRejectUnauthorized)) {
431
+ const nodeDevices = new NodeDevices_1.default(hubArgument, tlsRejectUnauthorized);
432
+ try {
433
+ yield nodeDevices.postDevicesToHub(devices, 'add');
434
+ if (staleLocalDevices.length > 0) {
435
+ yield nodeDevices.postDevicesToHub(staleLocalDevices, 'remove');
436
+ }
437
+ }
438
+ catch (error) {
439
+ logger_1.default.error(`Cannot send device list update. Reason: ${error}`);
440
+ }
441
+ }
442
+ else {
443
+ logger_1.default.warn(`Not sending device update since hub ${hubArgument} is not running`);
444
+ }
445
+ }
446
+ return devices;
447
+ });
448
+ }
449
+ function refreshSimulatorState(pluginArgs, hostPort) {
450
+ return __awaiter(this, void 0, void 0, function* () {
451
+ if (timer) {
452
+ clearInterval(timer);
453
+ }
454
+ timer = setInterval(() => __awaiter(this, void 0, void 0, function* () {
455
+ const iosDiscoveryService = typedi_1.Container.get(IOSDiscoveryService_1.IOSDiscoveryService);
456
+ const simulators = yield iosDiscoveryService.getSimulators();
457
+ yield (0, device_service_1.setSimulatorState)(simulators);
458
+ }), 10000);
459
+ });
460
+ }
461
+ function setupCronCheckStaleDevices(intervalMs, currentHost, tlsRejectUnauthorized) {
462
+ return __awaiter(this, void 0, void 0, function* () {
463
+ setInterval(() => __awaiter(this, void 0, void 0, function* () {
464
+ yield removeStaleDevices(currentHost, tlsRejectUnauthorized);
465
+ }), intervalMs);
466
+ });
467
+ }
468
+ /**
469
+ * Remove devices where the host is not alive nor defined.
470
+ * @param currentHost current host ip address
471
+ */
472
+ function removeStaleDevices(currentHost, tlsRejectUnauthorized) {
473
+ return __awaiter(this, void 0, void 0, function* () {
474
+ const allDevices = yield (0, device_service_1.getAllDevices)();
475
+ const nodeDevices = allDevices.filter((device) => {
476
+ // devices that's not from this node ip address
477
+ return device.host !== undefined && !device.host.includes(currentHost);
478
+ });
479
+ const devicesWithNoHost = nodeDevices.filter((device) => {
480
+ return device.host === undefined;
481
+ });
482
+ const nodeHosts = nodeDevices.filter((device) => !device.cloud).map((device) => device.host);
483
+ const cloudHosts = nodeDevices.filter((device) => !!device.cloud).map((device) => device.host);
484
+ const aliveHosts = (yield Promise.allSettled(nodeHosts.map((host) => __awaiter(this, void 0, void 0, function* () {
485
+ return {
486
+ host: host,
487
+ alive: yield (0, helpers_1.isXenonRunning)(host, tlsRejectUnauthorized),
488
+ };
489
+ }))));
490
+ const aliveCloudHosts = (yield Promise.allSettled(cloudHosts.map((host) => __awaiter(this, void 0, void 0, function* () {
491
+ return {
492
+ host: host,
493
+ alive: yield (0, helpers_1.isAppiumRunningAt)(host, tlsRejectUnauthorized),
494
+ };
495
+ }))));
496
+ // summarize alive hosts
497
+ const allAliveHosts = [...aliveHosts, ...aliveCloudHosts]
498
+ .filter((item) => item.status === 'fulfilled' && item.value.alive)
499
+ .map((item) => item.value.host);
500
+ // stale devices are devices that's not alive
501
+ const staleDevices = nodeDevices.filter((device) => !allAliveHosts.includes(device.host));
502
+ yield (0, device_service_1.removeDevice)(staleDevices.map((device) => ({ udid: device.udid, host: device.host })));
503
+ if (staleDevices.length > 0) {
504
+ logger_1.default.debug(`Removing device with udid(s): ${staleDevices
505
+ .map((device) => device.udid)
506
+ .join(', ')} because the node is not alive`);
507
+ }
508
+ // remove devices with no host
509
+ yield (0, device_service_1.removeDevice)(devicesWithNoHost.map((device) => ({ udid: device.udid, host: device.host })));
510
+ if (devicesWithNoHost.length > 0) {
511
+ logger_1.default.debug(`Removing device with udid(s): ${devicesWithNoHost
512
+ .map((device) => device.udid)
513
+ .join(', ')} because the device has no host`);
514
+ }
515
+ });
516
+ }
517
+ function unblockCandidateDevices() {
518
+ return __awaiter(this, void 0, void 0, function* () {
519
+ const allDevices = yield (0, device_service_1.getAllDevices)();
520
+ return allDevices.filter((device) => {
521
+ // log.debug(`Checking if device ${device.udid} from ${device.host} is a candidate to be released: ${isCandidate}`);
522
+ return device.busy && !device.userBlocked && device.lastCmdExecutedAt != undefined;
523
+ });
524
+ });
525
+ }
526
+ function releaseBlockedDevices(newCommandTimeout) {
527
+ return __awaiter(this, void 0, void 0, function* () {
528
+ const busyDevices = yield unblockCandidateDevices();
529
+ logger_1.default.debug(`Found ${busyDevices.length} device candidates to be released`);
530
+ busyDevices.forEach(function (device) {
531
+ // need to keep this to make typescript happy. good thing tho.
532
+ if (device.lastCmdExecutedAt == undefined) {
533
+ return;
534
+ }
535
+ const currentEpoch = new Date().getTime();
536
+ const timeoutSeconds = device.newCommandTimeout != undefined ? device.newCommandTimeout : newCommandTimeout;
537
+ const timeSinceLastCmdExecuted = (currentEpoch - device.lastCmdExecutedAt) / 1000;
538
+ if (timeSinceLastCmdExecuted > timeoutSeconds) {
539
+ // unblock regardless of whether the device has session or not
540
+ logger_1.default.info(`Unblocking device ${device.udid} at host ${device.host} because it has been idle for ${timeSinceLastCmdExecuted} seconds`);
541
+ // Principal Protection: If this device has an active dashboard session, stop it properly
542
+ if (device.session_id) {
543
+ const sessionId = device.session_id;
544
+ // Mark as stopped/failed in dashboard
545
+ Promise.resolve().then(() => __importStar(require('./dashboard/event-manager'))).then((m) => {
546
+ m.DASHBORD_EVENT_MANAGER.onSessionStopped(sessionId, 'failed', 'Session timed out due to inactivity');
547
+ });
548
+ // Important: Remove from in-memory SessionManager so it doesn't leak
549
+ Promise.resolve().then(() => __importStar(require('./sessions/SessionManager'))).then((m) => {
550
+ m.SESSION_MANAGER.removeSession(sessionId);
551
+ });
552
+ logger_1.default.warn(`🕒 Session ${sessionId} timed out on device ${device.udid}`);
553
+ }
554
+ (0, device_service_1.unblockDevice)(device.udid, device.host);
555
+ }
556
+ });
557
+ });
558
+ }
559
+ function setupCronReleaseBlockedDevices(intervalMs, newCommandTimeoutSec) {
560
+ return __awaiter(this, void 0, void 0, function* () {
561
+ if (cronTimerToReleaseBlockedDevices) {
562
+ clearInterval(cronTimerToReleaseBlockedDevices);
563
+ }
564
+ yield releaseBlockedDevices(newCommandTimeoutSec);
565
+ cronTimerToReleaseBlockedDevices = setInterval(() => __awaiter(this, void 0, void 0, function* () {
566
+ yield releaseBlockedDevices(newCommandTimeoutSec);
567
+ }), intervalMs);
568
+ });
569
+ }
570
+ function setupCronUpdateDeviceList(host, hubArgument, intervalMs, tlsRejectUnauthorized) {
571
+ return __awaiter(this, void 0, void 0, function* () {
572
+ if (cronTimerToUpdateDevices) {
573
+ clearInterval(cronTimerToUpdateDevices);
574
+ }
575
+ logger_1.default.info(`This node will send device list update to the hub (${hubArgument}) every ${intervalMs} ms`);
576
+ cronTimerToUpdateDevices = setInterval(() => __awaiter(this, void 0, void 0, function* () {
577
+ yield updateDeviceList(host, hubArgument, tlsRejectUnauthorized);
578
+ }), intervalMs);
579
+ });
580
+ }
581
+ function cleanPendingSessions(timeoutMs) {
582
+ return __awaiter(this, void 0, void 0, function* () {
583
+ const pendingSessions = yield pendingStore.getAllPendingSessions();
584
+ const currentEpoch = new Date().getTime();
585
+ const timedOutSessions = pendingSessions.filter((session) => {
586
+ const timeSinceSessionCreated = currentEpoch - session.createdAt;
587
+ logger_1.default.debug(`Session queue ID:${session.capability_id} has been pending for ${timeSinceSessionCreated} ms`);
588
+ return timeSinceSessionCreated > timeoutMs;
589
+ });
590
+ if (timedOutSessions.length === 0) {
591
+ logger_1.default.debug('No pending sessions to clean');
592
+ }
593
+ else {
594
+ logger_1.default.debug(`Found ${timedOutSessions.length} pending sessions to clean`);
595
+ }
596
+ for (const session of timedOutSessions) {
597
+ logger_1.default.debug(`Removing pending session ${session.capability_id} because it has timed out`);
598
+ yield pendingStore.remove(session);
599
+ }
600
+ });
601
+ }
602
+ function setupCronCleanPendingSessions(intervalMs, timeoutMs) {
603
+ return __awaiter(this, void 0, void 0, function* () {
604
+ logger_1.default.info(`Hub will clean pending sessions every ${intervalMs} ms with pending session timeout: ${timeoutMs} ms`);
605
+ if (cronTimerToCleanPendingSessions) {
606
+ clearInterval(cronTimerToCleanPendingSessions);
607
+ }
608
+ cronTimerToCleanPendingSessions = setInterval(() => __awaiter(this, void 0, void 0, function* () {
609
+ logger_1.default.debug('Cleaning pending sessions...');
610
+ yield cleanPendingSessions(timeoutMs);
611
+ }), intervalMs);
612
+ });
613
+ }
614
+ let cronTimerToCleanExpiredReservations;
615
+ function setupCronCleanExpiredReservations(intervalMs) {
616
+ return __awaiter(this, void 0, void 0, function* () {
617
+ logger_1.default.info(`Hub will clean expired reservations every ${intervalMs} ms`);
618
+ if (cronTimerToCleanExpiredReservations) {
619
+ clearInterval(cronTimerToCleanExpiredReservations);
620
+ }
621
+ cronTimerToCleanExpiredReservations = setInterval(() => __awaiter(this, void 0, void 0, function* () {
622
+ logger_1.default.debug('Cleaning expired reservations...');
623
+ yield (0, device_service_1.cleanExpiredReservations)();
624
+ }), intervalMs);
625
+ });
626
+ }
627
+ /**
628
+ * Sets up a cron job to purge older builds and sessions based on configuration
629
+ */
630
+ function setupCronCleanupBuilds(pluginArgs) {
631
+ return __awaiter(this, void 0, void 0, function* () {
632
+ const { buildCleanupSchedule = '0 0 * * *' } = pluginArgs;
633
+ const { CleanupService } = yield Promise.resolve().then(() => __importStar(require('./services/CleanupService')));
634
+ const schedule = yield Promise.resolve().then(() => __importStar(require('node-schedule')));
635
+ const cleanupService = typedi_1.Container.get(CleanupService);
636
+ if (cronTimerToCleanupBuilds) {
637
+ cronTimerToCleanupBuilds.cancel();
638
+ }
639
+ logger_1.default.info(`Build cleanup scheduled with expression: ${buildCleanupSchedule}`);
640
+ cronTimerToCleanupBuilds = schedule.scheduleJob(buildCleanupSchedule, () => __awaiter(this, void 0, void 0, function* () {
641
+ logger_1.default.info('Running scheduled build cleanup...');
642
+ yield cleanupService.runCleanup(pluginArgs);
643
+ }));
644
+ });
645
+ }
646
+ /**
647
+ * Principal Shutdown: Clears all background intervals and scheduled jobs
648
+ * to prevent process hangs and handle leaks.
649
+ */
650
+ function stopAllTimers() {
651
+ if (timer)
652
+ clearInterval(timer);
653
+ if (cronTimerToReleaseBlockedDevices)
654
+ clearInterval(cronTimerToReleaseBlockedDevices);
655
+ if (cronTimerToUpdateDevices)
656
+ clearInterval(cronTimerToUpdateDevices);
657
+ if (cronTimerToCleanPendingSessions)
658
+ clearInterval(cronTimerToCleanPendingSessions);
659
+ if (cronTimerToCleanExpiredReservations)
660
+ clearInterval(cronTimerToCleanExpiredReservations);
661
+ if (cronTimerToCleanupBuilds)
662
+ cronTimerToCleanupBuilds.cancel();
663
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var Capabilities;
4
+ (function (Capabilities) {
5
+ Capabilities["DEVICE_NAME"] = "appium:deviceName";
6
+ Capabilities["PLATFORM_VERSION"] = "platformVersion";
7
+ })(Capabilities || (Capabilities = {}));
8
+ exports.default = Capabilities;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var Cloud;
4
+ (function (Cloud) {
5
+ Cloud["BROWSERSTACK"] = "browserstack";
6
+ Cloud["PCLOUDY"] = "pcloudy";
7
+ Cloud["SAUCELABS"] = "sauce";
8
+ Cloud["LAMBDATEST"] = "lambdatest";
9
+ Cloud["HEADSPIN"] = "headspin";
10
+ })(Cloud || (Cloud = {}));
11
+ exports.default = Cloud;