@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,87 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SocketServer = void 0;
13
+ const socket_io_1 = require("socket.io");
14
+ const typedi_1 = require("typedi");
15
+ const logger_1 = __importDefault(require("../logger"));
16
+ const SocketEvents_1 = require("../enums/SocketEvents");
17
+ let SocketServer = class SocketServer {
18
+ constructor() {
19
+ this.io = null;
20
+ this.nodes = new Map(); // socketId -> nodeHost
21
+ }
22
+ initialize(server) {
23
+ this.io = new socket_io_1.Server(server, {
24
+ cors: {
25
+ origin: '*',
26
+ methods: ['GET', 'POST'],
27
+ },
28
+ });
29
+ this.io.on('connection', (socket) => {
30
+ const socketId = socket.id;
31
+ logger_1.default.info(`[SocketServer] New connection attempt: ${socketId}`);
32
+ // 1. Mandatory Handshake for Protocol Sync
33
+ socket.on(SocketEvents_1.SocketEvents.HANDSHAKE, (data) => {
34
+ const { version, host } = data;
35
+ if (version !== SocketEvents_1.XENON_PROTOCOL_VERSION) {
36
+ logger_1.default.error(`[SocketServer] Protocol mismatch for client ${socketId}. Hub: ${SocketEvents_1.XENON_PROTOCOL_VERSION}, Client: ${version}`);
37
+ socket.disconnect();
38
+ return;
39
+ }
40
+ logger_1.default.info(`[SocketServer] Handshake successful with client ${host || socketId} (v${version})`);
41
+ });
42
+ socket.on(SocketEvents_1.SocketEvents.REGISTER_NODE, (data) => {
43
+ const { host } = data;
44
+ this.nodes.set(socketId, host);
45
+ logger_1.default.info(`[SocketServer] Node registered: ${host} (Socket: ${socketId})`);
46
+ socket.join('nodes');
47
+ // Notify dashboard about new node
48
+ this.emitToDashboard(SocketEvents_1.SocketEvents.NODE_CONNECTED, { host });
49
+ });
50
+ socket.on(SocketEvents_1.SocketEvents.REGISTER_DASHBOARD, () => {
51
+ logger_1.default.info(`[SocketServer] Dashboard client registered (Socket: ${socketId})`);
52
+ socket.join('dashboard');
53
+ });
54
+ socket.on('disconnect', () => {
55
+ if (this.nodes.has(socketId)) {
56
+ const host = this.nodes.get(socketId);
57
+ logger_1.default.info(`[SocketServer] Node disconnected: ${host} (Socket: ${socketId})`);
58
+ this.nodes.delete(socketId);
59
+ this.emitToDashboard(SocketEvents_1.SocketEvents.NODE_DISCONNECTED, { host });
60
+ }
61
+ else {
62
+ logger_1.default.info(`[SocketServer] Client disconnected: ${socketId}`);
63
+ }
64
+ });
65
+ });
66
+ logger_1.default.info('[SocketServer] WebSocket server initialized');
67
+ }
68
+ emitToDashboard(event, data) {
69
+ if (this.io) {
70
+ this.io.to('dashboard').emit(event, data);
71
+ }
72
+ }
73
+ emitToNodes(event, data) {
74
+ if (this.io) {
75
+ this.io.to('nodes').emit(event, data);
76
+ }
77
+ }
78
+ broadcast(event, data) {
79
+ if (this.io) {
80
+ this.io.emit(event, data);
81
+ }
82
+ }
83
+ };
84
+ exports.SocketServer = SocketServer;
85
+ exports.SocketServer = SocketServer = __decorate([
86
+ (0, typedi_1.Service)()
87
+ ], SocketServer);
@@ -0,0 +1,132 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TracingService = void 0;
13
+ const api_1 = require("@opentelemetry/api");
14
+ const sdk_node_1 = require("@opentelemetry/sdk-node");
15
+ const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
16
+ const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
17
+ const typedi_1 = require("typedi");
18
+ const logger_1 = __importDefault(require("../logger"));
19
+ let TracingService = class TracingService {
20
+ constructor() {
21
+ this.sdk = null;
22
+ this.tracer = api_1.trace.getTracer('xenon-core');
23
+ this.activeSpans = new Map();
24
+ }
25
+ initialize() {
26
+ const exporters = [];
27
+ // Always add console exporter for debugging if enabled via env or log level
28
+ if (process.env.XENON_OTEL_DEBUG === 'true') {
29
+ logger_1.default.info('[TracingService] XENON_OTEL_DEBUG is true. Adding ConsoleSpanExporter.');
30
+ exporters.push(new sdk_trace_base_1.ConsoleSpanExporter());
31
+ }
32
+ // Add OTLP exporter if endpoint is provided
33
+ if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
34
+ logger_1.default.info(`[TracingService] OTLP Endpoint found: ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}. Adding OTLPTraceExporter.`);
35
+ exporters.push(new exporter_trace_otlp_http_1.OTLPTraceExporter({
36
+ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
37
+ }));
38
+ }
39
+ if (exporters.length === 0) {
40
+ logger_1.default.info('[TracingService] No OTel exporters configured. Spans will be recorded in memory only.');
41
+ }
42
+ logger_1.default.info(`[TracingService] Initializing with ${exporters.length} exporters. XENON_OTEL_DEBUG=${process.env.XENON_OTEL_DEBUG}`);
43
+ if (exporters.length > 0) {
44
+ this.sdk = new sdk_node_1.NodeSDK({
45
+ serviceName: 'xenon',
46
+ spanProcessor: new sdk_trace_base_1.SimpleSpanProcessor(exporters[0]),
47
+ });
48
+ try {
49
+ this.sdk.start();
50
+ logger_1.default.info('[TracingService] OpenTelemetry SDK started');
51
+ }
52
+ catch (err) {
53
+ logger_1.default.error(`[TracingService] Failed to start OTel SDK: ${err.message}`);
54
+ }
55
+ }
56
+ else {
57
+ logger_1.default.info('[TracingService] No OTel exporters enabled.');
58
+ }
59
+ }
60
+ startSessionSpan(sessionId, name, attributes = {}) {
61
+ if (!this.sdk)
62
+ return undefined;
63
+ const span = this.tracer.startSpan(`Session: ${name || sessionId}`, {
64
+ kind: api_1.SpanKind.SERVER,
65
+ attributes: Object.assign({ 'xenon.session_id': sessionId }, attributes),
66
+ });
67
+ this.activeSpans.set(sessionId, span);
68
+ return span;
69
+ }
70
+ startCommandSpan(sessionId, commandName, attributes = {}) {
71
+ if (!this.sdk)
72
+ return undefined;
73
+ const parentSpan = this.activeSpans.get(sessionId);
74
+ const spanOptions = {
75
+ kind: api_1.SpanKind.INTERNAL,
76
+ attributes: Object.assign({ 'xenon.session_id': sessionId, 'xenon.command': commandName }, attributes),
77
+ };
78
+ let span;
79
+ if (parentSpan) {
80
+ const ctx = api_1.trace.setSpan(api_1.context.active(), parentSpan);
81
+ span = this.tracer.startSpan(commandName, spanOptions, ctx);
82
+ }
83
+ else {
84
+ span = this.tracer.startSpan(commandName, spanOptions);
85
+ }
86
+ this.activeSpans.set(`${sessionId}:${commandName}`, span);
87
+ return span;
88
+ }
89
+ endSpan(id, status = 'OK', attributes = {}) {
90
+ if (!this.sdk)
91
+ return;
92
+ const span = this.activeSpans.get(id);
93
+ if (span) {
94
+ if (Object.keys(attributes).length > 0) {
95
+ span.setAttributes(attributes);
96
+ }
97
+ span.setStatus({
98
+ code: status === 'OK' ? api_1.SpanStatusCode.OK : api_1.SpanStatusCode.ERROR,
99
+ });
100
+ span.end();
101
+ this.activeSpans.delete(id);
102
+ }
103
+ }
104
+ recordError(id, error) {
105
+ if (!this.sdk)
106
+ return;
107
+ const span = this.activeSpans.get(id);
108
+ if (span) {
109
+ span.recordException(error);
110
+ span.setStatus({
111
+ code: api_1.SpanStatusCode.ERROR,
112
+ message: typeof error === 'string' ? error : error.message,
113
+ });
114
+ }
115
+ }
116
+ getTraceId(sessionId) {
117
+ var _a;
118
+ return (_a = this.activeSpans.get(sessionId)) === null || _a === void 0 ? void 0 : _a.spanContext().traceId;
119
+ }
120
+ getSpanId(id) {
121
+ var _a;
122
+ return (_a = this.activeSpans.get(id)) === null || _a === void 0 ? void 0 : _a.spanContext().spanId;
123
+ }
124
+ shutdown() {
125
+ var _a;
126
+ (_a = this.sdk) === null || _a === void 0 ? void 0 : _a.shutdown();
127
+ }
128
+ };
129
+ exports.TracingService = TracingService;
130
+ exports.TracingService = TracingService = __decorate([
131
+ (0, typedi_1.Service)()
132
+ ], TracingService);
@@ -0,0 +1,220 @@
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.VideoPipelineService = void 0;
58
+ const typedi_1 = require("typedi");
59
+ const child_process_1 = require("child_process");
60
+ const logger_1 = __importDefault(require("../logger"));
61
+ const path_1 = __importDefault(require("path"));
62
+ const os_1 = __importDefault(require("os"));
63
+ const fs = __importStar(require("fs"));
64
+ const config_1 = require("../config");
65
+ const device_store_1 = require("../data-service/device-store");
66
+ const ResourceIsolationService_1 = require("./ResourceIsolationService");
67
+ /**
68
+ * Intelligent Video Pipeline Service
69
+ *
70
+ * Objectives:
71
+ * 1. Hardware-accelerated encoding (VideoToolbox on Mac).
72
+ * 2. Zero-copy asset management (direct storage).
73
+ * 3. Instant Playback via fragmented MP4.
74
+ */
75
+ let VideoPipelineService = class VideoPipelineService {
76
+ constructor() {
77
+ this.activeRecordings = new Map();
78
+ this.recordingPaths = new Map();
79
+ this.isMac = os_1.default.platform() === 'darwin';
80
+ }
81
+ /**
82
+ * Start background recording for a session
83
+ */
84
+ startRecording(options) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ var _a;
87
+ const { sessionId, udid } = options;
88
+ if (this.activeRecordings.has(sessionId)) {
89
+ logger_1.default.warn(`[VideoPipeline] Recording already in progress for session ${sessionId}`);
90
+ return;
91
+ }
92
+ // 1. Resolve Device MJPEG Port
93
+ let mjpegPort = options.mjpegPort;
94
+ if (!mjpegPort) {
95
+ const device = yield device_store_1.DeviceStoreFactory.getStore().findDevice({ udid });
96
+ if (!device || !device.mjpegServerPort) {
97
+ throw new Error(`[VideoPipeline] Cannot find MJPEG port for device ${udid}. Is the stream service running?`);
98
+ }
99
+ mjpegPort = device.mjpegServerPort;
100
+ }
101
+ const mjpegUrl = `http://127.0.0.1:${mjpegPort}`;
102
+ const outputDir = path_1.default.join(config_1.config.sessionAssetsPath, sessionId, 'video');
103
+ if (!fs.existsSync(outputDir)) {
104
+ fs.mkdirSync(outputDir, { recursive: true });
105
+ }
106
+ const outputPath = path_1.default.join(outputDir, `${sessionId}.mp4`);
107
+ logger_1.default.info(`[VideoPipeline] Starting HW-accelerated recording for ${sessionId} from ${mjpegUrl}`);
108
+ // Small settlement delay to allow the source stream to prime
109
+ yield new Promise((r) => setTimeout(r, 500));
110
+ // 2. Construct FFMPEG Args
111
+ // -f mjpeg: Input format
112
+ // -i: Input source
113
+ // -c:v: Hardware accelerated encoder based on platform
114
+ // -movflags: fMP4 for instant playback and crash resiliency
115
+ const args = [
116
+ '-y',
117
+ '-loglevel',
118
+ 'error', // Only log errors to keep console clean
119
+ '-reconnect',
120
+ '1',
121
+ '-reconnect_at_eof',
122
+ '1',
123
+ '-reconnect_streamed',
124
+ '1',
125
+ '-reconnect_delay_max',
126
+ '5',
127
+ '-probesize',
128
+ '32', // Fast startup for MJPEG
129
+ '-analyzeduration',
130
+ '0',
131
+ '-f',
132
+ 'mjpeg',
133
+ '-i',
134
+ mjpegUrl,
135
+ '-pix_fmt',
136
+ 'yuv420p',
137
+ ];
138
+ if (this.isMac) {
139
+ args.push('-c:v', 'h264_videotoolbox');
140
+ args.push('-realtime', '1'); // VideoToolbox optimization
141
+ args.push('-q:v', '50'); // High quality/efficiency balance
142
+ }
143
+ else {
144
+ args.push('-c:v', 'libx264');
145
+ args.push('-preset', 'veryfast');
146
+ args.push('-crf', '25');
147
+ }
148
+ // Instant Playback Flags (fMP4)
149
+ args.push('-movflags', 'frag_keyframe+empty_moov+default_base_moof');
150
+ args.push(outputPath);
151
+ // 3. Wrap with Resource Isolation (Economy)
152
+ const isolationService = typedi_1.Container.get(ResourceIsolationService_1.ResourceIsolationService);
153
+ const { command, args: wrappedArgs } = isolationService.wrapSpawn('ffmpeg', args, 'Economy');
154
+ const ffmpegProc = (0, child_process_1.spawn)(command, wrappedArgs, {
155
+ stdio: ['ignore', 'ignore', 'pipe'], // Only capture stderr for errors
156
+ });
157
+ (_a = ffmpegProc.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
158
+ const msg = data.toString();
159
+ if (msg.toLowerCase().includes('error')) {
160
+ logger_1.default.error(`[VideoPipeline] FFMPEG Error [${sessionId}]: ${msg}`);
161
+ }
162
+ });
163
+ ffmpegProc.on('exit', (code) => {
164
+ if (code !== 0 && code !== null) {
165
+ logger_1.default.warn(`[VideoPipeline] FFMPEG for ${sessionId} exited with code ${code}`);
166
+ }
167
+ this.activeRecordings.delete(sessionId);
168
+ });
169
+ this.activeRecordings.set(sessionId, ffmpegProc);
170
+ this.recordingPaths.set(sessionId, outputPath);
171
+ });
172
+ }
173
+ /**
174
+ * Stop recording and return the relative asset path
175
+ */
176
+ stopRecording(sessionId) {
177
+ return __awaiter(this, void 0, void 0, function* () {
178
+ const proc = this.activeRecordings.get(sessionId);
179
+ const recordedPath = this.recordingPaths.get(sessionId);
180
+ if (!proc) {
181
+ logger_1.default.info(`[VideoPipeline] No active recording process for ${sessionId}, returning stored path if any.`);
182
+ const relativePath = recordedPath
183
+ ? path_1.default.relative(config_1.config.sessionAssetsPath, recordedPath)
184
+ : null;
185
+ this.recordingPaths.delete(sessionId);
186
+ return relativePath;
187
+ }
188
+ logger_1.default.info(`[VideoPipeline] Stopping recording for ${sessionId}`);
189
+ return new Promise((resolve) => {
190
+ proc.on('exit', () => {
191
+ const relativePath = path_1.default.relative(config_1.config.sessionAssetsPath, recordedPath);
192
+ this.activeRecordings.delete(sessionId);
193
+ this.recordingPaths.delete(sessionId);
194
+ resolve(relativePath);
195
+ });
196
+ proc.kill('SIGINT'); // Graceful termination to ensure header finalization
197
+ });
198
+ });
199
+ }
200
+ isRecording(sessionId) {
201
+ return this.activeRecordings.has(sessionId) || this.recordingPaths.has(sessionId);
202
+ }
203
+ /**
204
+ * Terminate all active recordings (used during system shutdown)
205
+ */
206
+ cleanup() {
207
+ return __awaiter(this, void 0, void 0, function* () {
208
+ logger_1.default.info(`[VideoPipeline] Cleaning up ${this.activeRecordings.size} active recordings`);
209
+ for (const [sessionId, proc] of this.activeRecordings.entries()) {
210
+ proc.kill('SIGINT');
211
+ }
212
+ this.activeRecordings.clear();
213
+ });
214
+ }
215
+ };
216
+ exports.VideoPipelineService = VideoPipelineService;
217
+ exports.VideoPipelineService = VideoPipelineService = __decorate([
218
+ (0, typedi_1.Service)(),
219
+ __metadata("design:paramtypes", [])
220
+ ], VideoPipelineService);