@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,301 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ const prisma_1 = require("../../prisma");
46
+ const SessionManager_1 = require("../../sessions/SessionManager");
47
+ const UniversalMjpegProxy_1 = require("../../helpers/UniversalMjpegProxy");
48
+ const web_config_service_1 = require("../../data-service/web-config-service");
49
+ const typedi_1 = require("typedi");
50
+ const MJPEG_PROXY_CACHE = new Map();
51
+ //session guard
52
+ function isValidSession(request, response, next) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ const sessionId = request.params.sessionId;
55
+ // Principal Robustness: Allow virtual manual sessions
56
+ if (sessionId && sessionId.startsWith('manual_')) {
57
+ return next();
58
+ }
59
+ const session = yield prisma_1.prisma.session.findFirst({
60
+ where: {
61
+ id: sessionId,
62
+ },
63
+ });
64
+ if (!session) {
65
+ return response.status(404).send({
66
+ error: true,
67
+ message: `Session with id ${sessionId} not found`,
68
+ });
69
+ }
70
+ else {
71
+ return next();
72
+ }
73
+ });
74
+ }
75
+ function getSessions(request, response) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ const { buildId, query, status, platform } = request.query;
78
+ const where = {};
79
+ if (buildId) {
80
+ where.build_id = buildId;
81
+ }
82
+ if (status) {
83
+ where.status = status;
84
+ }
85
+ if (platform) {
86
+ where.device_platform = platform;
87
+ }
88
+ if (query) {
89
+ where.OR = [
90
+ { id: { contains: query } },
91
+ { name: { contains: query } },
92
+ { device_udid: { contains: query } },
93
+ { device_name: { contains: query } },
94
+ { failure_category: { contains: query } },
95
+ { tags: { contains: query } },
96
+ ];
97
+ }
98
+ const sessions = yield prisma_1.prisma.session.findMany({
99
+ orderBy: {
100
+ createdAt: 'desc',
101
+ },
102
+ where,
103
+ take: 500,
104
+ });
105
+ return response.status(200).json(sessions);
106
+ });
107
+ }
108
+ function getBuilds(request, response) {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ const builds = yield prisma_1.prisma.build.findMany({
111
+ orderBy: {
112
+ createdAt: 'desc',
113
+ },
114
+ include: {
115
+ _count: {
116
+ select: { sessions: true },
117
+ },
118
+ sessions: {
119
+ select: {
120
+ status: true,
121
+ },
122
+ },
123
+ },
124
+ });
125
+ // Principal formatting: Add a flat summary object for the frontend
126
+ const formattedBuilds = builds.map((b) => (Object.assign(Object.assign({}, b), { sessionCount: b._count.sessions, passedCount: b.sessions.filter((s) => ['success', 'passed'].includes(s.status)).length, failedCount: b.sessions.filter((s) => s.status === 'failed').length, runningCount: b.sessions.filter((s) => s.status === 'running').length, sessions: undefined })));
127
+ return response.status(200).json(formattedBuilds);
128
+ });
129
+ }
130
+ function getSessionLogs(request, response) {
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ const sessionId = request.params.sessionId;
133
+ const logs = yield prisma_1.prisma.sessionLog.findMany({
134
+ orderBy: {
135
+ createdAt: 'desc',
136
+ },
137
+ where: {
138
+ session_id: sessionId,
139
+ },
140
+ });
141
+ return response.status(200).json(logs);
142
+ });
143
+ }
144
+ function getDeviceLogs(request, response) {
145
+ return __awaiter(this, void 0, void 0, function* () {
146
+ const sessionId = request.params.sessionId;
147
+ const logs = yield prisma_1.prisma.log.findMany({
148
+ orderBy: {
149
+ createdAt: 'asc',
150
+ },
151
+ where: {
152
+ session_id: sessionId,
153
+ log_type: 'DEVICE',
154
+ },
155
+ });
156
+ return response.status(200).json(logs);
157
+ });
158
+ }
159
+ function getDebugLogs(request, response) {
160
+ return __awaiter(this, void 0, void 0, function* () {
161
+ const sessionId = request.params.sessionId;
162
+ const logs = yield prisma_1.prisma.log.findMany({
163
+ orderBy: {
164
+ createdAt: 'asc',
165
+ },
166
+ where: {
167
+ session_id: sessionId,
168
+ log_type: 'DEBUG',
169
+ },
170
+ });
171
+ return response.status(200).json(logs);
172
+ });
173
+ }
174
+ function getProfilingData(request, response) {
175
+ return __awaiter(this, void 0, void 0, function* () {
176
+ const sessionId = request.params.sessionId;
177
+ const profilingData = yield prisma_1.prisma.profiling.findMany({
178
+ orderBy: {
179
+ timestamp: 'asc',
180
+ },
181
+ where: {
182
+ session_id: sessionId,
183
+ },
184
+ });
185
+ return response.status(200).json(profilingData);
186
+ });
187
+ }
188
+ function streamLiveSessionVideo(request, response) {
189
+ return __awaiter(this, void 0, void 0, function* () {
190
+ var _a;
191
+ const sessionId = request.params.sessionId;
192
+ const session = SessionManager_1.SESSION_MANAGER.getSession(sessionId);
193
+ const videoUrl = session === null || session === void 0 ? void 0 : session.getLiveVideoUrl();
194
+ if (videoUrl) {
195
+ if (!MJPEG_PROXY_CACHE.has(sessionId)) {
196
+ MJPEG_PROXY_CACHE.set(sessionId, new UniversalMjpegProxy_1.UniversalMjpegProxy(videoUrl));
197
+ }
198
+ // Principal Robustness: Ensure proxy is updated if URL changes
199
+ const existingProxy = MJPEG_PROXY_CACHE.get(sessionId);
200
+ if (existingProxy && existingProxy.mjpegUrl !== videoUrl) {
201
+ MJPEG_PROXY_CACHE.set(sessionId, new UniversalMjpegProxy_1.UniversalMjpegProxy(videoUrl));
202
+ }
203
+ (_a = MJPEG_PROXY_CACHE.get(sessionId)) === null || _a === void 0 ? void 0 : _a.proxyRequest(request, response);
204
+ }
205
+ else {
206
+ return response.status(500).send({
207
+ error: true,
208
+ message: `Live video not available for session with id ${sessionId}`,
209
+ });
210
+ }
211
+ });
212
+ }
213
+ function getGlobalConfig(request, response) {
214
+ return __awaiter(this, void 0, void 0, function* () {
215
+ try {
216
+ const dbConfig = yield typedi_1.Container.get(web_config_service_1.WebConfigService).getConfig();
217
+ // Merge with Environment Config (AI Settings)
218
+ const { config } = yield Promise.resolve().then(() => __importStar(require('../../config')));
219
+ // Sanitize keys - return boolean existence only
220
+ const aiConfig = {
221
+ aiProvider: config.aiProvider,
222
+ aiModel: config.aiModel,
223
+ aiBaseUrl: config.aiBaseUrl,
224
+ geminiModel: config.geminiModel,
225
+ openaiModel: config.openaiModel,
226
+ anthropicModel: config.anthropicModel,
227
+ ollamaModel: config.ollamaModel,
228
+ geminiSet: !!config.geminiApiKey,
229
+ openaiSet: !!config.openaiApiKey,
230
+ anthropicSet: !!config.anthropicApiKey,
231
+ };
232
+ return response.status(200).json(Object.assign(Object.assign({}, dbConfig), aiConfig));
233
+ }
234
+ catch (err) {
235
+ return response.status(500).json({ error: true, message: err.message });
236
+ }
237
+ });
238
+ }
239
+ function updateGlobalConfig(request, response) {
240
+ return __awaiter(this, void 0, void 0, function* () {
241
+ try {
242
+ const payload = request.body;
243
+ // Handle Runtime AI Config Overrides (Memory only)
244
+ // Only pass defined values to avoid overwriting env vars (e.g. aiBaseUrl, ollamaModel) with undefined
245
+ const runtimeOverrides = {};
246
+ if (payload.aiProvider !== undefined)
247
+ runtimeOverrides.aiProvider = payload.aiProvider;
248
+ if (payload.aiModel !== undefined)
249
+ runtimeOverrides.aiModel = payload.aiModel;
250
+ if (payload.aiBaseUrl !== undefined)
251
+ runtimeOverrides.aiBaseUrl = payload.aiBaseUrl;
252
+ if (payload.geminiModel !== undefined)
253
+ runtimeOverrides.geminiModel = payload.geminiModel;
254
+ if (payload.openaiModel !== undefined)
255
+ runtimeOverrides.openaiModel = payload.openaiModel;
256
+ if (payload.anthropicModel !== undefined)
257
+ runtimeOverrides.anthropicModel = payload.anthropicModel;
258
+ if (payload.ollamaModel !== undefined)
259
+ runtimeOverrides.ollamaModel = payload.ollamaModel;
260
+ if (Object.keys(runtimeOverrides).length > 0) {
261
+ const { updateConfig } = yield Promise.resolve().then(() => __importStar(require('../../config')));
262
+ updateConfig(runtimeOverrides);
263
+ }
264
+ // Persist Web Configs to DB
265
+ yield typedi_1.Container.get(web_config_service_1.WebConfigService).setConfig(payload);
266
+ return response.status(200).json({ success: true });
267
+ }
268
+ catch (err) {
269
+ return response.status(500).json({ error: true, message: err.message });
270
+ }
271
+ });
272
+ }
273
+ function resetMetrics(request, response) {
274
+ return __awaiter(this, void 0, void 0, function* () {
275
+ try {
276
+ const { DeviceStoreFactory } = yield Promise.resolve().then(() => __importStar(require('../../data-service/device-store')));
277
+ const store = DeviceStoreFactory.getStore();
278
+ yield store.resetMetrics();
279
+ return response.status(200).json({ success: true });
280
+ }
281
+ catch (err) {
282
+ return response.status(500).json({ error: true, message: err.message });
283
+ }
284
+ });
285
+ }
286
+ function register(router) {
287
+ router.use('/session/:sessionId', isValidSession);
288
+ router.get('/session', getSessions);
289
+ router.get('/build', getBuilds);
290
+ router.get('/session/:sessionId/live_video', streamLiveSessionVideo);
291
+ router.get('/session/:sessionId/session_log', getSessionLogs);
292
+ router.get('/session/:sessionId/logs/device', getDeviceLogs);
293
+ router.get('/session/:sessionId/logs/debug', getDebugLogs);
294
+ router.get('/session/:sessionId/profiling', getProfilingData);
295
+ router.get('/config', getGlobalConfig);
296
+ router.post('/config', updateGlobalConfig);
297
+ router.post('/config/reset-metrics', resetMetrics);
298
+ }
299
+ exports.default = {
300
+ register,
301
+ };
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ const device_store_1 = require("../../data-service/device-store");
49
+ const queue_service_1 = require("../../data-service/queue-service");
50
+ const InternalHttpClient_1 = require("../../InternalHttpClient");
51
+ const lodash_1 = __importDefault(require("lodash"));
52
+ const device_service_1 = require("../../data-service/device-service");
53
+ const logger_1 = __importDefault(require("../../logger"));
54
+ const device_managers_1 = require("../../device-managers");
55
+ const typedi_1 = require("typedi");
56
+ const store = device_store_1.DeviceStoreFactory.getStore();
57
+ const pendingStore = device_store_1.DeviceStoreFactory.getPendingSessionStore();
58
+ const SERVER_UP_TIME = new Date().toISOString();
59
+ function getDevices(request, response) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ var _a;
62
+ let devices = yield store.getAllDevices();
63
+ const { sessionId } = request.query;
64
+ if (sessionId) {
65
+ return response.json(devices.find((value) => value.session_id === sessionId));
66
+ }
67
+ /* dashboard-plugin-url is the base url for opening the appium-dashboard-plugin
68
+ * This value will be attached to all express request via middleware
69
+ */
70
+ const dashboardPluginUrl = request['dashboard-plugin-url'];
71
+ if (dashboardPluginUrl) {
72
+ const response = yield InternalHttpClient_1.InternalHttpClient.get(`${dashboardPluginUrl}/api/sessions?start_time=${SERVER_UP_TIME}`);
73
+ const sessions = ((_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.rows) || [];
74
+ const deviceSessionMap = {};
75
+ sessions.forEach((session) => {
76
+ if (!deviceSessionMap[session.udid]) {
77
+ deviceSessionMap[session.udid] = [];
78
+ }
79
+ deviceSessionMap[session.udid].push(session);
80
+ });
81
+ devices = devices.map((d) => {
82
+ var _a;
83
+ d.dashboard_link = `${dashboardPluginUrl}?device_udid=${d.udid}&start_time=${SERVER_UP_TIME}`;
84
+ d.total_session_count = ((_a = deviceSessionMap[d.udid]) === null || _a === void 0 ? void 0 : _a.length) || 0;
85
+ return d;
86
+ });
87
+ }
88
+ return response.json(devices);
89
+ });
90
+ }
91
+ function getDeviceByPlatform(request, response) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ const { platform } = request.params;
94
+ const { deviceType, booted } = request.query;
95
+ if (!platform || ['ios', 'android'].indexOf(platform.toLowerCase()) < 0) {
96
+ return response.status(200).send([]);
97
+ }
98
+ let devices = yield store.findDevices({
99
+ platform: platform.toLowerCase(),
100
+ });
101
+ if (!lodash_1.default.isNil(deviceType)) {
102
+ devices = devices.filter((value) => value.deviceType === deviceType);
103
+ }
104
+ if (!lodash_1.default.isNil(booted)) {
105
+ devices = devices.filter((d) => d.state === 'Booted');
106
+ }
107
+ return response.status(200).send(devices);
108
+ });
109
+ }
110
+ function registerNode(request, response) {
111
+ return __awaiter(this, void 0, void 0, function* () {
112
+ const requestBody = request.body;
113
+ const { type } = request.query;
114
+ if (type === 'add') {
115
+ const addedDevices = yield (0, device_service_1.addNewDevice)(requestBody);
116
+ if (addedDevices.length > 0) {
117
+ logger_1.default.info(`Added new devices: ${JSON.stringify(addedDevices)}`);
118
+ }
119
+ }
120
+ else if (type === 'remove') {
121
+ yield (0, device_service_1.removeDevice)(requestBody);
122
+ }
123
+ else if (type === 'unregister') {
124
+ const { host } = request.query;
125
+ if (host) {
126
+ yield (0, device_service_1.removeDevicesByHost)(host);
127
+ }
128
+ }
129
+ response.status(200).send({
130
+ success: true,
131
+ });
132
+ });
133
+ }
134
+ function blockDevice(request, response) {
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ const requestBody = request.body;
137
+ const device = yield (0, device_service_1.getDevice)(requestBody);
138
+ if (!lodash_1.default.isNil(device)) {
139
+ yield (0, device_service_1.userBlockDevice)(device.udid, device.host);
140
+ }
141
+ response.status(200).send({
142
+ success: true,
143
+ });
144
+ });
145
+ }
146
+ function unBlockDevice(request, response) {
147
+ return __awaiter(this, void 0, void 0, function* () {
148
+ const requestBody = request.body;
149
+ const device = yield (0, device_service_1.getDevice)(requestBody);
150
+ if (!lodash_1.default.isNil(device)) {
151
+ yield (0, device_service_1.userUnblockDevice)(device.udid, device.host);
152
+ }
153
+ response.status(200).send({
154
+ success: true,
155
+ });
156
+ });
157
+ }
158
+ function getQueuedSessionLength(request, response) {
159
+ return __awaiter(this, void 0, void 0, function* () {
160
+ response.json((yield pendingStore.getAllPendingSessions()).length);
161
+ });
162
+ }
163
+ function getQueuedSessionRequests(request, response) {
164
+ return __awaiter(this, void 0, void 0, function* () {
165
+ response.json(yield pendingStore.getAllPendingSessions());
166
+ });
167
+ }
168
+ function getNodes(request, response) {
169
+ return __awaiter(this, void 0, void 0, function* () {
170
+ const allDevices = yield store.getAllDevices();
171
+ const nodes = allDevices.map((node) => node.host);
172
+ // unique nodes
173
+ const uniqueNodes = lodash_1.default.uniq(nodes);
174
+ response.json(uniqueNodes);
175
+ });
176
+ }
177
+ function getQueueStatusById(request, response) {
178
+ return __awaiter(this, void 0, void 0, function* () {
179
+ const status = yield typedi_1.Container.get(queue_service_1.QueueService).getQueueStatus(request.params.capability_id);
180
+ if (!status) {
181
+ return response.status(404).json({ error: 'Pending session not found' });
182
+ }
183
+ response.json(status);
184
+ });
185
+ }
186
+ function getQueueSummary(request, response) {
187
+ return __awaiter(this, void 0, void 0, function* () {
188
+ const summary = yield typedi_1.Container.get(queue_service_1.QueueService).getQueueSummary();
189
+ response.json(summary);
190
+ });
191
+ }
192
+ function nodeAdbStatusOnOtherHost(currentHost, request, response) {
193
+ return __awaiter(this, void 0, void 0, function* () {
194
+ const { host } = request.params;
195
+ // when host is this hub, return status from AndroidDeviceManager directly
196
+ // otherwise, forward request to the node
197
+ logger_1.default.info(`currentHost: ${currentHost}, host: ${host}`);
198
+ if (host === currentHost) {
199
+ const devices = yield getDevicesFromDeviceManager();
200
+ response.json(devices.map((device) => {
201
+ return {
202
+ udid: device.udid,
203
+ host: device.host,
204
+ state: device.state,
205
+ platform: device.platform,
206
+ };
207
+ }));
208
+ }
209
+ else {
210
+ // find node url from database of devices
211
+ const devices = yield store.findDevices({ host: { $contains: host } });
212
+ if (devices.length === 0) {
213
+ response
214
+ .status(404)
215
+ .send(`Host ${host} does not have any devices listed in database. I don't know how to forward request to that host`);
216
+ return;
217
+ }
218
+ const device = devices[0];
219
+ // remove wd/hub from url
220
+ const normalizedUrl = device.host.replace(/\/wd\/hub$/, '');
221
+ const url = `${normalizedUrl}/xenon/api/node/status`;
222
+ const result = yield InternalHttpClient_1.InternalHttpClient.get(url);
223
+ response.json(result);
224
+ }
225
+ });
226
+ }
227
+ function nodeAdbStatusOnThisHost(request, response) {
228
+ return __awaiter(this, void 0, void 0, function* () {
229
+ const devices = yield getDevicesFromDeviceManager();
230
+ // return udid, host, state
231
+ response.json(devices.map((device) => {
232
+ return {
233
+ udid: device.udid,
234
+ host: device.host,
235
+ state: device.state,
236
+ platform: device.platform,
237
+ };
238
+ }));
239
+ });
240
+ }
241
+ /**
242
+ * Returns all devices from all device managers (this host only)
243
+ * @returns IDevice[]
244
+ */
245
+ function getDevicesFromDeviceManager() {
246
+ return __awaiter(this, void 0, void 0, function* () {
247
+ const dfm = typedi_1.Container.get(device_managers_1.XenonManager);
248
+ const instances = yield dfm.deviceInstances();
249
+ // return devices from all device managers
250
+ const devices = [];
251
+ for (const instance of instances) {
252
+ const instanceDevices = yield instance.getDevices({
253
+ androidDeviceType: 'both',
254
+ iosDeviceType: 'both',
255
+ }, []);
256
+ devices.push(...instanceDevices);
257
+ }
258
+ return devices;
259
+ });
260
+ }
261
+ function updateTags(request, response) {
262
+ return __awaiter(this, void 0, void 0, function* () {
263
+ const { udid, host, tags } = request.body;
264
+ if (!udid || !host || !Array.isArray(tags)) {
265
+ return response.status(400).json({ error: 'Missing udid, host, or tags array' });
266
+ }
267
+ yield (0, device_service_1.updateDeviceTags)(udid, host, tags);
268
+ response.status(200).json({ success: true });
269
+ });
270
+ }
271
+ /**
272
+ * Returns active session statistics
273
+ */
274
+ function getActiveSessions(request, response) {
275
+ return __awaiter(this, void 0, void 0, function* () {
276
+ const { SessionManager } = yield Promise.resolve().then(() => __importStar(require('../../sessions/SessionManager')));
277
+ const sessionManager = typedi_1.Container.get(SessionManager);
278
+ const stats = sessionManager.getStats();
279
+ const sessions = sessionManager.getAllSessions().map((s) => {
280
+ var _a, _b, _c;
281
+ return ({
282
+ id: s.getId(),
283
+ type: s.getType(),
284
+ deviceUdid: (_a = s.getDevice()) === null || _a === void 0 ? void 0 : _a.udid,
285
+ deviceName: (_b = s.getDevice()) === null || _b === void 0 ? void 0 : _b.name,
286
+ platform: (_c = s.getDevice()) === null || _c === void 0 ? void 0 : _c.platform,
287
+ });
288
+ });
289
+ response.json({
290
+ stats,
291
+ sessions,
292
+ });
293
+ });
294
+ }
295
+ /**
296
+ * Returns HTTP request logs for debugging
297
+ */
298
+ function getRequestLogs(request, response) {
299
+ return __awaiter(this, void 0, void 0, function* () {
300
+ const { RequestLogService } = yield Promise.resolve().then(() => __importStar(require('../../services/RequestLogService')));
301
+ const logService = typedi_1.Container.get(RequestLogService);
302
+ const limit = parseInt(request.query.limit) || 50;
303
+ const method = request.query.method;
304
+ const urlPattern = request.query.url;
305
+ const hasError = request.query.hasError === 'true'
306
+ ? true
307
+ : request.query.hasError === 'false'
308
+ ? false
309
+ : undefined;
310
+ const logs = logService.getRecentLogs(limit, {
311
+ method,
312
+ urlPattern,
313
+ hasError,
314
+ });
315
+ const stats = logService.getStats();
316
+ response.json({
317
+ stats,
318
+ logs,
319
+ });
320
+ });
321
+ }
322
+ function register(router, pluginArgs) {
323
+ router.get('/devices', getDevices);
324
+ router.get('/device', getDevices);
325
+ router.get('/device/:platform', getDeviceByPlatform);
326
+ router.post('/register', registerNode);
327
+ router.post('/block', blockDevice);
328
+ router.post('/unblock', unBlockDevice);
329
+ router.post('/device/tags', updateTags);
330
+ // session related
331
+ router.get('/queue/length', getQueuedSessionLength);
332
+ router.get('/queue', getQueuedSessionRequests);
333
+ router.get('/queue/status/:capability_id', getQueueStatusById);
334
+ router.get('/queue/summary', getQueueSummary);
335
+ router.get('/sessions/active', getActiveSessions);
336
+ // debugging / observability
337
+ router.get('/logs/requests', getRequestLogs);
338
+ // node related routes
339
+ router.get('/node', getNodes);
340
+ router.get('/node/status', nodeAdbStatusOnThisHost);
341
+ router.get('/node/:host/status', lodash_1.default.curry(nodeAdbStatusOnOtherHost)(pluginArgs.bindHostOrIp));
342
+ // node status
343
+ router.get('/status', (request, response) => {
344
+ response.json({
345
+ status: 'ok',
346
+ version: process.env.npm_package_version || 'unknown (not running from npm package)',
347
+ });
348
+ });
349
+ }
350
+ exports.default = {
351
+ register,
352
+ };