@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,395 @@
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 __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
23
+ var __importDefault = (this && this.__importDefault) || function (mod) {
24
+ return (mod && mod.__esModule) ? mod : { "default": mod };
25
+ };
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.InspectorService = void 0;
28
+ const typedi_1 = require("typedi");
29
+ const logger_1 = __importDefault(require("../logger"));
30
+ const PluginContext_1 = require("../PluginContext");
31
+ const fast_xml_parser_1 = require("fast-xml-parser");
32
+ let InspectorService = class InspectorService {
33
+ constructor(context) {
34
+ this.context = context;
35
+ this.log = logger_1.default.scope('Inspector');
36
+ this.parser = new fast_xml_parser_1.XMLParser({
37
+ ignoreAttributes: false,
38
+ attributeNamePrefix: '',
39
+ parseAttributeValue: true,
40
+ });
41
+ }
42
+ getSnapshot(udid) {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ try {
45
+ this.log.info(`Generating deep snapshot for device: ${udid}`);
46
+ // 1. Get Device Manager
47
+ const device = yield typedi_1.Container.get('DeviceStore').findDevice({ udid });
48
+ if (!device)
49
+ throw new Error(`Device ${udid} not found`);
50
+ let manager;
51
+ if (device.platform === 'android') {
52
+ manager = typedi_1.Container.get('AndroidDeviceManager');
53
+ }
54
+ else {
55
+ manager = typedi_1.Container.get('IOSDeviceManager');
56
+ }
57
+ // 2. Parallel Capture (Native Source + Screenshot)
58
+ const [screenshotBase64, xmlSource] = yield Promise.all([
59
+ manager.getScreenshot
60
+ ? manager.getScreenshot(udid)
61
+ : Promise.reject('Screenshot not supported'),
62
+ manager.getPageSource
63
+ ? manager.getPageSource(udid)
64
+ : Promise.reject('Page source not supported'),
65
+ ]);
66
+ // 3. Ensure Metadata (Lazy load dimensions if missing)
67
+ if (!device.screenWidth || !device.screenHeight) {
68
+ try {
69
+ if (manager.getAdditionalDeviceInfo) {
70
+ const additional = yield manager.getAdditionalDeviceInfo(device);
71
+ Object.assign(device, additional);
72
+ // Update store asynchronously
73
+ typedi_1.Container.get('DeviceStore')
74
+ .updateDevice(udid, device.host, additional)
75
+ .catch(() => { });
76
+ }
77
+ }
78
+ catch (e) {
79
+ this.log.warn(`Failed to lazy-load dimensions for ${udid}: ${e}`);
80
+ }
81
+ }
82
+ // 4. Parse Native Hierarchy
83
+ const hierarchy = this.parseXmlHierarchy(xmlSource, device.platform);
84
+ return {
85
+ udid,
86
+ platform: device.platform,
87
+ timestamp: new Date().toISOString(),
88
+ screenshot: screenshotBase64,
89
+ hierarchy,
90
+ metadata: {
91
+ screenWidth: parseInt(device.screenWidth || '0') || 393, // Fallback to common iPhone width
92
+ screenHeight: parseInt(device.screenHeight || '0') || 852,
93
+ },
94
+ };
95
+ }
96
+ catch (err) {
97
+ this.log.error(`Failed to generate deep snapshot for ${udid}: ${err.message}`);
98
+ throw err;
99
+ }
100
+ });
101
+ }
102
+ parseXmlHierarchy(xml, platform) {
103
+ var _a;
104
+ try {
105
+ const jsonObj = this.parser.parse(xml);
106
+ if (platform === 'android') {
107
+ // Android hierarchy usually starts with <hierarchy>
108
+ const root = ((_a = jsonObj.hierarchy) === null || _a === void 0 ? void 0 : _a.node) || jsonObj.hierarchy;
109
+ return this.transformAndroidNode(root, '');
110
+ }
111
+ else {
112
+ // iOS hierarchy usually starts with <AppiumAUT>
113
+ const root = jsonObj.AppiumAUT || jsonObj;
114
+ return this.transformIosNode(root, '');
115
+ }
116
+ }
117
+ catch (e) {
118
+ this.log.error(`Failed to parse XML hierarchy: ${e.message}`);
119
+ return {
120
+ name: 'root',
121
+ type: 'error',
122
+ rect: { x: 0, y: 0, width: 0, height: 0 },
123
+ xpath: '/',
124
+ children: [],
125
+ attributes: {},
126
+ suggestedLocators: [],
127
+ suggestedActions: [],
128
+ };
129
+ }
130
+ }
131
+ transformAndroidNode(node, root) {
132
+ var _a;
133
+ const type = node.class || 'Unknown';
134
+ const name = node.resourceId || node.text || type.split('.').pop() || 'node';
135
+ const bounds = this.parseAndroidBounds(node.bounds || '[0,0][0,0]');
136
+ const xpath = node.xpath || ''; // Placeholder for now, real xpath handled by generator
137
+ const children = Array.isArray(node.node)
138
+ ? node.node.map((c) => this.transformAndroidNode(c, root))
139
+ : node.node
140
+ ? [this.transformAndroidNode(node.node, root)]
141
+ : [];
142
+ const attributes = {};
143
+ Object.entries(node).forEach(([key, value]) => {
144
+ // Filter out internal structural keys and non-primitive values
145
+ if (key !== 'node' &&
146
+ key !== 'xpath' &&
147
+ (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')) {
148
+ attributes[key] = value;
149
+ }
150
+ });
151
+ const nodeObj = {
152
+ name,
153
+ type,
154
+ text: node.text,
155
+ rect: bounds,
156
+ xpath,
157
+ suggestedLocators: [],
158
+ suggestedActions: [],
159
+ children,
160
+ attributes,
161
+ };
162
+ nodeObj.suggestedLocators = this.generateLocators(nodeObj, 'android', root || node);
163
+ nodeObj.suggestedActions = this.generateActions(nodeObj);
164
+ nodeObj.xpath = ((_a = nodeObj.suggestedLocators.find((l) => l.strategy === 'xpath')) === null || _a === void 0 ? void 0 : _a.value) || '/';
165
+ return nodeObj;
166
+ }
167
+ transformIosNode(node, root) {
168
+ var _a;
169
+ const type = Object.keys(node).find((k) => k.startsWith('XCUIElement')) || 'Unknown';
170
+ const data = node[type];
171
+ const name = (data === null || data === void 0 ? void 0 : data.name) || (data === null || data === void 0 ? void 0 : data.label) || type;
172
+ const rect = {
173
+ x: (data === null || data === void 0 ? void 0 : data.x) || 0,
174
+ y: (data === null || data === void 0 ? void 0 : data.y) || 0,
175
+ width: (data === null || data === void 0 ? void 0 : data.width) || 0,
176
+ height: (data === null || data === void 0 ? void 0 : data.height) || 0,
177
+ };
178
+ const children = [];
179
+ Object.keys(data || {}).forEach((key) => {
180
+ if (key.startsWith('XCUIElement')) {
181
+ const childData = data[key];
182
+ if (Array.isArray(childData)) {
183
+ childData.forEach((c) => children.push(this.transformIosNode({ [key]: c }, root)));
184
+ }
185
+ else {
186
+ children.push(this.transformIosNode({ [key]: childData }, root));
187
+ }
188
+ }
189
+ });
190
+ const attributes = {};
191
+ Object.entries(data || {}).forEach(([key, value]) => {
192
+ // Filter out nested element keys (XCUIElement...) and non-primitive values
193
+ if (!key.startsWith('XCUIElement') &&
194
+ (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')) {
195
+ attributes[key] = value;
196
+ }
197
+ });
198
+ const nodeObj = {
199
+ name,
200
+ type,
201
+ text: (data === null || data === void 0 ? void 0 : data.label) || (data === null || data === void 0 ? void 0 : data.value),
202
+ rect,
203
+ xpath: '',
204
+ suggestedLocators: [],
205
+ suggestedActions: [],
206
+ children,
207
+ attributes,
208
+ };
209
+ nodeObj.suggestedLocators = this.generateLocators(nodeObj, 'ios', root || node);
210
+ nodeObj.suggestedActions = this.generateActions(nodeObj);
211
+ nodeObj.xpath = ((_a = nodeObj.suggestedLocators.find((l) => l.strategy === 'xpath')) === null || _a === void 0 ? void 0 : _a.value) || '/';
212
+ return nodeObj;
213
+ }
214
+ generateActions(node) {
215
+ const actions = [];
216
+ const type = node.type.toLowerCase();
217
+ // Common clickable elements
218
+ if (type.includes('button') ||
219
+ type.includes('imageview') ||
220
+ type.includes('icon') ||
221
+ node.attributes.clickable === 'true' ||
222
+ node.attributes.enabled === 'true') {
223
+ actions.push({
224
+ action: 'click',
225
+ snippet: 'await element.click();',
226
+ description: 'Performs a single tap interaction',
227
+ });
228
+ }
229
+ // Common input elements
230
+ if (type.includes('edit') ||
231
+ type.includes('textfield') ||
232
+ type.includes('input') ||
233
+ type.includes('search')) {
234
+ actions.push({
235
+ action: 'sendKeys',
236
+ snippet: 'await element.sendKeys("value");',
237
+ description: 'Inputs text into the element',
238
+ });
239
+ actions.push({
240
+ action: 'clear',
241
+ snippet: 'await element.clear();',
242
+ description: 'Clears the text content',
243
+ });
244
+ }
245
+ return actions;
246
+ }
247
+ generateLocators(node, platform, root) {
248
+ const suggestions = [];
249
+ // 1. Accessibility ID - GOLD STANDARD
250
+ // Android: content-desc | iOS: name/accessibility-id
251
+ const contentDesc = node.attributes.contentDescription || node.attributes['content-desc'];
252
+ const iosName = node.attributes.name || node.attributes.label;
253
+ if (contentDesc || iosName) {
254
+ const val = contentDesc || iosName;
255
+ suggestions.push({
256
+ strategy: 'accessibility id',
257
+ value: val,
258
+ unique: this.checkUniqueness(root, 'accessibility id', val),
259
+ score: 100,
260
+ });
261
+ }
262
+ // 2. Resource ID / ID
263
+ // Android: resource-id | iOS: identifier/name
264
+ const resId = node.attributes.resourceId || node.attributes['resource-id'] || node.attributes.identifier;
265
+ if (resId) {
266
+ suggestions.push({
267
+ strategy: 'id',
268
+ value: resId,
269
+ unique: this.checkUniqueness(root, 'id', resId),
270
+ score: 95,
271
+ });
272
+ }
273
+ // 3. Platform Specific - Performance Tier
274
+ if (platform === 'ios') {
275
+ // iOS Predicate String - High Performance
276
+ const predicate = `type == "${node.type}" AND label == "${node.text || node.label || ''}"`;
277
+ if (node.text || node.label) {
278
+ suggestions.push({
279
+ strategy: '-ios predicate string',
280
+ value: predicate,
281
+ unique: true,
282
+ score: 90,
283
+ });
284
+ }
285
+ // iOS Class Chain - High Precision
286
+ const classChain = `**/${node.type}[` +
287
+ (node.attributes.name
288
+ ? `name == "${node.attributes.name}"`
289
+ : `label == "${node.text || ''}"`) +
290
+ `]`;
291
+ suggestions.push({
292
+ strategy: '-ios class chain',
293
+ value: classChain,
294
+ unique: true,
295
+ score: 85,
296
+ });
297
+ }
298
+ else {
299
+ // Android UIAutomator - Reliable Native
300
+ if (resId) {
301
+ suggestions.push({
302
+ strategy: '-android uiautomator',
303
+ value: `new UiSelector().resourceId("${resId}")`,
304
+ unique: true,
305
+ score: 88,
306
+ });
307
+ }
308
+ if (node.text) {
309
+ suggestions.push({
310
+ strategy: '-android uiautomator',
311
+ value: `new UiSelector().text("${node.text}")`,
312
+ unique: this.checkUniqueness(root, 'text', node.text),
313
+ score: 80,
314
+ });
315
+ }
316
+ }
317
+ // 4. XPath - Stable Semantic version
318
+ // Use tag name from the last part of Class name
319
+ const tagName = node.type.includes('.') ? node.type.split('.').pop() : node.type;
320
+ if (resId) {
321
+ suggestions.push({
322
+ strategy: 'xpath',
323
+ value: `//${tagName}[@resource-id="${resId}"]`,
324
+ unique: true,
325
+ score: 70,
326
+ });
327
+ }
328
+ else if (node.text) {
329
+ suggestions.push({
330
+ strategy: 'xpath',
331
+ value: `//${tagName}[@text="${node.text}"]`,
332
+ unique: this.checkUniqueness(root, 'xpath', `//${tagName}[@text="${node.text}"]`),
333
+ score: 60,
334
+ });
335
+ }
336
+ else if (contentDesc) {
337
+ suggestions.push({
338
+ strategy: 'xpath',
339
+ value: `//${tagName}[@content-desc="${contentDesc}"]`,
340
+ unique: true,
341
+ score: 65,
342
+ });
343
+ }
344
+ return suggestions;
345
+ }
346
+ checkUniqueness(root, strategy, value) {
347
+ let matches = 0;
348
+ const search = (n) => {
349
+ const node = n.node || n;
350
+ if (strategy === 'id') {
351
+ if (node.resourceId === value || node['resource-id'] === value || node.identifier === value)
352
+ matches++;
353
+ }
354
+ else if (strategy === 'accessibility id') {
355
+ if (node.accessibilityId === value ||
356
+ node['content-desc'] === value ||
357
+ node.contentDescription === value ||
358
+ node.name === value)
359
+ matches++;
360
+ }
361
+ else if (strategy === 'text') {
362
+ if (node.text === value || node.label === value)
363
+ matches++;
364
+ }
365
+ if (matches > 1)
366
+ return;
367
+ const children = n.node || [];
368
+ if (Array.isArray(children))
369
+ children.forEach(search);
370
+ else if (n.children && Array.isArray(n.children))
371
+ n.children.forEach(search);
372
+ else if (children && typeof children === 'object')
373
+ search(children);
374
+ };
375
+ search(root);
376
+ return matches === 1;
377
+ }
378
+ parseAndroidBounds(boundsStr) {
379
+ const matches = /\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(boundsStr);
380
+ if (matches) {
381
+ const x1 = parseInt(matches[1]);
382
+ const y1 = parseInt(matches[2]);
383
+ const x2 = parseInt(matches[3]);
384
+ const y2 = parseInt(matches[4]);
385
+ return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
386
+ }
387
+ return { x: 0, y: 0, width: 0, height: 0 };
388
+ }
389
+ };
390
+ exports.InspectorService = InspectorService;
391
+ exports.InspectorService = InspectorService = __decorate([
392
+ (0, typedi_1.Service)(),
393
+ __param(0, (0, typedi_1.Inject)()),
394
+ __metadata("design:paramtypes", [PluginContext_1.PluginContext])
395
+ ], InspectorService);
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.MetricsService = void 0;
22
+ const typedi_1 = require("typedi");
23
+ const SessionManager_1 = require("../sessions/SessionManager");
24
+ const device_store_1 = require("../data-service/device-store");
25
+ const prisma_1 = require("../prisma");
26
+ const logger_1 = __importDefault(require("../logger"));
27
+ let MetricsService = class MetricsService {
28
+ constructor() {
29
+ this.CONFIG_ID = 'metrics';
30
+ }
31
+ incrementMetric(name) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ var _a;
34
+ try {
35
+ const config = yield prisma_1.prisma.webConfig.findUnique({
36
+ where: { name },
37
+ });
38
+ const currentValue = config ? parseInt(config.value) : 0;
39
+ yield prisma_1.prisma.webConfig.upsert({
40
+ where: { name },
41
+ update: { value: (currentValue + 1).toString() },
42
+ create: { id: name, name, value: '1' },
43
+ });
44
+ }
45
+ catch (error) {
46
+ const msg = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : String(error);
47
+ logger_1.default.error(`[MetricsService] Failed to increment ${name}: ${msg}`, error);
48
+ }
49
+ });
50
+ }
51
+ getMetric(name) {
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ try {
54
+ const config = yield prisma_1.prisma.webConfig.findUnique({
55
+ where: { name },
56
+ });
57
+ return config ? parseInt(config.value) : 0;
58
+ }
59
+ catch (error) {
60
+ return 0;
61
+ }
62
+ });
63
+ }
64
+ incrementSessionStart() {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ yield this.incrementMetric('metric_session_starts');
67
+ });
68
+ }
69
+ incrementSessionSuccess() {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ yield this.incrementMetric('metric_session_successes');
72
+ });
73
+ }
74
+ incrementSessionFailure() {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ yield this.incrementMetric('metric_session_failures');
77
+ });
78
+ }
79
+ incrementHealingAttempt() {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ yield this.incrementMetric('metric_healing_attempts');
82
+ });
83
+ }
84
+ incrementHealingSuccess() {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ yield this.incrementMetric('metric_healing_successes');
87
+ });
88
+ }
89
+ getMetrics() {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ const sessions = SessionManager_1.SESSION_MANAGER.getStats();
92
+ const devices = yield device_store_1.DeviceStoreFactory.getStore().getAllDevices();
93
+ const totalDevices = devices.length;
94
+ const busyDevices = devices.filter((d) => d.busy).length;
95
+ const offlineDevices = devices.filter((d) => d.offline).length;
96
+ // Fetch persisted counters
97
+ const [sessionStarts, sessionSuccesses, sessionFailures, healingAttempts, healingSuccesses] = yield Promise.all([
98
+ this.getMetric('metric_session_starts'),
99
+ this.getMetric('metric_session_successes'),
100
+ this.getMetric('metric_session_failures'),
101
+ this.getMetric('metric_healing_attempts'),
102
+ this.getMetric('metric_healing_successes'),
103
+ ]);
104
+ const lines = [
105
+ '# HELP xenon_sessions_total Total number of sessions across all time',
106
+ '# TYPE xenon_sessions_total counter',
107
+ `xenon_sessions_total{status="started"} ${sessionStarts}`,
108
+ `xenon_sessions_total{status="success"} ${sessionSuccesses}`,
109
+ `xenon_sessions_total{status="failure"} ${sessionFailures}`,
110
+ '# HELP xenon_sessions_active Current active sessions in the hub',
111
+ '# TYPE xenon_sessions_active gauge',
112
+ `xenon_sessions_active ${sessions.total}`,
113
+ '# HELP xenon_devices_total Total managed devices in the fleet',
114
+ '# TYPE xenon_devices_total gauge',
115
+ `xenon_devices_total ${totalDevices}`,
116
+ '# HELP xenon_devices_busy Current devices in active use',
117
+ '# TYPE xenon_devices_busy gauge',
118
+ `xenon_devices_busy ${busyDevices}`,
119
+ '# HELP xenon_devices_offline Devices currently not reachable',
120
+ '# TYPE xenon_devices_offline gauge',
121
+ `xenon_devices_offline ${offlineDevices}`,
122
+ '# HELP xenon_healing_total AI self-healing operations',
123
+ '# TYPE xenon_healing_total counter',
124
+ `xenon_healing_total{status="attempt"} ${healingAttempts}`,
125
+ `xenon_healing_total{status="success"} ${healingSuccesses}`,
126
+ ];
127
+ return lines.join('\n') + '\n';
128
+ });
129
+ }
130
+ };
131
+ exports.MetricsService = MetricsService;
132
+ exports.MetricsService = MetricsService = __decorate([
133
+ (0, typedi_1.Service)()
134
+ ], MetricsService);