@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.
- package/README.md +446 -0
- package/lib/package.json +207 -0
- package/lib/public/assets/Layouts-7IT8aFLI.js +11 -0
- package/lib/public/assets/Layouts-DPMls9vh.css +1 -0
- package/lib/public/assets/ai-settings-BbnfgdEx.js +11 -0
- package/lib/public/assets/apps-CRMrI4_p.js +16 -0
- package/lib/public/assets/apps-CcM77dgg.css +1 -0
- package/lib/public/assets/badge-B1nKs8zj.css +1 -0
- package/lib/public/assets/badge-CSvl5xIU.js +11 -0
- package/lib/public/assets/button-CJlKn4PZ.css +1 -0
- package/lib/public/assets/button-CvLaGFYj.js +26 -0
- package/lib/public/assets/calendar-6w-D6Oaw.js +6 -0
- package/lib/public/assets/clock-DcdeWBPr.js +6 -0
- package/lib/public/assets/cpu-DiSoXT9n.js +6 -0
- package/lib/public/assets/device-explorer-CajM63OJ.js +193 -0
- package/lib/public/assets/device-explorer-CxdUAoTL.css +1 -0
- package/lib/public/assets/index-ByQwMN5T.js +174 -0
- package/lib/public/assets/index-C1DBaoSh.js +1 -0
- package/lib/public/assets/index-qzCez_kk.css +1 -0
- package/lib/public/assets/lock-B23ibZmo.js +6 -0
- package/lib/public/assets/maintenance-settings-CirzA6yG.js +6 -0
- package/lib/public/assets/mouse-pointer-2-Cz76SHFb.js +6 -0
- package/lib/public/assets/plus-BBwlIevt.js +6 -0
- package/lib/public/assets/session-dashboard-C2k7FFv_.css +1 -0
- package/lib/public/assets/session-dashboard-HPDtwPOZ.js +62 -0
- package/lib/public/assets/settings-DrZsZwdc.js +1 -0
- package/lib/public/assets/trash-2-DQpvzJec.js +6 -0
- package/lib/public/assets/useSocket-Dxsqae2a.js +16 -0
- package/lib/public/assets/webhook-settings-CDPgsgkb.css +1 -0
- package/lib/public/assets/webhook-settings-Cp-B4Nrw.js +1 -0
- package/lib/public/assets/zap-DovP6iow.js +6 -0
- package/lib/public/favicon.ico +0 -0
- package/lib/public/favicon.png +0 -0
- package/lib/public/favicon.svg +9 -0
- package/lib/public/index.html +46 -0
- package/lib/public/logo.svg +17 -0
- package/lib/public/logo192.png +0 -0
- package/lib/public/logo512.png +0 -0
- package/lib/public/manifest.json +25 -0
- package/lib/public/robots.txt +3 -0
- package/lib/schema.json +348 -0
- package/lib/src/InternalHttpClient.js +212 -0
- package/lib/src/PluginContext.js +29 -0
- package/lib/src/XenonCapabilityManager.js +199 -0
- package/lib/src/app/index.js +167 -0
- package/lib/src/app/routers/apps.js +79 -0
- package/lib/src/app/routers/config.js +131 -0
- package/lib/src/app/routers/control.js +835 -0
- package/lib/src/app/routers/dashboard.js +301 -0
- package/lib/src/app/routers/grid.js +352 -0
- package/lib/src/app/routers/reservation.js +190 -0
- package/lib/src/app/routers/webhook.js +83 -0
- package/lib/src/app/swagger-docs.js +203 -0
- package/lib/src/app/swagger.js +366 -0
- package/lib/src/chromeUtils.js +148 -0
- package/lib/src/commands/handle.js +19 -0
- package/lib/src/commands/index.js +8 -0
- package/lib/src/config.js +73 -0
- package/lib/src/dashboard/asset-manager.js +84 -0
- package/lib/src/dashboard/commands.js +284 -0
- package/lib/src/dashboard/event-manager.js +699 -0
- package/lib/src/dashboard/services/app-service.js +134 -0
- package/lib/src/dashboard/services/failure-analysis-service.js +173 -0
- package/lib/src/dashboard/services/session-service.js +113 -0
- package/lib/src/data-service/CircuitBreaker.js +83 -0
- package/lib/src/data-service/config-service.js +155 -0
- package/lib/src/data-service/db.js +122 -0
- package/lib/src/data-service/device-service.js +320 -0
- package/lib/src/data-service/device-store.interface.js +2 -0
- package/lib/src/data-service/device-store.js +345 -0
- package/lib/src/data-service/pending-sessions-service.js +25 -0
- package/lib/src/data-service/pluginArgs.js +25 -0
- package/lib/src/data-service/prisma-service.js +31 -0
- package/lib/src/data-service/prisma-store.js +385 -0
- package/lib/src/data-service/queue-service.js +150 -0
- package/lib/src/data-service/web-config-service.js +130 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +1155 -0
- package/lib/src/device-managers/ChromeDriverManager.js +68 -0
- package/lib/src/device-managers/HealthMonitorService.js +325 -0
- package/lib/src/device-managers/IOSDeviceManager.js +351 -0
- package/lib/src/device-managers/NodeDevices.js +82 -0
- package/lib/src/device-managers/android/AndroidStreamService.js +370 -0
- package/lib/src/device-managers/android/DeviceLockManager.js +45 -0
- package/lib/src/device-managers/cloud/CapabilityManager.js +26 -0
- package/lib/src/device-managers/cloud/Devices.js +86 -0
- package/lib/src/device-managers/iOSTracker.js +44 -0
- package/lib/src/device-managers/index.js +89 -0
- package/lib/src/device-managers/ios/IOSDiscoveryService.js +268 -0
- package/lib/src/device-managers/ios/IOSStreamService.js +893 -0
- package/lib/src/device-managers/ios/WDAClient.js +866 -0
- package/lib/src/device-utils.js +663 -0
- package/lib/src/enums/Capabilities.js +8 -0
- package/lib/src/enums/Cloud.js +11 -0
- package/lib/src/enums/Platform.js +9 -0
- package/lib/src/enums/SessionType.js +9 -0
- package/lib/src/enums/SocketEvents.js +15 -0
- package/lib/src/helpers/UniversalMjpegProxy.js +273 -0
- package/lib/src/helpers/index.js +229 -0
- package/lib/src/index.js +95 -0
- package/lib/src/interceptors/CommandInterceptor.js +524 -0
- package/lib/src/interfaces/ICloudManager.js +2 -0
- package/lib/src/interfaces/IDevice.js +2 -0
- package/lib/src/interfaces/IDeviceFilterOptions.js +2 -0
- package/lib/src/interfaces/IDeviceManager.js +2 -0
- package/lib/src/interfaces/IOptions.js +2 -0
- package/lib/src/interfaces/IPluginArgs.js +55 -0
- package/lib/src/interfaces/ISessionCapability.js +2 -0
- package/lib/src/logger.js +225 -0
- package/lib/src/plugin.js +244 -0
- package/lib/src/prisma.js +12 -0
- package/lib/src/profiling/AndroidAppProfiler.js +213 -0
- package/lib/src/proxy/wd-command-proxy.js +221 -0
- package/lib/src/scripts/generate-database-migration.js +59 -0
- package/lib/src/scripts/initialize-database.js +55 -0
- package/lib/src/scripts/install-go-ios.js +66 -0
- package/lib/src/scripts/prepare-prisma.js +89 -0
- package/lib/src/services/AICommandService.js +143 -0
- package/lib/src/services/AIService.js +466 -0
- package/lib/src/services/CleanupService.js +141 -0
- package/lib/src/services/EventBus.js +74 -0
- package/lib/src/services/InspectorService.js +395 -0
- package/lib/src/services/MetricsService.js +134 -0
- package/lib/src/services/NetworkConditioningService.js +173 -0
- package/lib/src/services/NotificationService.js +163 -0
- package/lib/src/services/RequestLogService.js +252 -0
- package/lib/src/services/ResourceIsolationService.js +122 -0
- package/lib/src/services/SecurityService.js +120 -0
- package/lib/src/services/ServerManager.js +284 -0
- package/lib/src/services/SessionHeartbeatService.js +158 -0
- package/lib/src/services/SessionLifecycleService.js +572 -0
- package/lib/src/services/SocketClient.js +71 -0
- package/lib/src/services/SocketServer.js +87 -0
- package/lib/src/services/TracingService.js +132 -0
- package/lib/src/services/VideoPipelineService.js +220 -0
- package/lib/src/services/healing/FuzzyXmlHealingProvider.js +333 -0
- package/lib/src/services/healing/HealEtalonService.js +98 -0
- package/lib/src/services/healing/HealedLocatorGenerator.js +132 -0
- package/lib/src/services/healing/HealingOrchestrator.js +165 -0
- package/lib/src/services/healing/LlmHealingProvider.js +77 -0
- package/lib/src/services/healing/OcrHealingProvider.js +119 -0
- package/lib/src/services/healing/ResilioTreeHealingProvider.js +100 -0
- package/lib/src/services/healing/VisualAiHealingProvider.js +90 -0
- package/lib/src/services/healing/types.js +12 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +718 -0
- package/lib/src/services/omni-vision/VisionAssertionService.js +68 -0
- package/lib/src/sessions/CloudSession.js +42 -0
- package/lib/src/sessions/LocalSession.js +313 -0
- package/lib/src/sessions/RemoteSession.js +287 -0
- package/lib/src/sessions/SessionManager.js +238 -0
- package/lib/src/sessions/XenonSession.js +44 -0
- package/lib/src/types/CLIArgs.js +2 -0
- package/lib/src/types/CloudArgs.js +2 -0
- package/lib/src/types/CloudSchema.js +131 -0
- package/lib/src/types/DeviceType.js +2 -0
- package/lib/src/types/DeviceUpdate.js +2 -0
- package/lib/src/types/IOSDevice.js +2 -0
- package/lib/src/types/Platform.js +2 -0
- package/lib/src/types/SessionStatus.js +11 -0
- package/lib/src/validators/CapabilityValidator.js +93 -0
- package/lib/test/e2e/android/conf.spec.js +43 -0
- package/lib/test/e2e/android/conf2.spec.js +44 -0
- package/lib/test/e2e/android/conf3.spec.js +44 -0
- package/lib/test/e2e/e2ehelper.js +113 -0
- package/lib/test/e2e/hubnode/forward-request.spec.js +224 -0
- package/lib/test/e2e/hubnode/hubnode.spec.js +214 -0
- package/lib/test/e2e/ios/conf1.spec.js +39 -0
- package/lib/test/e2e/ios/conf2.spec.js +39 -0
- package/lib/test/e2e/plugin-harness.js +236 -0
- package/lib/test/e2e/plugin.spec.js +97 -0
- package/lib/test/e2e/telemetry_verification.spec.js +83 -0
- package/lib/test/e2e/video-recording-test.spec.js +63 -0
- package/lib/test/helpers/test-container.js +112 -0
- package/lib/test/integration/androidDevices.spec.js +137 -0
- package/lib/test/integration/cliArgs.js +73 -0
- package/lib/test/integration/ios/01iOSSimulator.spec.js +291 -0
- package/lib/test/integration/ios/02iOSDevices.spec.js +75 -0
- package/lib/test/integration/testHelpers.js +74 -0
- package/lib/test/unit/AndroidDeviceManager.spec.js +178 -0
- package/lib/test/unit/ChromeDriverManager.spec.js +26 -0
- package/lib/test/unit/CleanupService.spec.js +21 -0
- package/lib/test/unit/DeviceModel.spec.js +157 -0
- package/lib/test/unit/FuzzyXmlHealingProvider.test.js +294 -0
- package/lib/test/unit/GetAdbOriginal.js +42 -0
- package/lib/test/unit/HealingCascade.test.js +128 -0
- package/lib/test/unit/IOSDeviceManager.spec.js +261 -0
- package/lib/test/unit/RemoteIOs.spec.js +78 -0
- package/lib/test/unit/ResilioTreeHealingProvider.test.js +96 -0
- package/lib/test/unit/commands.spec.js +27 -0
- package/lib/test/unit/config.spec.js +27 -0
- package/lib/test/unit/device-service.spec.js +307 -0
- package/lib/test/unit/device-utils.spec.js +313 -0
- package/lib/test/unit/fixtures/device.config.js +4 -0
- package/lib/test/unit/fixtures/devices.js +89 -0
- package/lib/test/unit/helpers.spec.js +62 -0
- package/lib/test/unit/omni-vision.spec.js +100 -0
- package/lib/test/unit/plugin.spec.js +133 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +207 -0
- package/prisma/data.db +0 -0
- package/prisma/dev.db +0 -0
- package/prisma/dev.db-journal +0 -0
- package/prisma/migrations/20231011074725_initial_tables/migration.sql +47 -0
- package/prisma/migrations/20231226115334_update_session_log/migration.sql +2 -0
- package/prisma/migrations/20251204113710_add_video_recording_enabled/migration.sql +29 -0
- package/prisma/migrations/20251204132449_add_log_table/migration.sql +11 -0
- package/prisma/migrations/20251205050111_add_profiling_support/migration.sql +47 -0
- package/prisma/migrations/20251205050947_add_is_error_field/migration.sql +24 -0
- package/prisma/migrations/20260126201337_add_app_model/migration.sql +18 -0
- package/prisma/migrations/20260130115722_add_performance_trace_and_xenon_sync/migration.sql +2 -0
- package/prisma/migrations/20260130135114_add_device_models/migration.sql +57 -0
- package/prisma/migrations/20260130140655_make_systemport_optional/migration.sql +45 -0
- package/prisma/migrations/20260130140932_make_device_fields_optional/migration.sql +45 -0
- package/prisma/migrations/20260130141040_final_schema_fix/migration.sql +45 -0
- package/prisma/migrations/20260130143234_add_device_health_fields/migration.sql +4 -0
- package/prisma/migrations/20260130144921_add_failure_category/migration.sql +2 -0
- package/prisma/migrations/20260131151456_add_webhook_config/migration.sql +10 -0
- package/prisma/migrations/20260201094507_add_device_tags/migration.sql +11 -0
- package/prisma/migrations/20260201103410_add_managed_process/migration.sql +15 -0
- package/prisma/migrations/20260201140637_add_web_config/migration.sql +22 -0
- package/prisma/migrations/20260201162232_add_session_progress/migration.sql +2 -0
- package/prisma/migrations/20260201174231_add_total_healed_count/migration.sql +2 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +210 -0
- package/schema.json +348 -0
- package/scripts/build-xenon.sh +32 -0
- package/scripts/dev/debug-gemini.ts +44 -0
- package/scripts/generate-types-from-schema.js +86 -0
- package/scripts/install-compatible-driver.js +39 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
var DevicePlatform;
|
|
4
|
+
(function (DevicePlatform) {
|
|
5
|
+
DevicePlatform["ANDROID"] = "android";
|
|
6
|
+
DevicePlatform["IOS"] = "ios";
|
|
7
|
+
DevicePlatform["BOTH"] = "both";
|
|
8
|
+
})(DevicePlatform || (DevicePlatform = {}));
|
|
9
|
+
exports.default = DevicePlatform;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
var SessionType;
|
|
4
|
+
(function (SessionType) {
|
|
5
|
+
SessionType["LOCAL"] = "local";
|
|
6
|
+
SessionType["REMOTE"] = "remote";
|
|
7
|
+
SessionType["CLOUD"] = "cloud";
|
|
8
|
+
})(SessionType || (SessionType = {}));
|
|
9
|
+
exports.default = SessionType;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.XENON_PROTOCOL_VERSION = exports.SocketEvents = void 0;
|
|
4
|
+
var SocketEvents;
|
|
5
|
+
(function (SocketEvents) {
|
|
6
|
+
SocketEvents["HANDSHAKE"] = "handshake";
|
|
7
|
+
SocketEvents["REGISTER_NODE"] = "register_node";
|
|
8
|
+
SocketEvents["REGISTER_DASHBOARD"] = "register_dashboard";
|
|
9
|
+
SocketEvents["NODE_CONNECTED"] = "node_connected";
|
|
10
|
+
SocketEvents["NODE_DISCONNECTED"] = "node_disconnected";
|
|
11
|
+
SocketEvents["SESSION_STARTED"] = "session_started";
|
|
12
|
+
SocketEvents["SESSION_STOPPED"] = "session_stopped";
|
|
13
|
+
SocketEvents["SESSION_COMMAND"] = "session_command";
|
|
14
|
+
})(SocketEvents || (exports.SocketEvents = SocketEvents = {}));
|
|
15
|
+
exports.XENON_PROTOCOL_VERSION = '1.0.0';
|
|
@@ -0,0 +1,273 @@
|
|
|
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.UniversalMjpegProxy = void 0;
|
|
49
|
+
const http_1 = __importDefault(require("http"));
|
|
50
|
+
const url_1 = require("url");
|
|
51
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
52
|
+
/**
|
|
53
|
+
* Universal MJPEG Proxy
|
|
54
|
+
*
|
|
55
|
+
* Principal Design: Handling WDA's inconsistent MJPEG behavior.
|
|
56
|
+
* Some WDA versions/configurations send standard HTTP MJPEG streams,
|
|
57
|
+
* while others (especially over high-performance ports) might send raw boundaries
|
|
58
|
+
* without the initial HTTP/1.1 response status line.
|
|
59
|
+
*
|
|
60
|
+
* This proxy sniffs high-level response headers and gracefully handles raw streams
|
|
61
|
+
* by injecting consistent HTTP headers for the client browser.
|
|
62
|
+
*/
|
|
63
|
+
class UniversalMjpegProxy {
|
|
64
|
+
constructor(mjpegUrl) {
|
|
65
|
+
this.boundary = 'BoundaryString';
|
|
66
|
+
this.globalResponseHeaders = {
|
|
67
|
+
'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0',
|
|
68
|
+
Pragma: 'no-cache',
|
|
69
|
+
Connection: 'keep-alive',
|
|
70
|
+
Expires: '0',
|
|
71
|
+
'Content-Type': 'multipart/x-mixed-replace; boundary="BoundaryString"',
|
|
72
|
+
'Access-Control-Allow-Origin': '*',
|
|
73
|
+
};
|
|
74
|
+
this.clients = new Set();
|
|
75
|
+
this.sourceRequest = null;
|
|
76
|
+
this.isConnected = false;
|
|
77
|
+
this.reconnectionTimeout = null;
|
|
78
|
+
this.isStopped = false;
|
|
79
|
+
/**
|
|
80
|
+
* Principal Resilience: Retry limits with exponential backoff.
|
|
81
|
+
* Without these, the proxy retries every 500ms FOREVER, flooding logs
|
|
82
|
+
* and wasting CPU after a session ends or the server shuts down.
|
|
83
|
+
*/
|
|
84
|
+
this.consecutiveRetries = 0;
|
|
85
|
+
this.mjpegUrl = mjpegUrl;
|
|
86
|
+
}
|
|
87
|
+
get url() {
|
|
88
|
+
return this.mjpegUrl;
|
|
89
|
+
}
|
|
90
|
+
proxyRequest(req, res) {
|
|
91
|
+
res.writeHead(200, this.globalResponseHeaders);
|
|
92
|
+
this.clients.add(res);
|
|
93
|
+
logger_1.default.debug(`[MjpegProxy] New client joined stream: ${this.mjpegUrl}. Total clients: ${this.clients.size}`);
|
|
94
|
+
req.on('close', () => {
|
|
95
|
+
this.clients.delete(res);
|
|
96
|
+
logger_1.default.debug(`[MjpegProxy] Client left stream. Remaining: ${this.clients.size}`);
|
|
97
|
+
if (this.clients.size === 0) {
|
|
98
|
+
this.stopSource();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
if (!this.isConnected) {
|
|
102
|
+
this.startSource();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
startSource() {
|
|
106
|
+
const url = new url_1.URL(this.mjpegUrl);
|
|
107
|
+
const options = {
|
|
108
|
+
hostname: url.hostname,
|
|
109
|
+
port: url.port,
|
|
110
|
+
path: url.pathname + url.search,
|
|
111
|
+
method: 'GET',
|
|
112
|
+
};
|
|
113
|
+
logger_1.default.info(`[MjpegProxy] Connecting to source: ${this.mjpegUrl}`);
|
|
114
|
+
this.sourceRequest = http_1.default.get(options, (sourceRes) => {
|
|
115
|
+
if (sourceRes.statusCode !== 200) {
|
|
116
|
+
logger_1.default.error(`[MjpegProxy] Source returned non-200 status: ${sourceRes.statusCode}`);
|
|
117
|
+
this.reconnect();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.isConnected = true;
|
|
121
|
+
this.consecutiveRetries = 0; // Reset on successful connection
|
|
122
|
+
// Standard MJPEG
|
|
123
|
+
sourceRes.on('data', (chunk) => {
|
|
124
|
+
this.broadcast(chunk);
|
|
125
|
+
});
|
|
126
|
+
sourceRes.on('end', () => {
|
|
127
|
+
logger_1.default.warn(`[MjpegProxy] Source stream ended: ${this.mjpegUrl}`);
|
|
128
|
+
this.reconnect();
|
|
129
|
+
});
|
|
130
|
+
sourceRes.on('error', (err) => {
|
|
131
|
+
logger_1.default.error(`[MjpegProxy] Source stream error: ${err.message}`);
|
|
132
|
+
this.reconnect();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
this.sourceRequest.on('error', (err) => {
|
|
136
|
+
if (this.isStopped)
|
|
137
|
+
return;
|
|
138
|
+
// Principal Insight: Handle HPE_INVALID_CONSTANT (Parse Error)
|
|
139
|
+
// If Node's HTTP parser fails, it's likely a raw stream without HTTP headers.
|
|
140
|
+
if (err.code === 'HPE_INVALID_CONSTANT' || err.message.includes('Parse Error')) {
|
|
141
|
+
logger_1.default.info('[MjpegProxy] Detected raw (head-less) MJPEG stream from source. Switching to Raw Mode.');
|
|
142
|
+
this.startRawSource();
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
logger_1.default.error(`[MjpegProxy] Connection error: ${err.message}`);
|
|
146
|
+
this.reconnect();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Raw Source Mode
|
|
152
|
+
* Directly connects via TCP socket to bypass Node's strict HTTP parser.
|
|
153
|
+
*/
|
|
154
|
+
startRawSource() {
|
|
155
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
const net = yield Promise.resolve().then(() => __importStar(require('net')));
|
|
157
|
+
const url = new url_1.URL(this.mjpegUrl);
|
|
158
|
+
const socket = net.connect({
|
|
159
|
+
host: url.hostname,
|
|
160
|
+
port: parseInt(url.port || '80'),
|
|
161
|
+
}, () => {
|
|
162
|
+
logger_1.default.info(`[MjpegProxy] Raw MJPEG socket connected to ${url.hostname}:${url.port}`);
|
|
163
|
+
this.isConnected = true;
|
|
164
|
+
this.consecutiveRetries = 0; // Reset on successful connection
|
|
165
|
+
// Some MJPEG servers expect a GET request even if they don't send valid HTTP headers back
|
|
166
|
+
socket.write(`GET ${url.pathname}${url.search} HTTP/1.1\r\nHost: ${url.hostname}\r\n\r\n`);
|
|
167
|
+
});
|
|
168
|
+
socket.on('data', (chunk) => {
|
|
169
|
+
this.broadcast(chunk);
|
|
170
|
+
});
|
|
171
|
+
socket.on('end', () => {
|
|
172
|
+
logger_1.default.warn('[MjpegProxy] Raw source ended.');
|
|
173
|
+
this.reconnect();
|
|
174
|
+
});
|
|
175
|
+
socket.on('error', (err) => {
|
|
176
|
+
if (this.isStopped)
|
|
177
|
+
return;
|
|
178
|
+
logger_1.default.error(`[MjpegProxy] Raw socket error: ${err.message}`);
|
|
179
|
+
this.reconnect();
|
|
180
|
+
});
|
|
181
|
+
this.sourceRequest = { abort: () => socket.destroy() };
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
broadcast(chunk) {
|
|
185
|
+
for (const client of this.clients) {
|
|
186
|
+
try {
|
|
187
|
+
client.write(chunk);
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
this.clients.delete(client);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
stopSource(reason = 'intentional') {
|
|
195
|
+
if (!this.sourceRequest && !this.isConnected)
|
|
196
|
+
return;
|
|
197
|
+
logger_1.default.info(`[MjpegProxy] Stopping source (${reason}): ${this.mjpegUrl}`);
|
|
198
|
+
if (this.sourceRequest) {
|
|
199
|
+
try {
|
|
200
|
+
this.sourceRequest.abort();
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
/* ignore */
|
|
204
|
+
}
|
|
205
|
+
this.sourceRequest = null;
|
|
206
|
+
}
|
|
207
|
+
this.isConnected = false;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Explicitly stop the proxy and disconnect all clients.
|
|
211
|
+
* Prevents any future reconnection attempts.
|
|
212
|
+
*/
|
|
213
|
+
stop() {
|
|
214
|
+
if (this.isStopped)
|
|
215
|
+
return;
|
|
216
|
+
this.isStopped = true;
|
|
217
|
+
if (this.reconnectionTimeout) {
|
|
218
|
+
clearTimeout(this.reconnectionTimeout);
|
|
219
|
+
this.reconnectionTimeout = null;
|
|
220
|
+
}
|
|
221
|
+
this.stopSource('proxy stopped');
|
|
222
|
+
logger_1.default.info(`[MjpegProxy] Terminating ${this.clients.size} client connections for ${this.mjpegUrl}`);
|
|
223
|
+
for (const client of this.clients) {
|
|
224
|
+
try {
|
|
225
|
+
client.end();
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
/* ignore */
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
this.clients.clear();
|
|
232
|
+
}
|
|
233
|
+
reconnect() {
|
|
234
|
+
if (this.isStopped)
|
|
235
|
+
return;
|
|
236
|
+
// If no clients, we should NOT reconnect
|
|
237
|
+
if (this.clients.size === 0) {
|
|
238
|
+
this.stopSource('no active clients');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Principal Fix: Enforce maximum retry limit with exponential backoff.
|
|
242
|
+
// Without this, the proxy retries every 500ms FOREVER after a session
|
|
243
|
+
// ends or the server shuts down, flooding logs and wasting CPU.
|
|
244
|
+
// Peek-ahead check: will the next attempt exceed the limit?
|
|
245
|
+
if (this.consecutiveRetries + 1 > UniversalMjpegProxy.MAX_RETRIES) {
|
|
246
|
+
logger_1.default.warn(`[MjpegProxy] Max retries (${UniversalMjpegProxy.MAX_RETRIES}) reached for ${this.mjpegUrl}. Giving up and disconnecting clients.`);
|
|
247
|
+
this.stop();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// Guard: if a reconnection is already scheduled, don't schedule another
|
|
251
|
+
// and don't inflate the retry counter.
|
|
252
|
+
if (this.reconnectionTimeout)
|
|
253
|
+
return;
|
|
254
|
+
// Only increment when a real reconnection will be scheduled.
|
|
255
|
+
this.consecutiveRetries++;
|
|
256
|
+
this.stopSource('reconnecting');
|
|
257
|
+
// Exponential backoff: 500ms, 1s, 2s, 4s, 8s, 10s (capped)
|
|
258
|
+
const delayMs = Math.min(UniversalMjpegProxy.BASE_RETRY_MS * Math.pow(2, this.consecutiveRetries - 1), UniversalMjpegProxy.MAX_RETRY_MS);
|
|
259
|
+
if (this.clients.size > 0) {
|
|
260
|
+
logger_1.default.info(`[MjpegProxy] Retry ${this.consecutiveRetries}/${UniversalMjpegProxy.MAX_RETRIES} in ${delayMs}ms...`);
|
|
261
|
+
this.reconnectionTimeout = setTimeout(() => {
|
|
262
|
+
this.reconnectionTimeout = null;
|
|
263
|
+
if (this.clients.size > 0 && !this.isStopped) {
|
|
264
|
+
this.startSource();
|
|
265
|
+
}
|
|
266
|
+
}, delayMs);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
exports.UniversalMjpegProxy = UniversalMjpegProxy;
|
|
271
|
+
UniversalMjpegProxy.MAX_RETRIES = 10;
|
|
272
|
+
UniversalMjpegProxy.BASE_RETRY_MS = 500;
|
|
273
|
+
UniversalMjpegProxy.MAX_RETRY_MS = 10000;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.redactSecrets = void 0;
|
|
16
|
+
exports.asyncForEach = asyncForEach;
|
|
17
|
+
exports.spinWith = spinWith;
|
|
18
|
+
exports.isMac = isMac;
|
|
19
|
+
exports.cachePath = cachePath;
|
|
20
|
+
exports.isWindows = isWindows;
|
|
21
|
+
exports.checkIfPathIsAbsolute = checkIfPathIsAbsolute;
|
|
22
|
+
exports.getFreePort = getFreePort;
|
|
23
|
+
exports.nodeUrl = nodeUrl;
|
|
24
|
+
exports.isPortBusy = isPortBusy;
|
|
25
|
+
exports.hasHubArgument = hasHubArgument;
|
|
26
|
+
exports.hasCloudArgument = hasCloudArgument;
|
|
27
|
+
exports.stripAppiumPrefixes = stripAppiumPrefixes;
|
|
28
|
+
exports.isXenonRunning = isXenonRunning;
|
|
29
|
+
exports.isAppiumRunningAt = isAppiumRunningAt;
|
|
30
|
+
exports.safeParseJson = safeParseJson;
|
|
31
|
+
exports.takeScreenshot = takeScreenshot;
|
|
32
|
+
/* eslint-disable no-prototype-builtins */
|
|
33
|
+
const os_1 = __importDefault(require("os"));
|
|
34
|
+
const path_1 = __importDefault(require("path"));
|
|
35
|
+
const tcp_port_used_1 = __importDefault(require("tcp-port-used"));
|
|
36
|
+
const get_port_1 = __importDefault(require("get-port"));
|
|
37
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
38
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
39
|
+
const Cloud_1 = __importDefault(require("../enums/Cloud"));
|
|
40
|
+
const normalize_url_1 = __importDefault(require("normalize-url"));
|
|
41
|
+
const ora_1 = __importDefault(require("ora"));
|
|
42
|
+
const async_wait_until_1 = __importDefault(require("async-wait-until"));
|
|
43
|
+
const InternalHttpClient_1 = require("../InternalHttpClient");
|
|
44
|
+
const logger_2 = require("../logger");
|
|
45
|
+
Object.defineProperty(exports, "redactSecrets", { enumerable: true, get: function () { return logger_2.redactSecrets; } });
|
|
46
|
+
const APPIUM_VENDOR_PREFIX = 'appium:';
|
|
47
|
+
const XENON_PREFIXES = ['xe:'];
|
|
48
|
+
function asyncForEach(array, callback) {
|
|
49
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
+
for (let index = 0; index < array.length; index++) {
|
|
51
|
+
yield callback(array[index], index, array);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
56
|
+
function spinWith(msg_1, fn_1) {
|
|
57
|
+
return __awaiter(this, arguments, void 0, function* (msg, fn, callback = (_msg) => { }) {
|
|
58
|
+
const spinner = (0, ora_1.default)(msg).start();
|
|
59
|
+
yield (0, async_wait_until_1.default)(() => __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
try {
|
|
61
|
+
const res = yield fn();
|
|
62
|
+
spinner.succeed();
|
|
63
|
+
return res;
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
spinner.fail();
|
|
67
|
+
if (callback)
|
|
68
|
+
callback(msg);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}), {
|
|
72
|
+
intervalBetweenAttempts: 2000,
|
|
73
|
+
timeout: 60 * 1000,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function isMac() {
|
|
78
|
+
return os_1.default.type() === 'Darwin';
|
|
79
|
+
}
|
|
80
|
+
function cachePath(folder) {
|
|
81
|
+
return path_1.default.join(os_1.default.homedir(), '.cache', 'xenon', folder);
|
|
82
|
+
}
|
|
83
|
+
function isWindows() {
|
|
84
|
+
return os_1.default.type() === 'win32';
|
|
85
|
+
}
|
|
86
|
+
function checkIfPathIsAbsolute(configPath) {
|
|
87
|
+
return path_1.default.isAbsolute(configPath);
|
|
88
|
+
}
|
|
89
|
+
function getFreePort() {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
return yield (0, get_port_1.default)();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function nodeUrl(device, basePath = '') {
|
|
95
|
+
const host = (0, normalize_url_1.default)(device.host, { removeTrailingSlash: false });
|
|
96
|
+
if (device.cloud) {
|
|
97
|
+
if (device.cloud.toLowerCase() === Cloud_1.default.PCLOUDY) {
|
|
98
|
+
return `${host}/wd/hub`;
|
|
99
|
+
}
|
|
100
|
+
else if (device.cloud.toLowerCase() === Cloud_1.default.HEADSPIN) {
|
|
101
|
+
return `${host}`;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
return `https://${process.env.CLOUD_USERNAME}:${process.env.CLOUD_KEY}@${new URL(device.host).host}/wd/hub`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// hardcoded the `/wd/hub` for now. This can be fetch from serverArgs.basePath
|
|
108
|
+
return `${host}${basePath || ''}`;
|
|
109
|
+
}
|
|
110
|
+
function isPortBusy(port) {
|
|
111
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
112
|
+
try {
|
|
113
|
+
if (!port) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return yield tcp_port_used_1.default.check(port);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function hasHubArgument(cliArgs) {
|
|
124
|
+
return lodash_1.default.has(cliArgs, 'plugin["xenon"].hub');
|
|
125
|
+
}
|
|
126
|
+
function hasCloudArgument(cliArgs) {
|
|
127
|
+
return lodash_1.default.has(cliArgs, 'plugin["xenon"].cloud');
|
|
128
|
+
}
|
|
129
|
+
// Standard, non-prefixed capabilities (see https://www.w3.org/TR/webdriver/#dfn-table-of-standard-capabilities)
|
|
130
|
+
const STANDARD_CAPS = [
|
|
131
|
+
'browserName',
|
|
132
|
+
'browserVersion',
|
|
133
|
+
'platformName',
|
|
134
|
+
'acceptInsecureCerts',
|
|
135
|
+
'pageLoadStrategy',
|
|
136
|
+
'proxy',
|
|
137
|
+
'setWindowRect',
|
|
138
|
+
'timeouts',
|
|
139
|
+
'unhandledPromptBehavior',
|
|
140
|
+
];
|
|
141
|
+
function isStandardCap(cap) {
|
|
142
|
+
return !!lodash_1.default.find(STANDARD_CAPS, (standardCap) => standardCap.toLowerCase() === `${cap}`.toLowerCase());
|
|
143
|
+
}
|
|
144
|
+
// If the 'appium:' or Xenon prefixes were provided, strip them out (W3C Extension Capabilities)
|
|
145
|
+
// (NOTE: Method is destructive and mutates contents of caps)
|
|
146
|
+
function stripAppiumPrefixes(caps) {
|
|
147
|
+
const allPrefixes = [APPIUM_VENDOR_PREFIX, ...XENON_PREFIXES];
|
|
148
|
+
const keys = lodash_1.default.keys(caps);
|
|
149
|
+
const prefixedCaps = keys.filter((cap) => allPrefixes.some((prefix) => cap.startsWith(prefix)));
|
|
150
|
+
const nonPrefixedCaps = lodash_1.default.difference(keys, prefixedCaps);
|
|
151
|
+
// initialize this with the k/v pairs of the non-prefixed caps
|
|
152
|
+
const strippedCaps = /** @type {import('@appium/types').Capabilities<C>} */ lodash_1.default.pick(caps, nonPrefixedCaps);
|
|
153
|
+
const badPrefixedCaps = [];
|
|
154
|
+
// Strip prefixes
|
|
155
|
+
for (const prefixedCap of prefixedCaps) {
|
|
156
|
+
const activePrefix = allPrefixes.find((p) => prefixedCap.startsWith(p));
|
|
157
|
+
const strippedCapName = prefixedCap.substring(activePrefix.length);
|
|
158
|
+
// If it's standard capability that was prefixed, add it to an array of incorrectly prefixed capabilities
|
|
159
|
+
if (activePrefix === APPIUM_VENDOR_PREFIX && isStandardCap(strippedCapName)) {
|
|
160
|
+
badPrefixedCaps.push(strippedCapName);
|
|
161
|
+
if (lodash_1.default.isNil(strippedCaps[strippedCapName])) {
|
|
162
|
+
strippedCaps[strippedCapName] = caps[prefixedCap];
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
logger_1.default.warn(`Ignoring capability '${prefixedCap}=${caps[prefixedCap]}' and ` +
|
|
166
|
+
`using capability '${strippedCapName}=${strippedCaps[strippedCapName]}'`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
strippedCaps[strippedCapName] = caps[prefixedCap];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// If we found standard caps that were incorrectly prefixed, throw an exception (e.g.: don't accept 'appium:platformName', only accept just 'platformName')
|
|
174
|
+
if (badPrefixedCaps.length > 0) {
|
|
175
|
+
logger_1.default.warn(`The capabilities ${JSON.stringify(badPrefixedCaps)} are standard capabilities and do not require "appium:" prefix`);
|
|
176
|
+
}
|
|
177
|
+
return strippedCaps;
|
|
178
|
+
}
|
|
179
|
+
function isXenonRunning(host, tlsRejectUnauthorized) {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
181
|
+
try {
|
|
182
|
+
const client = InternalHttpClient_1.InternalHttpClient.getClient(tlsRejectUnauthorized);
|
|
183
|
+
yield client.get(`${host}/xenon/api/status`);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
logger_1.default.info(`Xenon is not running at ${host}. Error: ${error}`);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function isAppiumRunningAt(url, tlsRejectUnauthorized) {
|
|
193
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
194
|
+
try {
|
|
195
|
+
const client = InternalHttpClient_1.InternalHttpClient.getClient(tlsRejectUnauthorized);
|
|
196
|
+
yield client.get(`${url}/status`);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
logger_1.default.info(`Appium is not running at ${url}. Error: ${error}`);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function safeParseJson(jsonString) {
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(jsonString);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
return jsonString;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function takeScreenshot(driver) {
|
|
214
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
215
|
+
try {
|
|
216
|
+
// Use driver's native getScreenshot method
|
|
217
|
+
const screenshot = yield driver.getScreenshot();
|
|
218
|
+
if (screenshot && typeof screenshot === 'string') {
|
|
219
|
+
return screenshot;
|
|
220
|
+
}
|
|
221
|
+
logger_1.default.warn('Screenshot did not return expected base64 string');
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
logger_1.default.error(`Failed to take screenshot: ${error.message}`);
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
package/lib/src/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
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.XenonPlugin = void 0;
|
|
49
|
+
require("reflect-metadata");
|
|
50
|
+
const plugin_1 = require("./plugin");
|
|
51
|
+
Object.defineProperty(exports, "XenonPlugin", { enumerable: true, get: function () { return plugin_1.XenonPlugin; } });
|
|
52
|
+
const ffmpeg_1 = require("@ffmpeg-installer/ffmpeg");
|
|
53
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
54
|
+
const typedi_1 = require("typedi");
|
|
55
|
+
// Add FFMPEG to path for appium to record video of the session
|
|
56
|
+
process.env.PATH = process.env.PATH + ':' + ffmpeg_1.path.replace(/ffmpeg$/g, '');
|
|
57
|
+
/**
|
|
58
|
+
* Global Lifecycle Orchestrator
|
|
59
|
+
* Ensures that whenever the Appium process is terminated (SIGINT, SIGTERM),
|
|
60
|
+
* all spawned sidecar processes (go-ios, iproxy, tunnels) are killed.
|
|
61
|
+
*/
|
|
62
|
+
const cleanup = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
|
+
logger_1.default.info('🚀 [Xenon] Shutdown signal received. Performing graceful cleanup...');
|
|
64
|
+
try {
|
|
65
|
+
const { default: IOSStreamService } = yield Promise.resolve().then(() => __importStar(require('./device-managers/ios/IOSStreamService')));
|
|
66
|
+
const { default: AndroidStreamService } = yield Promise.resolve().then(() => __importStar(require('./device-managers/android/AndroidStreamService')));
|
|
67
|
+
const { stopAllTimers } = yield Promise.resolve().then(() => __importStar(require('./device-utils')));
|
|
68
|
+
const { VideoPipelineService } = yield Promise.resolve().then(() => __importStar(require('./services/VideoPipelineService')));
|
|
69
|
+
// Shutdown all independent MJPEG streams, tunnels, and video recordings
|
|
70
|
+
stopAllTimers();
|
|
71
|
+
yield typedi_1.Container.get(IOSStreamService).cleanup();
|
|
72
|
+
yield typedi_1.Container.get(AndroidStreamService).cleanup();
|
|
73
|
+
yield typedi_1.Container.get(VideoPipelineService).cleanup();
|
|
74
|
+
logger_1.default.info('✅ [Xenon] Infrastructure components sanitized. Safe to exit.');
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
logger_1.default.error(`❌ [Xenon] Cleanup failed: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
// Principal Delay: Wait 2 seconds before hard-exiting to allow
|
|
81
|
+
// other async handlers (like hub unregistration) to finish.
|
|
82
|
+
setTimeout(() => process.exit(0), 2000);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
process.on('SIGINT', cleanup);
|
|
86
|
+
process.on('SIGTERM', cleanup);
|
|
87
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
88
|
+
logger_1.default.error('❌ [Xenon] Unhandled Rejection at:', promise, 'reason:', reason);
|
|
89
|
+
});
|
|
90
|
+
process.on('uncaughtException', (err) => {
|
|
91
|
+
logger_1.default.error('❌ [Xenon] Uncaught Exception:', err);
|
|
92
|
+
// Give logger time to flush before exiting
|
|
93
|
+
setTimeout(() => process.exit(1), 1000);
|
|
94
|
+
});
|
|
95
|
+
exports.default = plugin_1.XenonPlugin;
|