@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,212 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.InternalHttpClient = void 0;
49
+ const axios_1 = __importDefault(require("axios"));
50
+ const http_1 = __importDefault(require("http"));
51
+ const https_1 = __importDefault(require("https"));
52
+ const logger_1 = __importDefault(require("./logger"));
53
+ /**
54
+ * InternalHttpClient - Centralized HTTP client for all internal communication.
55
+ *
56
+ * Features:
57
+ * - Keep-alive connections for performance
58
+ * - Automatic retry with exponential backoff
59
+ * - Request/response logging for debugging
60
+ * - Correlation ID tracking
61
+ */
62
+ class InternalHttpClient {
63
+ constructor(tlsRejectUnauthorized) {
64
+ this.axiosInstance = axios_1.default.create({
65
+ httpAgent: this.getHttpAgent(),
66
+ httpsAgent: this.getHttpsAgent(tlsRejectUnauthorized),
67
+ timeout: 30000,
68
+ maxContentLength: Infinity,
69
+ maxBodyLength: Infinity,
70
+ });
71
+ this.setupInterceptors();
72
+ }
73
+ getHttpAgent() {
74
+ return new http_1.default.Agent({
75
+ keepAlive: true,
76
+ keepAliveMsecs: 120000,
77
+ });
78
+ }
79
+ getHttpsAgent(tlsRejectUnauthorized) {
80
+ // Principal Decoupling: Prioritize constructor arg, fall back to env var
81
+ const rejectUnauthorized = tlsRejectUnauthorized !== undefined
82
+ ? tlsRejectUnauthorized
83
+ : process.env.XENON_TLS_REJECT_UNAUTHORIZED !== 'false';
84
+ return new https_1.default.Agent({
85
+ // Hardened TLS Security: rejectUnauthorized defaults to true (production-safe).
86
+ rejectUnauthorized,
87
+ keepAlive: true,
88
+ keepAliveMsecs: 120000,
89
+ });
90
+ }
91
+ setupInterceptors() {
92
+ // Request interceptor - adds timing and logging
93
+ this.axiosInstance.interceptors.request.use((config) => {
94
+ var _a;
95
+ // Add request start time for duration calculation
96
+ config.metadata = { startTime: Date.now() };
97
+ // Log outgoing request
98
+ logger_1.default.debug(`[HTTP →] ${(_a = config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()} ${config.url}`);
99
+ return config;
100
+ }, (error) => {
101
+ logger_1.default.error(`[HTTP →] Request setup error: ${error.message}`);
102
+ return Promise.reject(error);
103
+ });
104
+ // Response interceptor - logging + retry logic
105
+ this.axiosInstance.interceptors.response.use((response) => __awaiter(this, void 0, void 0, function* () {
106
+ var _a, _b, _c;
107
+ const duration = Date.now() - (((_a = response.config.metadata) === null || _a === void 0 ? void 0 : _a.startTime) || Date.now());
108
+ // Log successful response
109
+ if (!response.config.silent) {
110
+ logger_1.default.debug(`[HTTP ←] ${(_b = response.config.method) === null || _b === void 0 ? void 0 : _b.toUpperCase()} ${response.config.url} ` +
111
+ `[${response.status}] ${duration}ms`);
112
+ }
113
+ // Principal Decoupling: Emit event for logging/observability
114
+ try {
115
+ const { EVENT_BUS } = yield Promise.resolve().then(() => __importStar(require('./services/EventBus')));
116
+ EVENT_BUS.emit('http:outgoing', {
117
+ direction: 'outgoing',
118
+ method: ((_c = response.config.method) === null || _c === void 0 ? void 0 : _c.toUpperCase()) || 'GET',
119
+ url: response.config.url || '',
120
+ requestBody: response.config.data ? JSON.stringify(response.config.data) : undefined,
121
+ responseBody: response.data ? JSON.stringify(response.data).slice(0, 2000) : undefined,
122
+ statusCode: response.status,
123
+ durationMs: duration,
124
+ source: 'InternalHttpClient',
125
+ });
126
+ }
127
+ catch (e) {
128
+ /* ignore */
129
+ }
130
+ return response;
131
+ }), (error) => __awaiter(this, void 0, void 0, function* () {
132
+ var _a, _b, _c;
133
+ const config = error.config;
134
+ const response = error.response;
135
+ const duration = Date.now() - (((_a = config === null || config === void 0 ? void 0 : config.metadata) === null || _a === void 0 ? void 0 : _a.startTime) || Date.now());
136
+ // Principal Decoupling: Emit event for logging/observability
137
+ try {
138
+ const { EVENT_BUS } = yield Promise.resolve().then(() => __importStar(require('./services/EventBus')));
139
+ EVENT_BUS.emit('http:outgoing', {
140
+ direction: 'outgoing',
141
+ method: ((_b = config === null || config === void 0 ? void 0 : config.method) === null || _b === void 0 ? void 0 : _b.toUpperCase()) || 'GET',
142
+ url: (config === null || config === void 0 ? void 0 : config.url) || '',
143
+ requestBody: (config === null || config === void 0 ? void 0 : config.data) ? JSON.stringify(config.data) : undefined,
144
+ responseBody: (response === null || response === void 0 ? void 0 : response.data) ? JSON.stringify(response.data).slice(0, 2000) : undefined,
145
+ statusCode: response === null || response === void 0 ? void 0 : response.status,
146
+ durationMs: duration,
147
+ error: error.message,
148
+ source: 'InternalHttpClient',
149
+ });
150
+ }
151
+ catch (e) {
152
+ /* ignore */
153
+ }
154
+ if (!config || config.retryCount === undefined) {
155
+ config.retryCount = 0;
156
+ }
157
+ const maxRetries = 2;
158
+ const status = response === null || response === void 0 ? void 0 : response.status;
159
+ // Log failed request
160
+ if (!(config === null || config === void 0 ? void 0 : config.silent)) {
161
+ logger_1.default.warn(`[HTTP ←] ${(_c = config === null || config === void 0 ? void 0 : config.method) === null || _c === void 0 ? void 0 : _c.toUpperCase()} ${config === null || config === void 0 ? void 0 : config.url} ` +
162
+ `[${status || 'ERR'}] ${duration}ms - ${error.message}`);
163
+ }
164
+ // Don't retry client errors (4xx) except for occasional 429
165
+ if (status && status < 500 && status !== 429) {
166
+ return Promise.reject(error);
167
+ }
168
+ if (config.retryCount < maxRetries) {
169
+ config.retryCount += 1;
170
+ const backoff = config.retryCount * 1000;
171
+ logger_1.default.warn(`[HTTP ↻] Retrying ${config.url} in ${backoff}ms (Attempt ${config.retryCount}/${maxRetries})...`);
172
+ yield new Promise((resolve) => setTimeout(resolve, backoff));
173
+ return this.axiosInstance(config);
174
+ }
175
+ return Promise.reject(error);
176
+ }));
177
+ }
178
+ static getClient(tlsRejectUnauthorized) {
179
+ if (tlsRejectUnauthorized !== undefined) {
180
+ return new InternalHttpClient(tlsRejectUnauthorized).axiosInstance;
181
+ }
182
+ if (!this.defaultInstance) {
183
+ this.defaultInstance = new InternalHttpClient();
184
+ }
185
+ return this.defaultInstance.axiosInstance;
186
+ }
187
+ static post(url, data, config) {
188
+ return __awaiter(this, void 0, void 0, function* () {
189
+ const response = yield this.getClient().post(url, data, config);
190
+ return response.data;
191
+ });
192
+ }
193
+ static get(url, config) {
194
+ return __awaiter(this, void 0, void 0, function* () {
195
+ const response = yield this.getClient().get(url, config);
196
+ return response.data;
197
+ });
198
+ }
199
+ static delete(url, config) {
200
+ return __awaiter(this, void 0, void 0, function* () {
201
+ const response = yield this.getClient().delete(url, config);
202
+ return response.data;
203
+ });
204
+ }
205
+ static put(url, data, config) {
206
+ return __awaiter(this, void 0, void 0, function* () {
207
+ const response = yield this.getClient().put(url, data, config);
208
+ return response.data;
209
+ });
210
+ }
211
+ }
212
+ exports.InternalHttpClient = InternalHttpClient;
@@ -0,0 +1,29 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.PluginContext = void 0;
10
+ const typedi_1 = require("typedi");
11
+ const IPluginArgs_1 = require("./interfaces/IPluginArgs");
12
+ let PluginContext = class PluginContext {
13
+ constructor() {
14
+ this.pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs);
15
+ this.port = 4723;
16
+ this.nodeId = '';
17
+ this.nodeBasePath = '';
18
+ }
19
+ setContext(args, port, nodeId, nodeBasePath) {
20
+ this.pluginArgs = args;
21
+ this.port = port;
22
+ this.nodeId = nodeId;
23
+ this.nodeBasePath = nodeBasePath;
24
+ }
25
+ };
26
+ exports.PluginContext = PluginContext;
27
+ exports.PluginContext = PluginContext = __decorate([
28
+ (0, typedi_1.Service)()
29
+ ], PluginContext);
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.XENON_CAPABILITIES = void 0;
49
+ exports.androidCapabilities = androidCapabilities;
50
+ exports.iOSCapabilities = iOSCapabilities;
51
+ exports.getXenonCapabilities = getXenonCapabilities;
52
+ const get_port_1 = __importDefault(require("get-port"));
53
+ const lodash_1 = __importDefault(require("lodash"));
54
+ const typedi_1 = require("typedi");
55
+ const logger_1 = __importDefault(require("./logger"));
56
+ var XENON_CAPABILITIES;
57
+ (function (XENON_CAPABILITIES) {
58
+ XENON_CAPABILITIES["BUILD_NAME"] = "build";
59
+ XENON_CAPABILITIES["SESSION_NAME"] = "name";
60
+ XENON_CAPABILITIES["VIDEO_RECORDING"] = "record_video";
61
+ XENON_CAPABILITIES["RECORD_VIDEO"] = "recordVideo";
62
+ XENON_CAPABILITIES["VIDEO_RESOLUTION"] = "video_resolution";
63
+ XENON_CAPABILITIES["LIVE_VIDEO"] = "live_video";
64
+ XENON_CAPABILITIES["SCREENSHOT_ON_FAILURE"] = "screenshot_on_failure";
65
+ XENON_CAPABILITIES["SCREENSHOT_ON_FAIL"] = "screenshotOnFailure";
66
+ XENON_CAPABILITIES["XENON_OPTIONS"] = "xenon:options";
67
+ XENON_CAPABILITIES["SAVE_DEVICE_LOGS"] = "saveDeviceLogs";
68
+ XENON_CAPABILITIES["SAVE_LOGS"] = "save_device_logs";
69
+ XENON_CAPABILITIES["SCREENSHOT_ON_EVERY_COMMAND"] = "screenshot_on_every_command";
70
+ XENON_CAPABILITIES["SCREENSHOT_EVERY_COMMAND"] = "screenshotOnEveryCommand";
71
+ XENON_CAPABILITIES["NETWORK_PROFILE"] = "network_profile";
72
+ XENON_CAPABILITIES["ISOLATION_PROFILE"] = "isolation_profile";
73
+ })(XENON_CAPABILITIES || (exports.XENON_CAPABILITIES = XENON_CAPABILITIES = {}));
74
+ function isCapabilityAlreadyPresent(caps, capabilityName) {
75
+ return lodash_1.default.has(caps.alwaysMatch, capabilityName) || lodash_1.default.has(caps.firstMatch[0], capabilityName);
76
+ }
77
+ function deleteAlwaysMatch(caps, capabilityName) {
78
+ if (lodash_1.default.has(caps.alwaysMatch, capabilityName))
79
+ delete caps.alwaysMatch[capabilityName];
80
+ }
81
+ function androidCapabilities(caps, freeDevice) {
82
+ return __awaiter(this, void 0, void 0, function* () {
83
+ caps.firstMatch[0]['appium:udid'] = freeDevice.udid;
84
+ caps.firstMatch[0]['platformName'] = freeDevice.platform;
85
+ caps.firstMatch[0]['appium:systemPort'] = yield (0, get_port_1.default)();
86
+ caps.firstMatch[0]['appium:chromeDriverPort'] = yield (0, get_port_1.default)();
87
+ caps.firstMatch[0]['appium:adbRemoteHost'] = freeDevice.adbRemoteHost;
88
+ caps.firstMatch[0]['appium:adbPort'] = freeDevice.adbPort;
89
+ if (freeDevice.chromeDriverPath)
90
+ caps.firstMatch[0]['appium:chromedriverExecutable'] = freeDevice.chromeDriverPath;
91
+ if (!isCapabilityAlreadyPresent(caps, 'appium:mjpegServerPort')) {
92
+ caps.firstMatch[0]['appium:mjpegServerPort'] = yield (0, get_port_1.default)();
93
+ }
94
+ deleteAlwaysMatch(caps, 'platformName');
95
+ deleteAlwaysMatch(caps, 'appium:udid');
96
+ deleteAlwaysMatch(caps, 'appium:systemPort');
97
+ deleteAlwaysMatch(caps, 'appium:chromeDriverPort');
98
+ deleteAlwaysMatch(caps, 'appium:adbRemoteHost');
99
+ deleteAlwaysMatch(caps, 'appium:adbPort');
100
+ });
101
+ }
102
+ function iOSCapabilities(caps, freeDevice) {
103
+ return __awaiter(this, void 0, void 0, function* () {
104
+ caps.firstMatch[0]['appium:udid'] = freeDevice.udid;
105
+ caps.firstMatch[0]['platformName'] = freeDevice.platform;
106
+ caps.firstMatch[0]['appium:deviceName'] = freeDevice.name;
107
+ caps.firstMatch[0]['appium:platformVersion'] = freeDevice.sdk;
108
+ caps.firstMatch[0]['appium:wdaLocalPort'] = freeDevice.wdaLocalPort;
109
+ caps.firstMatch[0]['appium:mjpegServerPort'] = freeDevice.mjpegServerPort;
110
+ caps.firstMatch[0]['appium:derivedDataPath'] = freeDevice.derivedDataPath;
111
+ // Technical Optimization: Reuse existing WDA tunnel if Stream Service is active
112
+ // This prevents "Port Occupied" errors when the dashboard is open and speeds up startup by 15-30s
113
+ try {
114
+ const { default: IOSStreamService } = yield Promise.resolve().then(() => __importStar(require('./device-managers/ios/IOSStreamService')));
115
+ const streamService = typedi_1.Container.get(IOSStreamService);
116
+ const streamStatus = streamService.getStreamStatus(freeDevice.udid);
117
+ logger_1.default.info(`[Xenon] 🔍 Checking Stream Status for ${freeDevice.udid}: ${(streamStatus === null || streamStatus === void 0 ? void 0 : streamStatus.status) || 'None'}`);
118
+ if (streamStatus && (streamStatus.status === 'running' || streamStatus.status === 'starting')) {
119
+ const wdaUrl = `http://127.0.0.1:${streamStatus.wdaPort}`;
120
+ caps.firstMatch[0]['appium:webDriverAgentUrl'] = wdaUrl;
121
+ // If we are reusing the WDA, we MUST NOT pass wdaLocalPort or mjpegServerPort
122
+ // as XCUITestDriver will still try to verify they are free and fail if busy.
123
+ delete caps.firstMatch[0]['appium:wdaLocalPort'];
124
+ delete caps.firstMatch[0]['appium:mjpegServerPort'];
125
+ if (caps.alwaysMatch) {
126
+ delete caps.alwaysMatch['appium:wdaLocalPort'];
127
+ delete caps.alwaysMatch['appium:mjpegServerPort'];
128
+ }
129
+ logger_1.default.info(`[Xenon] 🚀 Optimization: Reusing active WDA tunnel at ${wdaUrl} for ${freeDevice.udid}. Port check bypassed.`);
130
+ }
131
+ }
132
+ catch (e) {
133
+ logger_1.default.warn(`[Xenon] ⚠️ Failed to check Stream Service: ${e.message}`);
134
+ }
135
+ // Senior Resiliency: Inject higher defaults for WebDriverAgent in enterprise environments
136
+ if (!isCapabilityAlreadyPresent(caps, 'appium:wdaLaunchTimeout')) {
137
+ // 180s is safer for physical devices that might need WDA signing/installation
138
+ caps.firstMatch[0]['appium:wdaLaunchTimeout'] = 180000;
139
+ }
140
+ if (!isCapabilityAlreadyPresent(caps, 'appium:wdaConnectionTimeout')) {
141
+ // 120s is safer for remote devices
142
+ caps.firstMatch[0]['appium:wdaConnectionTimeout'] = 120000;
143
+ }
144
+ const deleteMatch = [
145
+ 'appium:wdaLocalPort',
146
+ 'appium:mjpegServerPort',
147
+ 'appium:udid',
148
+ 'appium:deviceName',
149
+ 'platformName',
150
+ ];
151
+ deleteMatch.forEach((value) => deleteAlwaysMatch(caps, value));
152
+ });
153
+ }
154
+ function getXenonCapabilities(caps) {
155
+ const mergedCapabilites = Object.assign({}, caps.firstMatch[0], caps.alwaysMatch);
156
+ const getAnyCap = (snake, camel) => {
157
+ // Principal Intelligence: Strict Prefix Resolution
158
+ // Supports only xe:, appium: and no-prefix, with snake_case and camelCase fallbacks.
159
+ const prefixes = ['xe:', 'appium:', ''];
160
+ const names = [snake, camel];
161
+ for (const prefix of prefixes) {
162
+ for (const name of names) {
163
+ const key = prefix ? `${prefix}${name}` : name;
164
+ if (mergedCapabilites[key] !== undefined)
165
+ return mergedCapabilites[key];
166
+ }
167
+ }
168
+ return undefined;
169
+ };
170
+ const capabilities = {};
171
+ // Normalize all essential capabilities to standard snake_case keys used by the plugin
172
+ // 1. Video Recording
173
+ const videoCap = getAnyCap(XENON_CAPABILITIES.VIDEO_RECORDING, XENON_CAPABILITIES.RECORD_VIDEO);
174
+ capabilities[XENON_CAPABILITIES.VIDEO_RECORDING] =
175
+ videoCap !== undefined ? String(videoCap) === 'true' : true;
176
+ // 2. Screenshot on Failure
177
+ const screenCapOnFail = getAnyCap(XENON_CAPABILITIES.SCREENSHOT_ON_FAILURE, XENON_CAPABILITIES.SCREENSHOT_ON_FAIL);
178
+ capabilities[XENON_CAPABILITIES.SCREENSHOT_ON_FAILURE] =
179
+ screenCapOnFail !== undefined ? String(screenCapOnFail) === 'true' : true;
180
+ // 3. Screenshot on Every Command
181
+ const screenCapEvery = getAnyCap(XENON_CAPABILITIES.SCREENSHOT_ON_EVERY_COMMAND, XENON_CAPABILITIES.SCREENSHOT_EVERY_COMMAND);
182
+ capabilities[XENON_CAPABILITIES.SCREENSHOT_ON_EVERY_COMMAND] = String(screenCapEvery) === 'true';
183
+ // 4. Save Logs
184
+ const logsCap = getAnyCap(XENON_CAPABILITIES.SAVE_LOGS, XENON_CAPABILITIES.SAVE_DEVICE_LOGS);
185
+ capabilities[XENON_CAPABILITIES.SAVE_DEVICE_LOGS] = String(logsCap) === 'true';
186
+ // 5. Build and Session Names
187
+ capabilities[XENON_CAPABILITIES.BUILD_NAME] = getAnyCap(XENON_CAPABILITIES.BUILD_NAME, 'buildName');
188
+ capabilities[XENON_CAPABILITIES.SESSION_NAME] = getAnyCap(XENON_CAPABILITIES.SESSION_NAME, 'sessionName');
189
+ // 6. Network Profile
190
+ capabilities[XENON_CAPABILITIES.NETWORK_PROFILE] = getAnyCap(XENON_CAPABILITIES.NETWORK_PROFILE, 'networkProfile');
191
+ // 7. Isolation Profile
192
+ capabilities[XENON_CAPABILITIES.ISOLATION_PROFILE] = getAnyCap(XENON_CAPABILITIES.ISOLATION_PROFILE, 'isolationProfile');
193
+ logger_1.default.debug('[CapabilityManager] Resolved Capabilities: ' +
194
+ `Video=${capabilities[XENON_CAPABILITIES.VIDEO_RECORDING]}, ` +
195
+ `EveryScreenshot=${capabilities[XENON_CAPABILITIES.SCREENSHOT_ON_EVERY_COMMAND]}, ` +
196
+ `FailScreenshot=${capabilities[XENON_CAPABILITIES.SCREENSHOT_ON_FAILURE]}, ` +
197
+ `SaveLogs=${capabilities[XENON_CAPABILITIES.SAVE_DEVICE_LOGS]}`);
198
+ return capabilities;
199
+ }
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.createRouter = createRouter;
49
+ const express_1 = __importDefault(require("express"));
50
+ const path_1 = __importDefault(require("path"));
51
+ const fs_1 = __importDefault(require("fs"));
52
+ const pluginArgs_1 = require("../data-service/pluginArgs");
53
+ const cors_1 = __importDefault(require("cors"));
54
+ const async_lock_1 = __importDefault(require("async-lock"));
55
+ const InternalHttpClient_1 = require("../InternalHttpClient");
56
+ const config_1 = require("../config");
57
+ const logger_1 = __importStar(require("../logger"));
58
+ const dashboard_1 = __importDefault(require("./routers/dashboard"));
59
+ const grid_1 = __importDefault(require("./routers/grid"));
60
+ const control_1 = __importDefault(require("./routers/control"));
61
+ const apps_1 = __importDefault(require("./routers/apps"));
62
+ const webhook_1 = __importDefault(require("./routers/webhook"));
63
+ const reservation_1 = __importDefault(require("./routers/reservation"));
64
+ const config_2 = __importDefault(require("./routers/config"));
65
+ const swagger_1 = require("./swagger");
66
+ const typedi_1 = require("typedi");
67
+ const dashboardPluginUrl = null;
68
+ const ASYNC_LOCK = new async_lock_1.default();
69
+ const router = express_1.default.Router(), apiRouter = express_1.default.Router(), staticFilesRouter = express_1.default.Router();
70
+ router.use((0, cors_1.default)());
71
+ apiRouter.use((0, cors_1.default)());
72
+ staticFilesRouter.use((0, cors_1.default)());
73
+ apiRouter.use((req, res, next) => {
74
+ // Defensive Body Parsing Logic:
75
+ // In some Appium versions, the global HTTP logger or other middleware drains the request stream
76
+ // before it reaches the plugin. Re-calling express.json() on a drained stream throws
77
+ // 'InternalServerError: stream is not readable'.
78
+ // 1. If body is already an object (parsed by parent), proceed.
79
+ if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
80
+ return next();
81
+ }
82
+ // 2. If stream is already spent and nothing was parsed, we can't do much, just proceed gracefully.
83
+ if (!req.readable) {
84
+ logger_1.default.debug(`[Xenon] Stream drained for ${req.method} ${req.originalUrl}. Skipping local body-parser.`);
85
+ return next();
86
+ }
87
+ // 3. Otherwise, try safe parsing.
88
+ express_1.default.json()(req, res, (err) => {
89
+ if (err) {
90
+ logger_1.default.warn(`[Xenon] Body parsing failed for ${req.originalUrl}: ${err.message}`);
91
+ // Continue without failing - handlers will validate missing body
92
+ return next();
93
+ }
94
+ next();
95
+ });
96
+ });
97
+ apiRouter.use((req, res, next) => {
98
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
99
+ res.setHeader('Pragma', 'no-cache');
100
+ res.setHeader('Expires', '0');
101
+ // Redact secrets from request body to prevent them from leaking into external logs
102
+ if (req.body && typeof req.body === 'object') {
103
+ req.body = (0, logger_1.redactSecrets)(req.body);
104
+ }
105
+ next();
106
+ });
107
+ // Dashboard state cache - runs once and persists
108
+ let dashboardPluginPromise = null;
109
+ apiRouter.use((req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
110
+ if (dashboardPluginPromise === null) {
111
+ dashboardPluginPromise = (() => __awaiter(void 0, void 0, void 0, function* () {
112
+ const pingurl = `${req.protocol}://${req.get('host')}/dashboard/api/ping`;
113
+ try {
114
+ const response = yield InternalHttpClient_1.InternalHttpClient.get(pingurl, { silent: true });
115
+ return response['pong'] ? `${req.protocol}://${req.get('host')}/dashboard` : '';
116
+ }
117
+ catch (err) {
118
+ return '';
119
+ }
120
+ }))();
121
+ }
122
+ req['dashboard-plugin-url'] = yield dashboardPluginPromise;
123
+ return next();
124
+ }));
125
+ apiRouter.get('/cliArgs', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
126
+ res.json(yield (0, pluginArgs_1.getCLIArgs)());
127
+ }));
128
+ apiRouter.get('/metrics', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
129
+ const { MetricsService } = yield Promise.resolve().then(() => __importStar(require('../services/MetricsService')));
130
+ const metrics = yield typedi_1.Container.get(MetricsService).getMetrics();
131
+ res.set('Content-Type', 'text/plain');
132
+ res.send(metrics);
133
+ }));
134
+ const publicPath = [path_1.default.join(__dirname, '..', 'public'), path_1.default.join(__dirname, '..', '..', 'public')].find((p) => fs_1.default.existsSync(p)) || path_1.default.join(__dirname, '..', '..', 'public');
135
+ staticFilesRouter.use(express_1.default.static(publicPath));
136
+ router.use('/api', apiRouter);
137
+ router.use('/assets', express_1.default.static(config_1.config.sessionAssetsPath));
138
+ router.use(staticFilesRouter);
139
+ function createRouter(pluginArgs) {
140
+ dashboard_1.default.register(apiRouter);
141
+ grid_1.default.register(apiRouter, pluginArgs);
142
+ control_1.default.register(apiRouter);
143
+ apps_1.default.register(apiRouter);
144
+ webhook_1.default.register(apiRouter);
145
+ config_2.default.register(apiRouter, pluginArgs);
146
+ apiRouter.use('/reservation', reservation_1.default);
147
+ // Setup Swagger API documentation at /xenon/api-docs
148
+ try {
149
+ (0, swagger_1.setupSwagger)(router, '/xenon');
150
+ }
151
+ catch (err) {
152
+ logger_1.default.warn('Swagger documentation not available. Install swagger-jsdoc and swagger-ui-express to enable.');
153
+ }
154
+ // Handle unmatched API routes with a 404 JSON response instead of the UI fallback
155
+ apiRouter.use('*', (req, res) => {
156
+ res.status(404).json({
157
+ error: true,
158
+ message: `API endpoint ${req.method} ${req.originalUrl} not found`,
159
+ });
160
+ });
161
+ // Fallback route for client-side routing - serve index.html for all non-API routes
162
+ // MUST be registered after Swagger to avoid interception
163
+ router.get(/^(?!\/api).*/, (req, res) => {
164
+ res.sendFile(path_1.default.join(publicPath, 'index.html'));
165
+ });
166
+ return router;
167
+ }