@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,173 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
42
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
43
+ return new (P || (P = Promise))(function (resolve, reject) {
44
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
45
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
46
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
47
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
48
+ });
49
+ };
50
+ var __importDefault = (this && this.__importDefault) || function (mod) {
51
+ return (mod && mod.__esModule) ? mod : { "default": mod };
52
+ };
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.NetworkConditioningService = void 0;
55
+ const typedi_1 = require("typedi");
56
+ const logger_1 = __importDefault(require("../logger"));
57
+ const NETWORK_PROFILES = {
58
+ Normal: null,
59
+ '4G': { latencyMs: 20, downloadKbps: 15000, uploadKbps: 7500 },
60
+ '3G': { latencyMs: 100, downloadKbps: 2000, uploadKbps: 1000 },
61
+ Edge: { latencyMs: 400, downloadKbps: 250, uploadKbps: 150 },
62
+ Offline: { latencyMs: 0, downloadKbps: 0, uploadKbps: 0 },
63
+ };
64
+ /**
65
+ * Service to manage network conditioning (Throttling/Latency)
66
+ */
67
+ let NetworkConditioningService = class NetworkConditioningService {
68
+ constructor() {
69
+ this.log = logger_1.default.scope('NetworkConditioning');
70
+ this.activeConditions = new Map();
71
+ }
72
+ /**
73
+ * Applies a network profile to a session/device
74
+ */
75
+ applyProfile(sessionId, device, profile) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ this.log.info(`Applying network profile '${profile}' to session ${sessionId} on device ${device.udid}`);
78
+ this.activeConditions.set(sessionId, profile);
79
+ // Platform specific optimizations
80
+ if (device.platform === 'android') {
81
+ yield this.applyAndroidConditioning(device.udid, profile);
82
+ }
83
+ else if (device.deviceType === 'simulator') {
84
+ yield this.applyIOSSimulatorConditioning(device.udid, profile);
85
+ }
86
+ });
87
+ }
88
+ /**
89
+ * Gets the active profile for a session
90
+ */
91
+ getProfile(sessionId) {
92
+ return this.activeConditions.get(sessionId);
93
+ }
94
+ /**
95
+ * Resets network conditions for a session
96
+ */
97
+ reset(sessionId, device) {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ if (this.activeConditions.has(sessionId)) {
100
+ this.log.info(`Resetting network conditions for session ${sessionId}`);
101
+ this.activeConditions.delete(sessionId);
102
+ if (device.platform === 'android') {
103
+ yield this.applyAndroidConditioning(device.udid, 'Normal');
104
+ }
105
+ else if (device.deviceType === 'simulator') {
106
+ yield this.applyIOSSimulatorConditioning(device.udid, 'Normal');
107
+ }
108
+ }
109
+ });
110
+ }
111
+ applyAndroidConditioning(udid, profile) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ try {
114
+ const { exec } = yield Promise.resolve().then(() => __importStar(require('child_process')));
115
+ const { promisify } = yield Promise.resolve().then(() => __importStar(require('util')));
116
+ const execAsync = promisify(exec);
117
+ if (profile === 'Offline') {
118
+ this.log.info(`📱 Android: Disabling data for ${udid}`);
119
+ yield execAsync(`adb -s ${udid} shell svc data disable`);
120
+ yield execAsync(`adb -s ${udid} shell svc wifi disable`);
121
+ }
122
+ else if (profile === 'Normal') {
123
+ this.log.info(`📱 Android: Enabling data for ${udid}`);
124
+ yield execAsync(`adb -s ${udid} shell svc data enable`);
125
+ yield execAsync(`adb -s ${udid} shell svc wifi enable`);
126
+ }
127
+ // Latency is handled by the proxy
128
+ }
129
+ catch (e) {
130
+ this.log.warn(`⚠️ Failed to apply Android conditioning for ${udid}: ${e.message}`);
131
+ }
132
+ });
133
+ }
134
+ applyIOSSimulatorConditioning(udid, profile) {
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ try {
137
+ const { exec } = yield Promise.resolve().then(() => __importStar(require('child_process')));
138
+ const { promisify } = yield Promise.resolve().then(() => __importStar(require('util')));
139
+ const execAsync = promisify(exec);
140
+ // simctl network <device> <status>
141
+ // Profiles: wifi, 3g, edge, etc. (depends on OS version)
142
+ let status = 'none';
143
+ if (profile === '3G')
144
+ status = '3g';
145
+ if (profile === 'Edge')
146
+ status = 'edge';
147
+ if (profile === 'Offline')
148
+ status = 'off';
149
+ if (profile === 'Normal')
150
+ status = 'none';
151
+ this.log.info(`🍎 iOS Sim: Setting network to ${status} for ${udid}`);
152
+ yield execAsync(`xcrun simctl network ${udid} status ${status}`);
153
+ }
154
+ catch (e) {
155
+ this.log.warn(`⚠️ Failed to apply iOS Simulator conditioning for ${udid}: ${e.message}`);
156
+ }
157
+ });
158
+ }
159
+ /**
160
+ * Returns the latency to inject into a command proxy for the given profile
161
+ */
162
+ getLatency(sessionId) {
163
+ var _a;
164
+ const profile = this.activeConditions.get(sessionId);
165
+ if (!profile)
166
+ return 0;
167
+ return ((_a = NETWORK_PROFILES[profile]) === null || _a === void 0 ? void 0 : _a.latencyMs) || 0;
168
+ }
169
+ };
170
+ exports.NetworkConditioningService = NetworkConditioningService;
171
+ exports.NetworkConditioningService = NetworkConditioningService = __decorate([
172
+ (0, typedi_1.Service)()
173
+ ], NetworkConditioningService);
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
12
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
13
+ return new (P || (P = Promise))(function (resolve, reject) {
14
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
15
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
16
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
17
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
18
+ });
19
+ };
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.NotificationService = void 0;
25
+ const axios_1 = __importDefault(require("axios"));
26
+ const logger_1 = __importDefault(require("../logger"));
27
+ const prisma_service_1 = require("../data-service/prisma-service");
28
+ const typedi_1 = require("typedi");
29
+ let NotificationService = class NotificationService {
30
+ constructor(prisma) {
31
+ this.prisma = prisma;
32
+ }
33
+ getConfigs() {
34
+ return __awaiter(this, void 0, void 0, function* () {
35
+ return this.prisma.webhookConfig.findMany();
36
+ });
37
+ }
38
+ saveConfig(url_1, events_1) {
39
+ return __awaiter(this, arguments, void 0, function* (url, events, type = 'slack', payloadTemplate) {
40
+ return this.prisma.webhookConfig.create({
41
+ data: {
42
+ url,
43
+ events: JSON.stringify(events),
44
+ type,
45
+ payloadTemplate,
46
+ active: true,
47
+ },
48
+ });
49
+ });
50
+ }
51
+ deleteConfig(id) {
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ yield this.prisma.webhookConfig.delete({ where: { id } });
54
+ });
55
+ }
56
+ dispatchEvent(eventType, payload) {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ const configs = yield this.getConfigs();
59
+ for (const config of configs) {
60
+ if (!config.active)
61
+ continue;
62
+ try {
63
+ const events = JSON.parse(config.events);
64
+ if (events.includes(eventType)) {
65
+ yield this.sendToWebhook(config, eventType, payload);
66
+ }
67
+ }
68
+ catch (err) {
69
+ logger_1.default.error(`Failed to process webhook config ${config.id}: ${err}`);
70
+ }
71
+ }
72
+ });
73
+ }
74
+ sendToWebhook(config, eventType, payload) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ // Principal Logic: Use custom template if defined
77
+ if (config.payloadTemplate) {
78
+ const substitutedBody = this.substituteTemplate(config.payloadTemplate, Object.assign({ eventType }, payload));
79
+ try {
80
+ // Try to parse as JSON first
81
+ const jsonBody = JSON.parse(substitutedBody);
82
+ yield axios_1.default.post(config.url, jsonBody);
83
+ }
84
+ catch (e) {
85
+ // Fallback to sending as plain text or simple object
86
+ yield axios_1.default.post(config.url, { text: substitutedBody });
87
+ }
88
+ return;
89
+ }
90
+ if (config.type === 'slack') {
91
+ yield this.sendSlackMessage(config.url, eventType, payload);
92
+ }
93
+ else {
94
+ // Generic webhook fallback
95
+ try {
96
+ yield axios_1.default.post(config.url, { event: eventType, payload });
97
+ }
98
+ catch (err) {
99
+ logger_1.default.error(`Webhook failed for ${config.url}: ${err}`);
100
+ }
101
+ }
102
+ });
103
+ }
104
+ // Principal Logic: Recursive variable substitution with {{key}} support
105
+ substituteTemplate(template, data) {
106
+ return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
107
+ const keys = key.trim().split('.');
108
+ let value = data;
109
+ for (const k of keys) {
110
+ value = value ? value[k] : undefined;
111
+ }
112
+ return value !== undefined ? String(value) : match;
113
+ });
114
+ }
115
+ sendSlackMessage(url, eventType, payload) {
116
+ return __awaiter(this, void 0, void 0, function* () {
117
+ let text = '';
118
+ let color = '#36a64f'; // green
119
+ switch (eventType) {
120
+ case 'device_offline':
121
+ text = `🚨 *Device Offline*: ${payload.udid} (${payload.host})`;
122
+ color = '#ff0000';
123
+ break;
124
+ case 'session_failed':
125
+ text = `❌ *Session Failed*: ${payload.sessionId}\nReason: ${payload.failureReason}`;
126
+ color = '#ff0000';
127
+ break;
128
+ case 'device_new':
129
+ text = `📱 *New Device Connected*: ${payload.name} (${payload.udid})`;
130
+ break;
131
+ default:
132
+ text = `Event: ${eventType}\nPayload: ${JSON.stringify(payload)}`;
133
+ }
134
+ const body = {
135
+ attachments: [
136
+ {
137
+ color,
138
+ text,
139
+ fields: Object.keys(payload).map((k) => ({
140
+ title: k,
141
+ value: typeof payload[k] === 'object' ? JSON.stringify(payload[k]) : String(payload[k]),
142
+ short: true,
143
+ })),
144
+ footer: 'Xenon Device Farm',
145
+ ts: Math.floor(Date.now() / 1000),
146
+ },
147
+ ],
148
+ };
149
+ try {
150
+ yield axios_1.default.post(url, body);
151
+ logger_1.default.info(`Slack notification sent to ${url}`);
152
+ }
153
+ catch (err) {
154
+ logger_1.default.error(`Failed to send Slack notification: ${err}`);
155
+ }
156
+ });
157
+ }
158
+ };
159
+ exports.NotificationService = NotificationService;
160
+ exports.NotificationService = NotificationService = __decorate([
161
+ (0, typedi_1.Service)(),
162
+ __metadata("design:paramtypes", [prisma_service_1.PrismaService])
163
+ ], NotificationService);
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
45
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
46
+ return new (P || (P = Promise))(function (resolve, reject) {
47
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
48
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
49
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
50
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
51
+ });
52
+ };
53
+ var __importDefault = (this && this.__importDefault) || function (mod) {
54
+ return (mod && mod.__esModule) ? mod : { "default": mod };
55
+ };
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.RequestLogService = void 0;
58
+ const typedi_1 = require("typedi");
59
+ const logger_1 = __importDefault(require("../logger"));
60
+ /**
61
+ * Sanitizes sensitive data from request payloads before logging.
62
+ * Redacts passwords, tokens, API keys, and other secrets.
63
+ */
64
+ function sanitizePayload(data, depth = 0) {
65
+ if (depth > 5)
66
+ return '[MAX_DEPTH]';
67
+ if (data === null || data === undefined)
68
+ return data;
69
+ if (typeof data === 'string') {
70
+ // Truncate very long strings (e.g., base64 images)
71
+ if (data.length > 500) {
72
+ return `[STRING_TRUNCATED: ${data.length} chars]`;
73
+ }
74
+ return data;
75
+ }
76
+ if (Array.isArray(data)) {
77
+ if (data.length > 10) {
78
+ return `[ARRAY_TRUNCATED: ${data.length} items]`;
79
+ }
80
+ return data.map((item) => sanitizePayload(item, depth + 1));
81
+ }
82
+ if (typeof data === 'object') {
83
+ const sanitized = {};
84
+ const sensitiveKeys = [
85
+ 'password',
86
+ 'token',
87
+ 'apikey',
88
+ 'api_key',
89
+ 'secret',
90
+ 'authorization',
91
+ 'auth',
92
+ 'credential',
93
+ 'key',
94
+ 'private',
95
+ 'accesstoken',
96
+ 'access_token',
97
+ 'refreshtoken',
98
+ 'refresh_token',
99
+ 'bearer',
100
+ 'jwt',
101
+ ];
102
+ for (const [key, value] of Object.entries(data)) {
103
+ const lowerKey = key.toLowerCase();
104
+ if (sensitiveKeys.some((s) => lowerKey.includes(s))) {
105
+ sanitized[key] = '[REDACTED]';
106
+ }
107
+ else {
108
+ sanitized[key] = sanitizePayload(value, depth + 1);
109
+ }
110
+ }
111
+ return sanitized;
112
+ }
113
+ return data;
114
+ }
115
+ /**
116
+ * RequestLogService provides centralized logging for HTTP requests.
117
+ *
118
+ * Features:
119
+ * - In-memory ring buffer for recent requests (fast access)
120
+ * - Database persistence for audit trail (optional)
121
+ * - Automatic payload sanitization (no secrets in logs)
122
+ * - Correlation ID tracking for distributed tracing
123
+ */
124
+ let RequestLogService = class RequestLogService {
125
+ constructor() {
126
+ this.ringBuffer = [];
127
+ this.bufferSize = 500; // Keep last 500 requests in memory
128
+ this.persistToDb = false; // Enable for full audit trail
129
+ logger_1.default.info('[RequestLogService] Initialized with buffer size', this.bufferSize);
130
+ // Principal Decoupling: Subscribe to global events
131
+ Promise.resolve().then(() => __importStar(require('./EventBus'))).then(({ EVENT_BUS }) => {
132
+ EVENT_BUS.on('http:outgoing', (data) => this.logRequest(data));
133
+ });
134
+ }
135
+ /**
136
+ * Enable/disable database persistence
137
+ */
138
+ setPersistence(enabled) {
139
+ this.persistToDb = enabled;
140
+ logger_1.default.info(`[RequestLogService] Database persistence: ${enabled ? 'enabled' : 'disabled'}`);
141
+ }
142
+ /**
143
+ * Log an outgoing HTTP request
144
+ */
145
+ logRequest(entry) {
146
+ return __awaiter(this, void 0, void 0, function* () {
147
+ const fullEntry = Object.assign(Object.assign({}, entry), { timestamp: new Date(), requestBody: entry.requestBody
148
+ ? JSON.stringify(sanitizePayload(JSON.parse(entry.requestBody || '{}')))
149
+ : undefined, responseBody: entry.responseBody
150
+ ? JSON.stringify(sanitizePayload(JSON.parse(entry.responseBody || '{}')))
151
+ : undefined });
152
+ // Add to ring buffer
153
+ this.ringBuffer.push(fullEntry);
154
+ if (this.ringBuffer.length > this.bufferSize) {
155
+ this.ringBuffer.shift();
156
+ }
157
+ // Log to console with appropriate level
158
+ if (entry.error) {
159
+ logger_1.default.error(`[HTTP ${entry.direction.toUpperCase()}] ${entry.method} ${entry.url} ` +
160
+ `[${entry.statusCode || 'ERR'}] ${entry.durationMs}ms - ${entry.error}`);
161
+ }
162
+ else if ((entry.statusCode || 0) >= 400) {
163
+ logger_1.default.warn(`[HTTP ${entry.direction.toUpperCase()}] ${entry.method} ${entry.url} ` +
164
+ `[${entry.statusCode}] ${entry.durationMs}ms`);
165
+ }
166
+ else {
167
+ logger_1.default.debug(`[HTTP ${entry.direction.toUpperCase()}] ${entry.method} ${entry.url} ` +
168
+ `[${entry.statusCode}] ${entry.durationMs}ms`);
169
+ }
170
+ // Persist to database if enabled
171
+ if (this.persistToDb) {
172
+ yield this.persistEntry(fullEntry);
173
+ }
174
+ });
175
+ }
176
+ /**
177
+ * Get recent request logs from the ring buffer
178
+ */
179
+ getRecentLogs(limit = 50, filter) {
180
+ let logs = [...this.ringBuffer].reverse();
181
+ if (filter) {
182
+ if (filter.method) {
183
+ logs = logs.filter((l) => l.method === filter.method);
184
+ }
185
+ if (filter.urlPattern) {
186
+ const pattern = filter.urlPattern;
187
+ logs = logs.filter((l) => l.url.includes(pattern));
188
+ }
189
+ if (filter.direction) {
190
+ logs = logs.filter((l) => l.direction === filter.direction);
191
+ }
192
+ if (filter.hasError !== undefined) {
193
+ logs = logs.filter((l) => (filter.hasError ? !!l.error : !l.error));
194
+ }
195
+ }
196
+ return logs.slice(0, limit);
197
+ }
198
+ /**
199
+ * Get summary statistics
200
+ */
201
+ getStats() {
202
+ const logs = this.ringBuffer;
203
+ const byMethod = {};
204
+ const byStatusCode = {};
205
+ let errorCount = 0;
206
+ let totalDuration = 0;
207
+ for (const entry of logs) {
208
+ byMethod[entry.method] = (byMethod[entry.method] || 0) + 1;
209
+ if (entry.statusCode) {
210
+ byStatusCode[entry.statusCode] = (byStatusCode[entry.statusCode] || 0) + 1;
211
+ }
212
+ if (entry.error)
213
+ errorCount++;
214
+ if (entry.durationMs)
215
+ totalDuration += entry.durationMs;
216
+ }
217
+ return {
218
+ totalLogged: logs.length,
219
+ errorCount,
220
+ avgDurationMs: logs.length > 0 ? Math.round(totalDuration / logs.length) : 0,
221
+ byMethod,
222
+ byStatusCode,
223
+ };
224
+ }
225
+ /**
226
+ * Clear the ring buffer
227
+ */
228
+ clear() {
229
+ this.ringBuffer = [];
230
+ logger_1.default.info('[RequestLogService] Buffer cleared');
231
+ }
232
+ /**
233
+ * Persist entry to database (for audit trail)
234
+ */
235
+ persistEntry(entry) {
236
+ return __awaiter(this, void 0, void 0, function* () {
237
+ try {
238
+ // Using a generic Log table or you could create a dedicated RequestLog table
239
+ // For now, we'll just log - you can add Prisma model later
240
+ // await prisma.requestLog.create({ data: { ... } });
241
+ }
242
+ catch (err) {
243
+ logger_1.default.warn(`[RequestLogService] Failed to persist log: ${err.message}`);
244
+ }
245
+ });
246
+ }
247
+ };
248
+ exports.RequestLogService = RequestLogService;
249
+ exports.RequestLogService = RequestLogService = __decorate([
250
+ (0, typedi_1.Service)(),
251
+ __metadata("design:paramtypes", [])
252
+ ], RequestLogService);