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