@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,699 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.DASHBORD_EVENT_MANAGER = exports.DashboardEventManager = void 0;
55
+ const SessionManager_1 = require("../sessions/SessionManager");
56
+ const prisma_1 = require("../prisma");
57
+ const logger_1 = __importDefault(require("../logger"));
58
+ const session_service_1 = require("./services/session-service");
59
+ const XenonCapabilityManager_1 = require("../XenonCapabilityManager");
60
+ const lodash_1 = __importDefault(require("lodash"));
61
+ const helpers_1 = require("../helpers");
62
+ const asset_manager_1 = require("./asset-manager");
63
+ const commands_1 = require("./commands");
64
+ const SessionStatus_1 = require("../types/SessionStatus");
65
+ const appium_ios_device_1 = require("appium-ios-device");
66
+ const AndroidAppProfiler_1 = require("../profiling/AndroidAppProfiler");
67
+ const appium_adb_1 = require("appium-adb");
68
+ const config_1 = require("../config");
69
+ const helpers_2 = require("../helpers");
70
+ const typedi_1 = require("typedi");
71
+ const device_managers_1 = require("../device-managers");
72
+ const AndroidDeviceManager_1 = __importDefault(require("../device-managers/AndroidDeviceManager"));
73
+ const SocketServer_1 = require("../services/SocketServer");
74
+ const TracingService_1 = require("../services/TracingService");
75
+ const MetricsService_1 = require("../services/MetricsService");
76
+ const SocketEvents_1 = require("../enums/SocketEvents");
77
+ const typedi_2 = require("typedi");
78
+ let DashboardEventManager = class DashboardEventManager {
79
+ constructor() {
80
+ // private SCREENSHOT_FOR_COMMANDS = ['click', 'setUrl', 'setValue', 'performActions'];
81
+ // Store syslog services for real iOS devices
82
+ this.syslogServices = new Map();
83
+ // Map session ID to UDID for cleanup
84
+ this.sessionToUdid = new Map();
85
+ // Map session ID to device info
86
+ this.sessionToDevice = new Map();
87
+ // Store app profilers for Android sessions
88
+ this.appProfilers = new Map();
89
+ // Track last log line for each session (for device logs)
90
+ this.lastLogLine = new Map();
91
+ // Track start time for each command to calculate duration
92
+ this.commandStartTime = new Map();
93
+ // Idempotency Guard: Prevents double-invocation of onSessionStopped by racing actors
94
+ this.stoppingSessionIds = new Set();
95
+ }
96
+ onSessionStarted(capabilities, session, device) {
97
+ return __awaiter(this, void 0, void 0, function* () {
98
+ // Store device info for this session
99
+ this.sessionToDevice.set(session.getId(), device);
100
+ const createOptions = {
101
+ id: session.getId(),
102
+ };
103
+ // create directory to store screenshots, videos and log files for the session
104
+ (0, asset_manager_1.prepareDirectory)(session.getId());
105
+ // Initialize app profiling for Android sessions
106
+ const { is_profiling_available, device_info } = yield this.startAppProfiling(session.getId(), device, session.getCapabilities());
107
+ // If iOS real device, start performance recording (Time Profiler)
108
+ // Note: This only works on real devices with XCUITest driver 4.5+
109
+ let isIosProfilingStarted = false;
110
+ if (device.platform.toLowerCase() === 'ios' && device.realDevice === true) {
111
+ logger_1.default.info(`[Profiling] Starting iOS performance recording for session ${session.getId()}`);
112
+ try {
113
+ yield session.startPerformanceRecording();
114
+ isIosProfilingStarted = true;
115
+ logger_1.default.info(`[Profiling] ✅ iOS performance recording started for ${session.getId()}`);
116
+ }
117
+ catch (err) {
118
+ logger_1.default.warn(`[Profiling] Failed to start iOS performance recording: ${err.message}`);
119
+ // Not a fatal error - profiling is optional
120
+ }
121
+ }
122
+ // start video recording is now handled in plugin.ts createSession to avoid double calls
123
+ const videoMsg = `📹 Video recording capability for session ${session.getId()}: ${capabilities[XenonCapabilityManager_1.XENON_CAPABILITIES.VIDEO_RECORDING]}`;
124
+ logger_1.default.info(videoMsg);
125
+ const buildName = capabilities[XenonCapabilityManager_1.XENON_CAPABILITIES.BUILD_NAME] || 'Default Build';
126
+ const build = yield (0, session_service_1.getOrCreateNewBuild)(buildName);
127
+ const sessionResponse = lodash_1.default.assign({}, session.getCapabilities());
128
+ const tracingService = typedi_1.Container.get(TracingService_1.TracingService);
129
+ const traceId = tracingService.getTraceId(session.getId());
130
+ const createData = {
131
+ id: session.getId(),
132
+ build: build.id ? { connect: { id: build.id } } : undefined,
133
+ name: capabilities[XenonCapabilityManager_1.XENON_CAPABILITIES.SESSION_NAME] || undefined,
134
+ desired_capabilities: JSON.stringify(sessionResponse.desired || {}),
135
+ session_capabilities: JSON.stringify(lodash_1.default.omit(sessionResponse, 'desired')),
136
+ node_id: device.nodeId || '',
137
+ has_live_video: session.getLiveVideoUrl() !== null,
138
+ video_recording_enabled: capabilities[XenonCapabilityManager_1.XENON_CAPABILITIES.VIDEO_RECORDING] === true,
139
+ is_profiling_available: is_profiling_available || isIosProfilingStarted,
140
+ device_info: device_info ? JSON.stringify(device_info) : null,
141
+ device_udid: device.udid || '',
142
+ device_platform: device.platform || '',
143
+ device_version: device.sdk || '',
144
+ device_name: device.name,
145
+ trace_id: traceId,
146
+ status: 'running', // Principal Polish: Set status explicitly
147
+ };
148
+ yield prisma_1.prisma.session.create({
149
+ data: createData,
150
+ });
151
+ // Emit session started event
152
+ typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboard(SocketEvents_1.SocketEvents.SESSION_STARTED, Object.assign(Object.assign({}, createData), { status: 'running', build_name: buildName }));
153
+ // Increment Metrics
154
+ typedi_1.Container.get(MetricsService_1.MetricsService).incrementSessionStart();
155
+ });
156
+ }
157
+ onSessionStopped(sessionId, status, failureReason) {
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ var _a, _b, _c, _d;
160
+ // Idempotency Guard: If this session is already being stopped by another actor
161
+ // (heartbeat, stream watchdog, plugin.deleteSession), skip to avoid double-cleanup.
162
+ if (this.stoppingSessionIds.has(sessionId)) {
163
+ logger_1.default.info(`⏭️ onSessionStopped already in progress for ${sessionId}. Skipping duplicate call.`);
164
+ return;
165
+ }
166
+ this.stoppingSessionIds.add(sessionId);
167
+ try {
168
+ logger_1.default.info(`🟢 onSessionStopped called for session ${sessionId}`);
169
+ // Video recording is now handled in plugin.ts deleteSession() before the session is deleted
170
+ // This ensures we can call stop_recording_screen while the session is still active
171
+ // Here we just handle the session status update
172
+ const session = SessionManager_1.SESSION_MANAGER.getSession(sessionId);
173
+ if (session) {
174
+ logger_1.default.info(`Session ${sessionId} found in SESSION_MANAGER`);
175
+ // Save Android profiling data before cleanup
176
+ yield this.saveAppProfilingData(sessionId);
177
+ // iOS profiling is now handled in plugin.ts deleteSession() before the session is deleted
178
+ // This ensures we can call mobile: stopPerfRecord while the driver is still alive
179
+ // Clean up syslog service for real iOS devices
180
+ const udid = this.sessionToUdid.get(sessionId);
181
+ if (udid) {
182
+ try {
183
+ this.syslogServices.delete(udid);
184
+ this.sessionToUdid.delete(sessionId);
185
+ logger_1.default.info(`Cleaned up syslog service for device ${udid}`);
186
+ }
187
+ catch (err) {
188
+ logger_1.default.debug(`Error cleaning up syslog service info: ${err}`);
189
+ }
190
+ }
191
+ // Clean up last log line tracking
192
+ this.lastLogLine.delete(sessionId);
193
+ // Principal Resource Management: Unblock device immediately
194
+ const device = this.sessionToDevice.get(sessionId);
195
+ if (device) {
196
+ const { unblockDevice } = yield Promise.resolve().then(() => __importStar(require('../data-service/device-service')));
197
+ try {
198
+ yield unblockDevice(device.udid, device.host);
199
+ logger_1.default.info(`🔓 [${sessionId}] Device ${device.udid} released.`);
200
+ }
201
+ catch (unblockErr) {
202
+ const msg = (_a = unblockErr === null || unblockErr === void 0 ? void 0 : unblockErr.message) !== null && _a !== void 0 ? _a : String(unblockErr);
203
+ logger_1.default.error(`⚠️ Failed to unblock device ${device.udid} for session ${sessionId}: ${msg}`, unblockErr);
204
+ }
205
+ }
206
+ else {
207
+ const { unblockDeviceMatchingFilter } = yield Promise.resolve().then(() => __importStar(require('../data-service/device-service')));
208
+ try {
209
+ yield unblockDeviceMatchingFilter({ session_id: sessionId });
210
+ logger_1.default.info(`🔓 [${sessionId}] Device released via session_id fallback.`);
211
+ }
212
+ catch (unblockErr) {
213
+ const msg = (_b = unblockErr === null || unblockErr === void 0 ? void 0 : unblockErr.message) !== null && _b !== void 0 ? _b : String(unblockErr);
214
+ logger_1.default.error(`⚠️ Failed to unblock device for session ${sessionId}: ${msg}`, unblockErr);
215
+ }
216
+ }
217
+ // Final local cleanup to prevent state leaks
218
+ this.sessionToDevice.delete(sessionId);
219
+ this.lastLogLine.delete(sessionId);
220
+ const orphanUdid = this.sessionToUdid.get(sessionId);
221
+ if (orphanUdid) {
222
+ this.sessionToUdid.delete(sessionId);
223
+ this.syslogServices.delete(orphanUdid);
224
+ }
225
+ }
226
+ else {
227
+ logger_1.default.warn(`⚠️ Session ${sessionId} not found in SESSION_MANAGER`);
228
+ // Fallback: If session not in manager, attempt to unblock by session_id in store
229
+ const { unblockDeviceMatchingFilter } = yield Promise.resolve().then(() => __importStar(require('../data-service/device-service')));
230
+ try {
231
+ yield unblockDeviceMatchingFilter({ session_id: sessionId });
232
+ logger_1.default.info(`🔓 [${sessionId}] Orphaned device released via session_id fallback.`);
233
+ }
234
+ catch (unblockErr) {
235
+ const msg = (_c = unblockErr === null || unblockErr === void 0 ? void 0 : unblockErr.message) !== null && _c !== void 0 ? _c : String(unblockErr);
236
+ logger_1.default.error(`⚠️ Failed to unblock device for orphaned session ${sessionId}: ${msg}`, unblockErr);
237
+ }
238
+ finally {
239
+ this.sessionToDevice.delete(sessionId);
240
+ this.lastLogLine.delete(sessionId);
241
+ const orphanUdid = this.sessionToUdid.get(sessionId);
242
+ if (orphanUdid) {
243
+ this.sessionToUdid.delete(sessionId);
244
+ this.syslogServices.delete(orphanUdid);
245
+ }
246
+ }
247
+ }
248
+ const sessionEntry = yield (0, session_service_1.getSessionById)(sessionId);
249
+ if (sessionEntry) {
250
+ logger_1.default.info(`Session ${sessionId} current status: ${sessionEntry.status}`);
251
+ const updateData = {
252
+ endTime: new Date(),
253
+ has_live_video: false,
254
+ };
255
+ // Principal Intelligence: Determined final status based on command history
256
+ if (status) {
257
+ updateData['status'] = status;
258
+ if (failureReason)
259
+ updateData['failure_reason'] = failureReason;
260
+ }
261
+ else if (sessionEntry.status === SessionStatus_1.SessionStatus.RUNNING ||
262
+ !sessionEntry.status ||
263
+ sessionEntry.status === SessionStatus_1.SessionStatus.UNMARKED) {
264
+ // Check if any command failed in this session
265
+ const failedCommand = yield prisma_1.prisma.sessionLog.findFirst({
266
+ where: { session_id: sessionId, is_error: true },
267
+ orderBy: { createdAt: 'desc' },
268
+ });
269
+ if (failedCommand) {
270
+ updateData['status'] = SessionStatus_1.SessionStatus.FAILED;
271
+ updateData['failure_reason'] =
272
+ failedCommand.response && failedCommand.response.includes('error')
273
+ ? ((_d = (0, helpers_1.safeParseJson)(failedCommand.response).value) === null || _d === void 0 ? void 0 : _d.error) ||
274
+ `Command failed: ${failedCommand.command_name}`
275
+ : `Command failed: ${failedCommand.command_name}`;
276
+ logger_1.default.info(`Session ${sessionId} marked as FAILED due to error in command: ${failedCommand.command_name}`);
277
+ }
278
+ else {
279
+ updateData['status'] = SessionStatus_1.SessionStatus.SUCCESS;
280
+ logger_1.default.info(`Session ${sessionId} marked as SUCCESS`);
281
+ }
282
+ }
283
+ else {
284
+ // Principal Reliability: If the session already has a terminal status,
285
+ // ensure we still use that status for metrics and events below.
286
+ updateData['status'] = sessionEntry.status;
287
+ }
288
+ yield (0, session_service_1.updateSessionDetails)(sessionId, updateData);
289
+ logger_1.default.info(`✅ Session ${sessionId} updated successfully`);
290
+ // 🟢 Socket Events must happen AFTER DB update and MUST include a status
291
+ // to ensure the UI row changes from 'RUNNING' to its final state.
292
+ typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboard(SocketEvents_1.SocketEvents.SESSION_STOPPED, {
293
+ id: sessionId,
294
+ status: updateData.status || sessionEntry.status || SessionStatus_1.SessionStatus.SUCCESS,
295
+ failure_reason: updateData.failure_reason || sessionEntry.failure_reason,
296
+ });
297
+ // Principal Analytics: Increment Metrics AFTER emission
298
+ if (updateData.status === SessionStatus_1.SessionStatus.SUCCESS) {
299
+ typedi_1.Container.get(MetricsService_1.MetricsService).incrementSessionSuccess();
300
+ }
301
+ else if (updateData.status === SessionStatus_1.SessionStatus.FAILED) {
302
+ typedi_1.Container.get(MetricsService_1.MetricsService).incrementSessionFailure();
303
+ }
304
+ // Principal Triage: If session failed, perform intelligent failure analysis
305
+ if (updateData.status === SessionStatus_1.SessionStatus.FAILED) {
306
+ try {
307
+ const { analyzeSessionFailure } = yield Promise.resolve().then(() => __importStar(require('./services/failure-analysis-service')));
308
+ yield analyzeSessionFailure(sessionId);
309
+ }
310
+ catch (analysisErr) {
311
+ logger_1.default.warn(`⚠️ Failure analysis skipped for ${sessionId}: ${analysisErr.message}`);
312
+ }
313
+ }
314
+ }
315
+ else {
316
+ logger_1.default.warn(`⚠️ Session ${sessionId} not found in database`);
317
+ }
318
+ }
319
+ finally {
320
+ // Always release the idempotency lock so future cleanup calls
321
+ // (e.g., manual recovery) can proceed if needed.
322
+ this.stoppingSessionIds.delete(sessionId);
323
+ }
324
+ });
325
+ }
326
+ beforeSessionCommand(sessionId, commandName, request, response) {
327
+ return __awaiter(this, void 0, void 0, function* () {
328
+ var _a;
329
+ if (commandName) {
330
+ this.commandStartTime.set(`${sessionId}:${commandName}`, Date.now());
331
+ logger_1.default.debug(`[EventManager] beforeSessionCommand: sessionId=${sessionId}, commandName=${commandName}`);
332
+ }
333
+ // Principal Interception: Handle Xenon-specific commands regardless of session state in memory
334
+ if (commandName === 'execute') {
335
+ const script = ((_a = request.body) === null || _a === void 0 ? void 0 : _a.script) || (Array.isArray(request.body) ? request.body[0] : undefined);
336
+ if (script && commands_1.dashboardCommands.isDashboardCommand(script)) {
337
+ logger_1.default.info(`[EventManager] Intercepting Xenon command: ${script} for session ${sessionId}`);
338
+ yield commands_1.dashboardCommands.process(sessionId, request, response);
339
+ return false;
340
+ }
341
+ else if (script && script.includes(':')) {
342
+ logger_1.default.debug(`[EventManager] Custom command ${script} not handled by Xenon. Passing to driver.`);
343
+ }
344
+ }
345
+ const session = SessionManager_1.SESSION_MANAGER.getSession(sessionId);
346
+ if (!session) {
347
+ logger_1.default.debug(`[EventManager] No session object found in memory for ${sessionId}. Allowing command ${commandName} to proceed.`);
348
+ return true;
349
+ }
350
+ if (commandName === 'deleteSession') {
351
+ // Video recording is handled in onSessionStoped() called after deleteSession
352
+ // No need to handle it here to avoid race conditions
353
+ }
354
+ return true;
355
+ });
356
+ }
357
+ afterSessionCommand(sessionId, commandName, driver, request, response, responseBody, healingInfo) {
358
+ return __awaiter(this, void 0, void 0, function* () {
359
+ var _a;
360
+ const session = SessionManager_1.SESSION_MANAGER.getSession(sessionId);
361
+ if (session) {
362
+ try {
363
+ // Save device logs (only if driver is available)
364
+ if (driver) {
365
+ yield this.saveDeviceLogs(sessionId, driver);
366
+ }
367
+ // Save command log
368
+ const parsedResponse = (0, helpers_1.safeParseJson)(responseBody);
369
+ const isSuccessResponse = !((_a = parsedResponse === null || parsedResponse === void 0 ? void 0 : parsedResponse.value) === null || _a === void 0 ? void 0 : _a.error);
370
+ const tracingService = typedi_1.Container.get(TracingService_1.TracingService);
371
+ const spanId = tracingService.getSpanId(`${session.getId()}:${commandName}`);
372
+ const traceId = tracingService.getTraceId(session.getId());
373
+ const startTime = this.commandStartTime.get(`${sessionId}:${commandName}`);
374
+ const duration = startTime ? Date.now() - startTime : null;
375
+ const logEntry = {
376
+ session_id: session.getId(),
377
+ command_name: commandName || null,
378
+ body: JSON.stringify(request.body),
379
+ response: responseBody,
380
+ is_success: isSuccessResponse,
381
+ is_error: !isSuccessResponse,
382
+ method: request.method,
383
+ title: this.getTitleFromCommandName(commandName),
384
+ subtitle: '',
385
+ screenshot: null,
386
+ url: request.originalUrl,
387
+ is_healed: !!healingInfo,
388
+ original_selector: (healingInfo === null || healingInfo === void 0 ? void 0 : healingInfo.originalSelector) || null,
389
+ healed_selector: (healingInfo === null || healingInfo === void 0 ? void 0 : healingInfo.healedSelector) || null,
390
+ healing_confidence: (healingInfo === null || healingInfo === void 0 ? void 0 : healingInfo.confidence) || null,
391
+ span_id: spanId,
392
+ trace_id: traceId,
393
+ duration: duration,
394
+ };
395
+ // Increment Healing Metrics
396
+ if (healingInfo) {
397
+ typedi_1.Container.get(MetricsService_1.MetricsService).incrementHealingAttempt();
398
+ if (isSuccessResponse) {
399
+ typedi_1.Container.get(MetricsService_1.MetricsService).incrementHealingSuccess();
400
+ }
401
+ }
402
+ if (startTime) {
403
+ this.commandStartTime.delete(`${sessionId}:${commandName}`);
404
+ }
405
+ // Take screenshots for specific commands (like click, setValue, etc.)
406
+ // OR on failure if SCREENSHOT_ON_FAILURE capability is enabled
407
+ const shouldTakeScreenshotForCommand = commandName && config_1.config.takeScreenshotsFor.indexOf(commandName) >= 0;
408
+ const screenShotCapability = session.getXenonOption(XenonCapabilityManager_1.XENON_CAPABILITIES.SCREENSHOT_ON_FAILURE, false);
409
+ const screenshotEveryCommandCapability = session.getXenonOption(XenonCapabilityManager_1.XENON_CAPABILITIES.SCREENSHOT_ON_EVERY_COMMAND, false);
410
+ const shouldTakeScreenshotOnFailure = !lodash_1.default.isNil(screenShotCapability) &&
411
+ screenShotCapability.toString() === 'true' &&
412
+ !isSuccessResponse;
413
+ const shouldTakeScreenshotOnEveryCommand = !lodash_1.default.isNil(screenshotEveryCommandCapability) &&
414
+ screenshotEveryCommandCapability.toString() === 'true';
415
+ if (shouldTakeScreenshotForCommand ||
416
+ shouldTakeScreenshotOnFailure ||
417
+ shouldTakeScreenshotOnEveryCommand) {
418
+ let screenshotBase64 = null;
419
+ try {
420
+ // Principal Intelligence: Always prefer session.getScreenShot() because it contains
421
+ // platform-specific optimizations (like direct, high-speed ADB capture for Android).
422
+ screenshotBase64 = yield session.getScreenShot();
423
+ }
424
+ catch (err) {
425
+ logger_1.default.warn(`[Dashboard] Session-level screenshot failed for ${sessionId}: ${err.message}. Trying direct driver...`);
426
+ if (driver) {
427
+ try {
428
+ screenshotBase64 = yield (0, helpers_2.takeScreenshot)(driver);
429
+ }
430
+ catch (driverErr) {
431
+ logger_1.default.error(`[Dashboard] Driver screenshot also failed: ${driverErr.message}`);
432
+ }
433
+ }
434
+ }
435
+ if (screenshotBase64) {
436
+ logEntry['screenshot'] = (0, asset_manager_1.saveScreenShot)(session.getId(), screenshotBase64);
437
+ }
438
+ }
439
+ yield prisma_1.prisma.sessionLog.create({
440
+ data: logEntry,
441
+ });
442
+ // Emit command log event to dashboard
443
+ typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboard(SocketEvents_1.SocketEvents.SESSION_COMMAND, Object.assign({ session_id: session.getId() }, logEntry));
444
+ }
445
+ catch (err) {
446
+ logger_1.default.error(`[Dashboard] Failed to process command telemetry for ${sessionId}: ${err.message}`);
447
+ }
448
+ }
449
+ });
450
+ }
451
+ onSessionLog(sessionId, logEntry) {
452
+ return __awaiter(this, void 0, void 0, function* () {
453
+ yield prisma_1.prisma.log.create({
454
+ data: {
455
+ session_id: sessionId,
456
+ log_type: logEntry.level.toUpperCase(),
457
+ message: logEntry.message,
458
+ timestamp: new Date(),
459
+ },
460
+ });
461
+ });
462
+ }
463
+ getTitleFromCommandName(commandName) {
464
+ if (commandName) {
465
+ return commandName.replace(/([A-Z])/g, ' $1').replace(/^./, function (str) {
466
+ return str.toUpperCase();
467
+ });
468
+ }
469
+ return undefined;
470
+ }
471
+ getDeviceLogs(driver, sessionId) {
472
+ return __awaiter(this, void 0, void 0, function* () {
473
+ try {
474
+ if (!driver || !driver.caps || !driver.caps.automationName) {
475
+ return [];
476
+ }
477
+ const automationName = driver.caps.automationName.toLowerCase();
478
+ // Get device info for this session
479
+ const device = this.sessionToDevice.get(sessionId);
480
+ // Use Appium driver's extractLogs method for proper log extraction
481
+ if (automationName === 'xcuitest' && typeof driver.extractLogs === 'function') {
482
+ try {
483
+ // Check if this is a real iOS device using device.realDevice property
484
+ const isRealDevice = (device === null || device === void 0 ? void 0 : device.realDevice) === true;
485
+ logger_1.default.debug(`Device info for session ${sessionId} - isRealDevice: ${isRealDevice}, deviceType: ${device === null || device === void 0 ? void 0 : device.deviceType}, UDID: ${device === null || device === void 0 ? void 0 : device.udid}`);
486
+ if (isRealDevice) {
487
+ // For real iOS devices, use appium-ios-device syslog service
488
+ const udid = driver.caps.udid;
489
+ // Track session to UDID mapping for cleanup
490
+ this.sessionToUdid.set(sessionId, udid);
491
+ // If we don't have a syslog service for this device yet, start one
492
+ if (!this.syslogServices.has(udid)) {
493
+ try {
494
+ const syslogService = yield appium_ios_device_1.services.startSyslogService(udid);
495
+ const logs = [];
496
+ // Start listening to logs and buffer them
497
+ syslogService.start((logLine) => {
498
+ logs.push(logLine);
499
+ });
500
+ // Store the service and logs
501
+ this.syslogServices.set(udid, { service: syslogService, logs });
502
+ logger_1.default.info(`Started syslog service for real iOS device ${udid}`);
503
+ }
504
+ catch (err) {
505
+ logger_1.default.debug(`Could not start syslog service for real device: ${err}`);
506
+ return [];
507
+ }
508
+ }
509
+ // Return the buffered logs
510
+ const deviceData = this.syslogServices.get(udid);
511
+ if (deviceData && deviceData.logs) {
512
+ const currentLogs = [...deviceData.logs];
513
+ // Clear the buffer after retrieving
514
+ deviceData.logs.length = 0;
515
+ return currentLogs.map((logLine) => ({ message: logLine, timestamp: Date.now() }));
516
+ }
517
+ return [];
518
+ }
519
+ // For iOS simulators, extract syslog
520
+ const logs = yield driver.extractLogs('syslog');
521
+ return Array.isArray(logs) ? logs : [];
522
+ }
523
+ catch (err) {
524
+ logger_1.default.debug(`Could not extract syslog: ${err}`);
525
+ return [];
526
+ }
527
+ }
528
+ else if (automationName === 'uiautomator2' || (device === null || device === void 0 ? void 0 : device.platform) === 'android') {
529
+ try {
530
+ if (typeof driver.extractLogs === 'function') {
531
+ const logs = yield driver.extractLogs('logcat');
532
+ if (Array.isArray(logs) && logs.length > 0) {
533
+ return logs;
534
+ }
535
+ }
536
+ }
537
+ catch (err) {
538
+ logger_1.default.debug(`Could not extract logcat via driver: ${err}. Trying direct ADB...`);
539
+ }
540
+ // Principal Intelligence: Fallback to direct ADB logs if driver fails or returns nothing
541
+ if (device === null || device === void 0 ? void 0 : device.udid) {
542
+ try {
543
+ const deviceManager = typedi_1.Container.get(device_managers_1.XenonManager);
544
+ const androidManager = (yield deviceManager.deviceInstances()).find((m) => m instanceof AndroidDeviceManager_1.default);
545
+ if (androidManager) {
546
+ const rawLogs = yield androidManager.getLogs(device.udid);
547
+ if (rawLogs && typeof rawLogs === 'string') {
548
+ const logLines = rawLogs.split('\n').filter((l) => l.trim().length > 0);
549
+ // Convert string lines to expected format (last 100 lines to avoid DB bloat)
550
+ return logLines.slice(-100).map((line) => ({
551
+ message: line,
552
+ timestamp: Date.now(),
553
+ level: 'INFO',
554
+ }));
555
+ }
556
+ }
557
+ }
558
+ catch (adbErr) {
559
+ logger_1.default.debug(`Direct ADB log fetch failed for ${device.udid}: ${adbErr.message}`);
560
+ }
561
+ }
562
+ }
563
+ return [];
564
+ }
565
+ catch (error) {
566
+ logger_1.default.error(`Error getting device logs: ${error}`);
567
+ return [];
568
+ }
569
+ });
570
+ }
571
+ saveDeviceLogs(sessionId, driver) {
572
+ return __awaiter(this, void 0, void 0, function* () {
573
+ try {
574
+ const logs = yield this.getDeviceLogs(driver, sessionId);
575
+ if (!logs || logs.length === 0) {
576
+ return;
577
+ }
578
+ const lastLine = this.lastLogLine.get(sessionId) || 0;
579
+ const newLogs = logs.slice(lastLine);
580
+ if (newLogs.length === 0) {
581
+ return;
582
+ }
583
+ this.lastLogLine.set(sessionId, logs.length);
584
+ // Save device logs to database
585
+ const logEntries = newLogs.map((logItem) => ({
586
+ session_id: sessionId,
587
+ log_type: 'DEVICE',
588
+ message: typeof logItem === 'string' ? logItem : logItem.message || JSON.stringify(logItem),
589
+ timestamp: logItem.timestamp ? new Date(logItem.timestamp) : new Date(),
590
+ }));
591
+ if (logEntries.length > 0) {
592
+ for (const logEntry of logEntries) {
593
+ yield prisma_1.prisma.log.create({
594
+ data: logEntry,
595
+ });
596
+ }
597
+ }
598
+ }
599
+ catch (error) {
600
+ logger_1.default.error(`Error saving device logs: ${error.message}`);
601
+ }
602
+ });
603
+ }
604
+ startAppProfiling(sessionId, device, sessionCapabilities) {
605
+ return __awaiter(this, void 0, void 0, function* () {
606
+ // Only support Android profiling
607
+ if (device.platform.toLowerCase() !== 'android') {
608
+ return { is_profiling_available: false };
609
+ }
610
+ // Check if we have an app package
611
+ const appPackage = (sessionCapabilities === null || sessionCapabilities === void 0 ? void 0 : sessionCapabilities.appPackage) || (sessionCapabilities === null || sessionCapabilities === void 0 ? void 0 : sessionCapabilities['appium:appPackage']);
612
+ if (!appPackage) {
613
+ logger_1.default.info(`[Profiling] No app package found for session ${sessionId}, skipping profiling`);
614
+ return { is_profiling_available: false };
615
+ }
616
+ try {
617
+ // Principal Intelligence: Use the Properly configured ADB from the manager
618
+ // instead of a naked instance, to avoid 'defaultArgs not iterable' crashes.
619
+ const deviceManager = typedi_1.Container.get(device_managers_1.XenonManager);
620
+ const androidManager = (yield deviceManager.deviceInstances()).find((m) => m instanceof AndroidDeviceManager_1.default);
621
+ let adb;
622
+ if (androidManager) {
623
+ adb = yield androidManager.getAdbForDevice(device.udid);
624
+ }
625
+ else {
626
+ adb = yield appium_adb_1.ADB.createADB({});
627
+ }
628
+ // Create app profiler
629
+ const profiler = new AndroidAppProfiler_1.AndroidAppProfiler({
630
+ adb,
631
+ deviceUDID: device.udid,
632
+ appPackage,
633
+ });
634
+ // Get device info and start capture
635
+ const device_info = yield profiler.getDeviceInfo();
636
+ yield profiler.startCapture();
637
+ // Store profiler for this session
638
+ this.appProfilers.set(sessionId, profiler);
639
+ logger_1.default.info(`[Profiling] Started app profiling for session ${sessionId}`);
640
+ return {
641
+ is_profiling_available: true,
642
+ device_info,
643
+ };
644
+ }
645
+ catch (err) {
646
+ logger_1.default.error(`[Profiling] Error initializing app profiler for session ${sessionId}: ${err.message}`);
647
+ return { is_profiling_available: false };
648
+ }
649
+ });
650
+ }
651
+ saveAppProfilingData(sessionId) {
652
+ return __awaiter(this, void 0, void 0, function* () {
653
+ const profiler = this.appProfilers.get(sessionId);
654
+ if (!profiler) {
655
+ return;
656
+ }
657
+ try {
658
+ // Stop capture
659
+ yield profiler.stopCapture();
660
+ // Get logs
661
+ const logs = profiler.getLogs();
662
+ if (logs.length === 0) {
663
+ logger_1.default.info(`[Profiling] No profiling data to save for session ${sessionId}`);
664
+ return;
665
+ }
666
+ // Save to database
667
+ const profilingEntries = logs.map((log) => ({
668
+ session_id: sessionId,
669
+ cpu: log.cpu,
670
+ memory: log.memory,
671
+ total_cpu_used: log.total_cpu_used.toString(),
672
+ total_memory_used: log.total_memory_used.toString(),
673
+ raw_cpu_log: log.raw_cpu_log,
674
+ raw_memory_log: log.raw_memory_log,
675
+ timestamp: new Date(log.timestamp),
676
+ }));
677
+ // Batch insert profiling data
678
+ for (const entry of profilingEntries) {
679
+ yield prisma_1.prisma.profiling.create({
680
+ data: entry,
681
+ });
682
+ }
683
+ logger_1.default.info(`[Profiling] Saved ${profilingEntries.length} profiling entries for session ${sessionId}`);
684
+ }
685
+ catch (err) {
686
+ logger_1.default.error(`[Profiling] Error saving profiling data for session ${sessionId}: ${err.message}`);
687
+ }
688
+ finally {
689
+ // Clean up profiler
690
+ this.appProfilers.delete(sessionId);
691
+ }
692
+ });
693
+ }
694
+ };
695
+ exports.DashboardEventManager = DashboardEventManager;
696
+ exports.DashboardEventManager = DashboardEventManager = __decorate([
697
+ (0, typedi_2.Service)()
698
+ ], DashboardEventManager);
699
+ exports.DASHBORD_EVENT_MANAGER = typedi_1.Container.get(DashboardEventManager);