@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 __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 __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ResourceIsolationService = void 0;
16
+ const typedi_1 = require("typedi");
17
+ const logger_1 = __importDefault(require("../logger"));
18
+ const os_1 = __importDefault(require("os"));
19
+ /**
20
+ * ResourceIsolationService
21
+ * Provides abstraction for OS-level process sandboxing and priority management.
22
+ * Prevents "Noisy Neighbor" issues in high-density device farms.
23
+ */
24
+ let ResourceIsolationService = class ResourceIsolationService {
25
+ constructor() {
26
+ this.isMac = os_1.default.platform() === 'darwin';
27
+ this.isLinux = os_1.default.platform() === 'linux';
28
+ }
29
+ /**
30
+ * Wraps a command string with OS-level isolation/priority prefixes.
31
+ * @param command The base command to execute
32
+ * @param profile The desired isolation profile
33
+ * @returns The wrapped command
34
+ */
35
+ wrapCommand(command, profile) {
36
+ if (profile === 'Performance')
37
+ return command; // Default, no wrap needed
38
+ if (this.isMac) {
39
+ return this.wrapMacOS(command, profile);
40
+ }
41
+ if (this.isLinux) {
42
+ return this.wrapLinux(command, profile);
43
+ }
44
+ return command;
45
+ }
46
+ /**
47
+ * Specialized wrapper for child_process.spawn
48
+ */
49
+ wrapSpawn(command, args, profile) {
50
+ if (profile === 'Performance')
51
+ return { command, args };
52
+ if (this.isMac) {
53
+ if (profile === 'Economy') {
54
+ return { command: 'taskpolicy', args: ['-c', 'background', command, ...args] };
55
+ }
56
+ if (profile === 'Guaranteed') {
57
+ return { command: 'nice', args: ['-n', '-5', command, ...args] };
58
+ }
59
+ }
60
+ if (this.isLinux) {
61
+ if (profile === 'Economy') {
62
+ return { command: 'nice', args: ['-n', '19', command, ...args] };
63
+ }
64
+ if (profile === 'Guaranteed') {
65
+ return { command: 'nice', args: ['-n', '-10', command, ...args] };
66
+ }
67
+ }
68
+ return { command, args };
69
+ }
70
+ /**
71
+ * macOS version using taskpolicy
72
+ * -c background: Moves process to background throttled state
73
+ * -p <pid>: Can also be used, but we wrap the spawn.
74
+ */
75
+ wrapMacOS(command, profile) {
76
+ if (profile === 'Economy') {
77
+ // taskpolicy -c background is the most effective way to throttle CPU/IO on Mac
78
+ logger_1.default.debug('🛡️ Resource Isolation: Throttling command to background (Economy)');
79
+ return `taskpolicy -c background ${command}`;
80
+ }
81
+ if (profile === 'Guaranteed') {
82
+ // MacOS doesn't have a simple "High priority" CLI wrapper for spawn easily
83
+ // without sudo, but we can use 'nice'
84
+ logger_1.default.debug('🛡️ Resource Isolation: Assigning high priority (Guaranteed)');
85
+ return `nice -n -5 ${command}`;
86
+ }
87
+ return command;
88
+ }
89
+ /**
90
+ * Linux version using nice/renice
91
+ * Priority -20 (Highest) to 19 (Lowest)
92
+ */
93
+ wrapLinux(command, profile) {
94
+ if (profile === 'Economy') {
95
+ logger_1.default.debug('🛡️ Resource Isolation: Throttling command (Economy)');
96
+ return `nice -n 19 ${command}`;
97
+ }
98
+ if (profile === 'Guaranteed') {
99
+ logger_1.default.debug('🛡️ Resource Isolation: Assigning high priority (Guaranteed)');
100
+ return `nice -n -10 ${command}`;
101
+ }
102
+ return command;
103
+ }
104
+ /**
105
+ * Get numeric priority value for libraries that support it
106
+ */
107
+ getPriority(profile) {
108
+ switch (profile) {
109
+ case 'Economy':
110
+ return 19;
111
+ case 'Guaranteed':
112
+ return -10;
113
+ default:
114
+ return 0;
115
+ }
116
+ }
117
+ };
118
+ exports.ResourceIsolationService = ResourceIsolationService;
119
+ exports.ResourceIsolationService = ResourceIsolationService = __decorate([
120
+ (0, typedi_1.Service)(),
121
+ __metadata("design:paramtypes", [])
122
+ ], ResourceIsolationService);
@@ -0,0 +1,120 @@
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.SecurityService = void 0;
25
+ const crypto_1 = __importDefault(require("crypto"));
26
+ const os_1 = __importDefault(require("os"));
27
+ const typedi_1 = require("typedi");
28
+ const logger_1 = __importDefault(require("../logger"));
29
+ let SecurityService = class SecurityService {
30
+ constructor() {
31
+ this.algorithm = 'aes-256-gcm';
32
+ this.ivLength = 12;
33
+ this.saltLength = 16;
34
+ this.tagLength = 16;
35
+ this.keyLength = 32;
36
+ this.iterations = 10000;
37
+ this.encryptionKey = null;
38
+ }
39
+ /**
40
+ * Derives a machine-unique encryption key.
41
+ * Uses hostname and MAC addresses to create a stable, unique seed.
42
+ */
43
+ getMachineSecret() {
44
+ const interfaces = os_1.default.networkInterfaces();
45
+ const macs = Object.values(interfaces)
46
+ .flat()
47
+ .filter((iface) => iface && !iface.internal)
48
+ .map((iface) => iface.mac)
49
+ .sort()
50
+ .join(':');
51
+ return `${os_1.default.hostname()}-${macs}-xenon-v1`;
52
+ }
53
+ getEncryptionKey(salt) {
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ if (this.encryptionKey && salt.length === 0)
56
+ return this.encryptionKey;
57
+ return new Promise((resolve, reject) => {
58
+ crypto_1.default.pbkdf2(this.getMachineSecret(), salt, this.iterations, this.keyLength, 'sha256', (err, derivedKey) => {
59
+ if (err)
60
+ reject(err);
61
+ else
62
+ resolve(derivedKey);
63
+ });
64
+ });
65
+ });
66
+ }
67
+ encrypt(text) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ try {
70
+ if (!text)
71
+ return text;
72
+ const salt = crypto_1.default.randomBytes(this.saltLength);
73
+ const iv = crypto_1.default.randomBytes(this.ivLength);
74
+ const key = yield this.getEncryptionKey(salt);
75
+ const cipher = crypto_1.default.createCipheriv(this.algorithm, key, iv);
76
+ const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
77
+ const tag = cipher.getAuthTag();
78
+ // Format: salt:iv:tag:encrypted
79
+ return Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
80
+ }
81
+ catch (err) {
82
+ logger_1.default.error(`Encryption failed: ${err.message}`);
83
+ throw new Error('Security Error: Failed to encrypt sensitive data.');
84
+ }
85
+ });
86
+ }
87
+ decrypt(data) {
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ try {
90
+ if (!data || (!data.includes('==') && data.length < 50))
91
+ return data; // Primitive check if it's already plain text
92
+ const buffer = Buffer.from(data, 'base64');
93
+ const salt = buffer.subarray(0, this.saltLength);
94
+ const iv = buffer.subarray(this.saltLength, this.saltLength + this.ivLength);
95
+ const tag = buffer.subarray(this.saltLength + this.ivLength, this.saltLength + this.ivLength + this.tagLength);
96
+ const encrypted = buffer.subarray(this.saltLength + this.ivLength + this.tagLength);
97
+ const key = yield this.getEncryptionKey(salt);
98
+ const decipher = crypto_1.default.createDecipheriv(this.algorithm, key, iv);
99
+ decipher.setAuthTag(tag);
100
+ const decrypted = Buffer.concat([
101
+ decipher.update(encrypted),
102
+ decipher.final(),
103
+ ]);
104
+ return decrypted.toString('utf8');
105
+ }
106
+ catch (err) {
107
+ // If decryption fails, it might be legacy plain text.
108
+ // In production, we should log this but maybe return the original for migration?
109
+ // For now, let's assume if it fails it's either corrupt or plain text.
110
+ logger_1.default.debug(`Decryption failed (possibly plain text): ${err.message}`);
111
+ return data;
112
+ }
113
+ });
114
+ }
115
+ };
116
+ exports.SecurityService = SecurityService;
117
+ exports.SecurityService = SecurityService = __decorate([
118
+ (0, typedi_1.Service)(),
119
+ __metadata("design:paramtypes", [])
120
+ ], SecurityService);
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
42
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
43
+ return new (P || (P = Promise))(function (resolve, reject) {
44
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
45
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
46
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
47
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
48
+ });
49
+ };
50
+ var __importDefault = (this && this.__importDefault) || function (mod) {
51
+ return (mod && mod.__esModule) ? mod : { "default": mod };
52
+ };
53
+ var ServerManager_1;
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.ServerManager = void 0;
56
+ const typedi_1 = require("typedi");
57
+ const uuid_1 = require("uuid");
58
+ const helpers_1 = require("../helpers");
59
+ const ip_1 = __importDefault(require("ip"));
60
+ const logger_1 = __importDefault(require("../logger"));
61
+ // enable resolveJsonModule in tsconfig must be true for this to work
62
+ const package_json_1 = __importDefault(require("../../package.json"));
63
+ const IPluginArgs_1 = require("../interfaces/IPluginArgs");
64
+ const config_service_1 = require("../data-service/config-service");
65
+ const PluginContext_1 = require("../PluginContext");
66
+ const device_store_1 = require("../data-service/device-store");
67
+ const device_utils_1 = require("../device-utils");
68
+ const app_1 = require("../app");
69
+ const wd_command_proxy_1 = require("../proxy/wd-command-proxy");
70
+ const appium_adb_1 = require("appium-adb");
71
+ const ChromeDriverManager_1 = __importDefault(require("../device-managers/ChromeDriverManager"));
72
+ const AndroidDeviceManager_1 = __importDefault(require("../device-managers/AndroidDeviceManager"));
73
+ const IOSDeviceManager_1 = __importDefault(require("../device-managers/IOSDeviceManager"));
74
+ const device_managers_1 = require("../device-managers");
75
+ const pluginArgs_1 = require("../data-service/pluginArgs");
76
+ const NodeDevices_1 = __importDefault(require("../device-managers/NodeDevices"));
77
+ const SocketServer_1 = require("./SocketServer");
78
+ const SocketClient_1 = require("./SocketClient");
79
+ const TracingService_1 = require("./TracingService");
80
+ const plugin_1 = require("../plugin");
81
+ let ServerManager = ServerManager_1 = class ServerManager {
82
+ constructor() {
83
+ this.logger = logger_1.default.scope('ServerManager');
84
+ }
85
+ updateServer(expressApp, httpServer, cliArgs) {
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ this.logger.debug(`📱 Update server with CLI Args: ${JSON.stringify((0, helpers_1.redactSecrets)(cliArgs))}`);
88
+ const pluginArgs = yield this.resolvePluginArgs(cliArgs);
89
+ const nodeId = (0, uuid_1.v4)();
90
+ logger_1.default.banner(package_json_1.default.version, nodeId);
91
+ // Standardize static variable initialization
92
+ plugin_1.XenonPlugin.NODE_ID = nodeId;
93
+ plugin_1.XenonPlugin.port = cliArgs.port;
94
+ plugin_1.XenonPlugin.nodeBasePath = cliArgs.basePath || '';
95
+ const context = typedi_1.Container.get(PluginContext_1.PluginContext);
96
+ context.setContext(pluginArgs, cliArgs.port, nodeId, cliArgs.basePath || '');
97
+ yield this.syncDatabaseAndAIConfig(pluginArgs);
98
+ yield this.initializeCoreSubsystems(pluginArgs);
99
+ this.registerRoutes(expressApp, cliArgs, pluginArgs);
100
+ yield this.bootEmulators(pluginArgs);
101
+ this.registerDependenciesInContainer(pluginArgs, cliArgs, nodeId);
102
+ yield this.setupHubOrNode(pluginArgs, cliArgs, httpServer, nodeId);
103
+ yield this.setupMaintenanceCrons(pluginArgs);
104
+ // Principal Cleaning: Attempt to recover remote sessions before marking as failed
105
+ const { SessionManager } = yield Promise.resolve().then(() => __importStar(require('../sessions/SessionManager')));
106
+ const sessionManager = typedi_1.Container.get(SessionManager);
107
+ const recoveredCount = yield sessionManager.recoverActiveSessions(nodeId, plugin_1.XenonPlugin.nodeBasePath);
108
+ const recoveredSessionIds = sessionManager.getAllSessions().map((s) => s.getId());
109
+ if (recoveredCount > 0) {
110
+ logger_1.default.info(`🔄 Successfully recovered ${recoveredCount} remote sessions`);
111
+ }
112
+ // Cleanup any remaining zombie sessions
113
+ const { cleanupZombieSessions } = yield Promise.resolve().then(() => __importStar(require('../dashboard/services/session-service')));
114
+ yield cleanupZombieSessions(recoveredSessionIds);
115
+ // Initial device discovery poll to start managers and trackers
116
+ yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp, pluginArgs.hub, pluginArgs.tlsRejectUnauthorized);
117
+ // remove stale devices
118
+ yield (0, device_utils_1.removeStaleDevices)(pluginArgs.bindHostOrIp, pluginArgs.tlsRejectUnauthorized);
119
+ this.logger.info(`🚀 Xenon will be served at http://${pluginArgs.bindHostOrIp}:${cliArgs.port}/xenon with id ${nodeId}`);
120
+ });
121
+ }
122
+ resolvePluginArgs(cliArgs) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ const pluginConfigs = cliArgs.plugin;
125
+ const pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, ((pluginConfigs === null || pluginConfigs === void 0 ? void 0 : pluginConfigs['xenon']) || {}));
126
+ try {
127
+ const persistedConfig = yield typedi_1.Container.get(config_service_1.ConfigService).loadConfig();
128
+ if (persistedConfig && Object.keys(persistedConfig).length > 0) {
129
+ this.logger.info('Loading persisted configuration', persistedConfig);
130
+ Object.assign(pluginArgs, persistedConfig);
131
+ }
132
+ }
133
+ catch (err) {
134
+ this.logger.warn(`Failed to load persisted config: ${err}`);
135
+ }
136
+ if (pluginArgs.bindHostOrIp === undefined) {
137
+ pluginArgs.bindHostOrIp = ip_1.default.address();
138
+ }
139
+ return pluginArgs;
140
+ });
141
+ }
142
+ syncDatabaseAndAIConfig(pluginArgs) {
143
+ return __awaiter(this, void 0, void 0, function* () {
144
+ const { updateConfig } = yield Promise.resolve().then(() => __importStar(require('../config')));
145
+ const update = {};
146
+ if (pluginArgs.databaseProvider) {
147
+ update.databaseProvider = pluginArgs.databaseProvider;
148
+ process.env.XENON_DB_PROVIDER = pluginArgs.databaseProvider;
149
+ }
150
+ if (pluginArgs.databaseUrl) {
151
+ update.databaseUrl = pluginArgs.databaseUrl;
152
+ process.env.DATABASE_URL = pluginArgs.databaseUrl;
153
+ }
154
+ if (pluginArgs.aiProvider) {
155
+ update.aiProvider = pluginArgs.aiProvider;
156
+ process.env.XENON_AI_PROVIDER = pluginArgs.aiProvider;
157
+ }
158
+ if (pluginArgs.aiModel) {
159
+ update.aiModel = pluginArgs.aiModel;
160
+ process.env.XENON_AI_MODEL = pluginArgs.aiModel;
161
+ }
162
+ if (pluginArgs.aiBaseUrl) {
163
+ update.aiBaseUrl = pluginArgs.aiBaseUrl;
164
+ process.env.XENON_AI_BASE_URL = pluginArgs.aiBaseUrl;
165
+ }
166
+ if (pluginArgs.geminiApiKey) {
167
+ update.geminiApiKey = pluginArgs.geminiApiKey;
168
+ process.env.GEMINI_API_KEY = pluginArgs.geminiApiKey;
169
+ }
170
+ if (pluginArgs.openaiApiKey) {
171
+ update.openaiApiKey = pluginArgs.openaiApiKey;
172
+ process.env.OPENAI_API_KEY = pluginArgs.openaiApiKey;
173
+ }
174
+ if (pluginArgs.anthropicApiKey) {
175
+ update.anthropicApiKey = pluginArgs.anthropicApiKey;
176
+ process.env.ANTHROPIC_API_KEY = pluginArgs.anthropicApiKey;
177
+ }
178
+ if (Object.keys(update).length > 0) {
179
+ this.logger.info('[Plugin] Synchronizing database config', update);
180
+ updateConfig(update);
181
+ }
182
+ });
183
+ }
184
+ initializeCoreSubsystems(pluginArgs) {
185
+ return __awaiter(this, void 0, void 0, function* () {
186
+ yield (0, device_utils_1.initializeStorage)();
187
+ yield device_store_1.DeviceStoreFactory.getStore().clearStorage();
188
+ });
189
+ }
190
+ registerRoutes(expressApp, cliArgs, pluginArgs) {
191
+ expressApp.use('/xenon', (0, app_1.createRouter)(pluginArgs));
192
+ (0, wd_command_proxy_1.registerProxyMiddlware)(expressApp, cliArgs);
193
+ }
194
+ bootEmulators(pluginArgs) {
195
+ return __awaiter(this, void 0, void 0, function* () {
196
+ if (pluginArgs.emulators &&
197
+ pluginArgs.emulators.length > 0 &&
198
+ pluginArgs.platform.toLowerCase().includes('android')) {
199
+ this.logger.info('Emulators will be booted!!');
200
+ const adb = yield appium_adb_1.ADB.createADB({});
201
+ const array = pluginArgs.emulators || [];
202
+ yield Promise.all(array.map((arr) => adb.launchAVD(arr.avdName, arr)));
203
+ }
204
+ });
205
+ }
206
+ registerDependenciesInContainer(pluginArgs, cliArgs, nodeId) {
207
+ typedi_1.Container.set('DeviceStore', device_store_1.DeviceStoreFactory.getStore());
208
+ typedi_1.Container.set('PendingSessionStore', device_store_1.DeviceStoreFactory.getPendingSessionStore());
209
+ typedi_1.Container.set('CLIArgsStore', device_store_1.DeviceStoreFactory.getCLIArgsStore());
210
+ typedi_1.Container.set('HealEtalonStore', device_store_1.DeviceStoreFactory.getHealEtalonStore());
211
+ typedi_1.Container.set('AndroidDeviceManager', typedi_1.Container.get(AndroidDeviceManager_1.default));
212
+ typedi_1.Container.set('IOSDeviceManager', typedi_1.Container.get(IOSDeviceManager_1.default));
213
+ const deviceManager = typedi_1.Container.get(device_managers_1.XenonManager);
214
+ deviceManager.init();
215
+ // Optional Chrome Download
216
+ if (pluginArgs.skipChromeDownload === false) {
217
+ ChromeDriverManager_1.default.create()
218
+ .then((mgr) => typedi_1.Container.set(ChromeDriverManager_1.default, mgr))
219
+ .catch((err) => this.logger.error(`Failed to initialize ChromeDriverManager: ${err}`));
220
+ }
221
+ (0, pluginArgs_1.addCLIArgs)(cliArgs);
222
+ }
223
+ setupHubOrNode(pluginArgs, cliArgs, httpServer, nodeId) {
224
+ return __awaiter(this, void 0, void 0, function* () {
225
+ const hubArgument = pluginArgs.hub;
226
+ if (hubArgument !== undefined) {
227
+ this.logger.info(`📡 I'm a node and my hub is ${hubArgument}`);
228
+ yield (0, device_utils_1.setupCronUpdateDeviceList)(pluginArgs.bindHostOrIp, hubArgument, pluginArgs.sendNodeDevicesToHubIntervalMs, pluginArgs.tlsRejectUnauthorized);
229
+ // Handle graceful shutdown
230
+ ['SIGINT', 'SIGTERM'].forEach((signal) => {
231
+ process.once(signal, () => __awaiter(this, void 0, void 0, function* () {
232
+ logger_1.default.info(`Received ${signal}, unregistering node from hub...`);
233
+ try {
234
+ yield new NodeDevices_1.default(hubArgument, pluginArgs.tlsRejectUnauthorized).unRegisterNode(pluginArgs.bindHostOrIp);
235
+ }
236
+ catch (err) {
237
+ logger_1.default.error(`Error during node unregistration: ${err}`);
238
+ }
239
+ process.kill(process.pid, signal);
240
+ }));
241
+ });
242
+ // Initialize Socket Client to connect to Hub
243
+ const socketClient = typedi_1.Container.get(SocketClient_1.SocketClient);
244
+ socketClient.initialize(hubArgument, pluginArgs.bindHostOrIp);
245
+ }
246
+ else {
247
+ ServerManager_1.IS_HUB = true;
248
+ this.logger.info(`🌐 I'm a hub and I'm listening on ${pluginArgs.bindHostOrIp}:${cliArgs.port}`);
249
+ const socketServer = typedi_1.Container.get(SocketServer_1.SocketServer);
250
+ socketServer.initialize(httpServer);
251
+ const tracingService = typedi_1.Container.get(TracingService_1.TracingService);
252
+ tracingService.initialize();
253
+ }
254
+ });
255
+ }
256
+ setupMaintenanceCrons(pluginArgs) {
257
+ return __awaiter(this, void 0, void 0, function* () {
258
+ var _a;
259
+ if (!((_a = pluginArgs.cloud) === null || _a === void 0 ? void 0 : _a.cloudName)) {
260
+ // 1. Check for stale nodes
261
+ yield (0, device_utils_1.setupCronCheckStaleDevices)(pluginArgs.checkStaleDevicesIntervalMs, pluginArgs.bindHostOrIp, pluginArgs.tlsRejectUnauthorized);
262
+ // 2. Release blocked devices
263
+ yield (0, device_utils_1.setupCronReleaseBlockedDevices)(pluginArgs.checkBlockedDevicesIntervalMs, pluginArgs.newCommandTimeoutSec);
264
+ // 3. Clean up pending sessions
265
+ yield (0, device_utils_1.setupCronCleanPendingSessions)(pluginArgs.checkBlockedDevicesIntervalMs, pluginArgs.deviceAvailabilityTimeoutMs + 10000);
266
+ // 4. Clean up expired reservations every 1 minute
267
+ yield (0, device_utils_1.setupCronCleanExpiredReservations)(60000);
268
+ // 5. Clean up older builds and sessions
269
+ yield (0, device_utils_1.setupCronCleanupBuilds)(pluginArgs);
270
+ // 6. Start Health Monitor Service
271
+ const { HealthMonitorService } = yield Promise.resolve().then(() => __importStar(require('../device-managers/HealthMonitorService')));
272
+ typedi_1.Container.get(HealthMonitorService).start(pluginArgs);
273
+ // 7. Start Session Heartbeat Service
274
+ const { SessionHeartbeatService } = yield Promise.resolve().then(() => __importStar(require('./SessionHeartbeatService')));
275
+ typedi_1.Container.get(SessionHeartbeatService).start(pluginArgs);
276
+ }
277
+ });
278
+ }
279
+ };
280
+ exports.ServerManager = ServerManager;
281
+ ServerManager.IS_HUB = false;
282
+ exports.ServerManager = ServerManager = ServerManager_1 = __decorate([
283
+ (0, typedi_1.Service)()
284
+ ], ServerManager);