@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,287 @@
|
|
|
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.RemoteSession = void 0;
|
|
16
|
+
const axios_1 = __importDefault(require("axios"));
|
|
17
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
18
|
+
const SessionType_1 = __importDefault(require("../enums/SessionType"));
|
|
19
|
+
const XenonSession_1 = require("./XenonSession");
|
|
20
|
+
class RemoteSession extends XenonSession_1.XenonSession {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super(options);
|
|
23
|
+
this.isVideoAvailable = false;
|
|
24
|
+
this.baseUrl = options.baseUrl;
|
|
25
|
+
}
|
|
26
|
+
isVideoRecordingInProgress() {
|
|
27
|
+
return this.isVideoAvailable;
|
|
28
|
+
}
|
|
29
|
+
getType() {
|
|
30
|
+
return SessionType_1.default.REMOTE;
|
|
31
|
+
}
|
|
32
|
+
getScreenShot() {
|
|
33
|
+
return (0, axios_1.default)({
|
|
34
|
+
method: 'get',
|
|
35
|
+
url: `${this.baseUrl}/session/${this.sessionId}/screenshot`,
|
|
36
|
+
}).then((response) => (response.data ? response.data.value : ''));
|
|
37
|
+
}
|
|
38
|
+
stopVideoRecording(_driver) {
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
41
|
+
logger_1.default.info(`[RemoteSession] stopVideoRecording called for session ${this.sessionId}. isVideoAvailable: ${this.isVideoAvailable}`);
|
|
42
|
+
try {
|
|
43
|
+
const response = yield (0, axios_1.default)({
|
|
44
|
+
method: 'post',
|
|
45
|
+
url: `${this.baseUrl}/session/${this.sessionId}/appium/stop_recording_screen`,
|
|
46
|
+
data: {},
|
|
47
|
+
});
|
|
48
|
+
const dataLen = ((_b = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.length) || 0;
|
|
49
|
+
logger_1.default.info(`[RemoteSession] stopVideoRecording response status: ${response.status}, data length: ${dataLen}`);
|
|
50
|
+
return response.status === 200 && ((_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.value) ? (_d = response === null || response === void 0 ? void 0 : response.data) === null || _d === void 0 ? void 0 : _d.value : '';
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
logger_1.default.warn(`[RemoteSession] stopVideoRecording error: ${err.message}, trying anyway to retrieve video...`);
|
|
54
|
+
// Even if there's an error, try to get the video data
|
|
55
|
+
try {
|
|
56
|
+
const retryResponse = yield (0, axios_1.default)({
|
|
57
|
+
method: 'post',
|
|
58
|
+
url: `${this.baseUrl}/session/${this.sessionId}/appium/stop_recording_screen`,
|
|
59
|
+
data: {},
|
|
60
|
+
});
|
|
61
|
+
const retryDataLen = ((_f = (_e = retryResponse === null || retryResponse === void 0 ? void 0 : retryResponse.data) === null || _e === void 0 ? void 0 : _e.value) === null || _f === void 0 ? void 0 : _f.length) || 0;
|
|
62
|
+
logger_1.default.info(`[RemoteSession] stopVideoRecording retry succeeded, data length: ${retryDataLen}`);
|
|
63
|
+
return ((_g = retryResponse === null || retryResponse === void 0 ? void 0 : retryResponse.data) === null || _g === void 0 ? void 0 : _g.value) || '';
|
|
64
|
+
}
|
|
65
|
+
catch (retryErr) {
|
|
66
|
+
logger_1.default.error(`[RemoteSession] stopVideoRecording retry also failed: ${retryErr.message}`);
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
stopPerformanceRecording() {
|
|
73
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
logger_1.default.info(`[RemoteSession] stopPerformanceRecording called for session ${this.sessionId}`);
|
|
76
|
+
try {
|
|
77
|
+
const response = yield (0, axios_1.default)({
|
|
78
|
+
method: 'post',
|
|
79
|
+
url: `${this.baseUrl}/session/${this.sessionId}/execute/sync`,
|
|
80
|
+
data: {
|
|
81
|
+
script: 'mobile: stopPerfRecord',
|
|
82
|
+
args: [
|
|
83
|
+
{
|
|
84
|
+
profileName: 'Time Profiler',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return response.status === 200 && ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.value) ? (_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.value : null;
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
logger_1.default.warn(`[RemoteSession] stopPerformanceRecording failed: ${err.message}`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
startPerformanceRecording() {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
logger_1.default.info(`[RemoteSession] startPerformanceRecording called for session ${this.sessionId}`);
|
|
100
|
+
try {
|
|
101
|
+
yield (0, axios_1.default)({
|
|
102
|
+
method: 'post',
|
|
103
|
+
url: `${this.baseUrl}/session/${this.sessionId}/execute/sync`,
|
|
104
|
+
data: {
|
|
105
|
+
script: 'mobile: startPerfRecord',
|
|
106
|
+
args: [
|
|
107
|
+
{
|
|
108
|
+
profileName: 'Time Profiler',
|
|
109
|
+
timeout: 1800000, // 30 mins
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
logger_1.default.warn(`[RemoteSession] startPerformanceRecording failed: ${err.message}`);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
startVideoRecording(options, _driver) {
|
|
121
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
const device = this.getDevice();
|
|
123
|
+
let resolution = (options === null || options === void 0 ? void 0 : options.resolution) ? options.resolution.replace('x', ':') : undefined;
|
|
124
|
+
let size = (options === null || options === void 0 ? void 0 : options.resolution) ? options.resolution.replace(':', 'x') : undefined;
|
|
125
|
+
// Principal Intelligence: Auto-detect orientation based on device dimensions
|
|
126
|
+
// to prevent squashed/stretched videos.
|
|
127
|
+
if (!resolution && device.screenWidth && device.screenHeight) {
|
|
128
|
+
const w = parseInt(device.screenWidth);
|
|
129
|
+
const h = parseInt(device.screenHeight);
|
|
130
|
+
logger_1.default.info(`[RemoteSession] Auto-detected device dimensions: ${w}x${h} for session ${this.sessionId}`);
|
|
131
|
+
if (h > w) {
|
|
132
|
+
// Portrait device: Use vertical 720p equivalent
|
|
133
|
+
resolution = '720:1280';
|
|
134
|
+
size = '720x1280';
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Landscape device: Use standard 720p
|
|
138
|
+
resolution = '1280:720';
|
|
139
|
+
size = '1280x720';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (!resolution) {
|
|
143
|
+
// Fallback: Default to portrait 720p if dimensions unknown, as most mobile tests are portrait
|
|
144
|
+
resolution = '720:1280';
|
|
145
|
+
size = '720x1280';
|
|
146
|
+
}
|
|
147
|
+
logger_1.default.info(`[RemoteSession] Starting recording with resolution: ${resolution}, size: ${size}`);
|
|
148
|
+
return (0, axios_1.default)({
|
|
149
|
+
method: 'post',
|
|
150
|
+
url: `${this.baseUrl}/session/${this.sessionId}/appium/start_recording_screen`,
|
|
151
|
+
data: {
|
|
152
|
+
options: {
|
|
153
|
+
videoType: 'libx264',
|
|
154
|
+
videoFps: 10,
|
|
155
|
+
/* Force video scale to ensure width/height are divisible by 2 (ffmpeg requirement) */
|
|
156
|
+
videoScale: resolution,
|
|
157
|
+
videoSize: size,
|
|
158
|
+
timeLimit: 1800, // 30 min
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
.then((response) => {
|
|
163
|
+
// Set flag to true if response is successful (status 200 or 2xx)
|
|
164
|
+
this.isVideoAvailable = response.status >= 200 && response.status < 300;
|
|
165
|
+
const status = response.status;
|
|
166
|
+
logger_1.default.info(`[RemoteSession] startVideoRecording response status: ${status}, isVideoAvailable: ${this.isVideoAvailable}`);
|
|
167
|
+
})
|
|
168
|
+
.catch((error) => {
|
|
169
|
+
logger_1.default.error('[RemoteSession] startVideoRecording error:', error.message);
|
|
170
|
+
this.isVideoAvailable = false;
|
|
171
|
+
throw error;
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
getLiveVideoUrl() {
|
|
176
|
+
const url = new URL(this.baseUrl);
|
|
177
|
+
const capabilities = this.getCapabilities();
|
|
178
|
+
if (capabilities['mjpegServerPort'] && !isNaN(capabilities['mjpegServerPort'])) {
|
|
179
|
+
return `${url.origin}/xenon/api/session/${this.sessionId}/live_video`;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
checkHealth() {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
var _a, _b, _c, _d;
|
|
188
|
+
if (!this.sessionId) {
|
|
189
|
+
return {
|
|
190
|
+
isHealthy: false,
|
|
191
|
+
errorType: XenonSession_1.HealthErrorType.NONE,
|
|
192
|
+
message: 'No session ID assigned',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// Identify the root Appium URL (strip /wd-internal if present)
|
|
196
|
+
const appiumUrl = this.baseUrl.replace(/\/wd-internal$/, '');
|
|
197
|
+
try {
|
|
198
|
+
// 1️⃣ Safe, read-only, lightweight probe: GET /session/{id}/timeouts
|
|
199
|
+
// This is a W3C standard command that all Appium drivers should register.
|
|
200
|
+
const response = yield (0, axios_1.default)({
|
|
201
|
+
method: 'get',
|
|
202
|
+
url: `${this.baseUrl}/session/${this.sessionId}/timeouts`,
|
|
203
|
+
timeout: 5000,
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
isHealthy: response.status >= 200 && response.status < 300,
|
|
207
|
+
errorType: XenonSession_1.HealthErrorType.NONE,
|
|
208
|
+
statusCode: response.status,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
const status = (_a = err.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
213
|
+
const message = err.message || 'Unknown error';
|
|
214
|
+
const errorData = ((_c = (_b = err.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.value) || ((_d = err.response) === null || _d === void 0 ? void 0 : _d.data);
|
|
215
|
+
const errorJson = JSON.stringify(errorData || {}).toLowerCase();
|
|
216
|
+
// 2️⃣ Failure Classification & Fallback Logic
|
|
217
|
+
if (status === 404) {
|
|
218
|
+
// Distinguish between "Session Not Found" and "Command Not Found"
|
|
219
|
+
// Appium 2 returns "No route found" or "unknown command" for unsupported endpoints.
|
|
220
|
+
const isUnsupported = errorJson.includes('unknown command') || errorJson.includes('no route found');
|
|
221
|
+
const isInvalidSession = errorJson.includes('invalid session id');
|
|
222
|
+
if (isUnsupported) {
|
|
223
|
+
logger_1.default.info('[RemoteSession] Probe endpoint unsupported on this driver, but driver is responsive. Classifying as HEALTHY.');
|
|
224
|
+
return {
|
|
225
|
+
isHealthy: true,
|
|
226
|
+
errorType: XenonSession_1.HealthErrorType.UNSUPPORTED_ENDPOINT,
|
|
227
|
+
message: 'Probe endpoint unsupported but driver is alive',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (isInvalidSession) {
|
|
231
|
+
logger_1.default.error(`[RemoteSession] Session ${this.sessionId} definitively GONE (Invalid Session ID).`);
|
|
232
|
+
return {
|
|
233
|
+
isHealthy: false,
|
|
234
|
+
errorType: XenonSession_1.HealthErrorType.SESSION_NOT_FOUND,
|
|
235
|
+
message: 'Invalid Session ID',
|
|
236
|
+
statusCode: 404,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
logger_1.default.warn(`[RemoteSession] Session ${this.sessionId} probe returned 404. Performing server status fallback...`);
|
|
240
|
+
// Fallback: Verify Appium server is alive
|
|
241
|
+
try {
|
|
242
|
+
const statusRes = yield axios_1.default.get(`${appiumUrl}/status`, { timeout: 3000 });
|
|
243
|
+
if (statusRes.status === 200) {
|
|
244
|
+
// If server is alive but we got a raw 404 with no specific error,
|
|
245
|
+
// it's likely the session is gone in Appium 2 (where /sessions list check is unsupported).
|
|
246
|
+
return {
|
|
247
|
+
isHealthy: false,
|
|
248
|
+
errorType: XenonSession_1.HealthErrorType.SESSION_NOT_FOUND,
|
|
249
|
+
message: 'Session likely gone (Server alive but 404 on session path)',
|
|
250
|
+
statusCode: 404,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (serverErr) {
|
|
255
|
+
logger_1.default.error(`[RemoteSession] Appium server REACHABILITY FAILED: ${serverErr.message}`);
|
|
256
|
+
return {
|
|
257
|
+
isHealthy: false,
|
|
258
|
+
errorType: XenonSession_1.HealthErrorType.SERVER_UNREACHABLE,
|
|
259
|
+
message: `Server unreachable: ${serverErr.message}`,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (status >= 500) {
|
|
264
|
+
return {
|
|
265
|
+
isHealthy: false,
|
|
266
|
+
errorType: XenonSession_1.HealthErrorType.DRIVER_ERROR,
|
|
267
|
+
message: `Driver internal error: ${message}`,
|
|
268
|
+
statusCode: status,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
|
|
272
|
+
return {
|
|
273
|
+
isHealthy: false,
|
|
274
|
+
errorType: XenonSession_1.HealthErrorType.SERVER_UNREACHABLE,
|
|
275
|
+
message: `Connection failed: ${err.code}`,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
isHealthy: false,
|
|
280
|
+
errorType: XenonSession_1.HealthErrorType.TIMEOUT,
|
|
281
|
+
message: `Probe timed out: ${message}`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
exports.RemoteSession = RemoteSession;
|
|
@@ -0,0 +1,238 @@
|
|
|
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.SESSION_MANAGER = exports.SessionManager = void 0;
|
|
22
|
+
const typedi_1 = require("typedi");
|
|
23
|
+
const prisma_1 = require("../prisma");
|
|
24
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
25
|
+
const RemoteSession_1 = require("./RemoteSession");
|
|
26
|
+
const CloudSession_1 = require("./CloudSession");
|
|
27
|
+
const device_store_1 = require("../data-service/device-store");
|
|
28
|
+
const helpers_1 = require("../helpers");
|
|
29
|
+
const XenonCapabilityManager_1 = require("../XenonCapabilityManager");
|
|
30
|
+
/**
|
|
31
|
+
* SessionManager with persistence and recovery capabilities.
|
|
32
|
+
*
|
|
33
|
+
* Key Design Decisions:
|
|
34
|
+
* 1. LocalSessions CANNOT be recovered (Appium driver dies with the hub).
|
|
35
|
+
* On recovery, orphaned local sessions are marked as failed.
|
|
36
|
+
* 2. RemoteSessions and CloudSessions CAN be recovered because they only
|
|
37
|
+
* need sessionId and baseUrl to continue proxying commands.
|
|
38
|
+
* 3. Session state is persisted to SQLite via Prisma for durability.
|
|
39
|
+
*/
|
|
40
|
+
let SessionManager = class SessionManager {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.sessionMap = new Map();
|
|
43
|
+
this.log = logger_1.default.scope('SessionManager');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Add a session to the in-memory map.
|
|
47
|
+
* Note: Session is already persisted to DB via event-manager.onSessionCreated
|
|
48
|
+
*/
|
|
49
|
+
addSession(sessionId, session) {
|
|
50
|
+
this.sessionMap.set(sessionId, session);
|
|
51
|
+
this.log.debug(`Added session ${sessionId} to memory (total: ${this.sessionMap.size})`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Remove a session from the in-memory map.
|
|
55
|
+
*/
|
|
56
|
+
removeSession(sessionId) {
|
|
57
|
+
this.sessionMap.delete(sessionId);
|
|
58
|
+
this.log.debug(`Removed session ${sessionId} from memory (total: ${this.sessionMap.size})`);
|
|
59
|
+
}
|
|
60
|
+
isValidSession(sessionId) {
|
|
61
|
+
return this.sessionMap.has(sessionId);
|
|
62
|
+
}
|
|
63
|
+
getSession(sessionId) {
|
|
64
|
+
return this.sessionMap.get(sessionId);
|
|
65
|
+
}
|
|
66
|
+
getAllSessions() {
|
|
67
|
+
return Array.from(this.sessionMap.values());
|
|
68
|
+
}
|
|
69
|
+
getSessionCount() {
|
|
70
|
+
return this.sessionMap.size;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Recover active sessions from the database after hub restart.
|
|
74
|
+
*
|
|
75
|
+
* @param currentNodeId - The ID of the current hub node
|
|
76
|
+
* @param nodeBasePath - The base path for WebDriver URLs
|
|
77
|
+
*
|
|
78
|
+
* Strategy:
|
|
79
|
+
* - Sessions on THIS node (LocalSessions) are marked as failed because
|
|
80
|
+
* the Appium driver is gone.
|
|
81
|
+
* - Sessions on REMOTE nodes are recovered as RemoteSessions.
|
|
82
|
+
* - Sessions on CLOUD providers are recovered as CloudSessions.
|
|
83
|
+
*/
|
|
84
|
+
recoverActiveSessions(currentNodeId, nodeBasePath) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
this.log.info('🔄 Attempting to recover active sessions from database...');
|
|
87
|
+
try {
|
|
88
|
+
// Find all sessions that are still "running" (not ended)
|
|
89
|
+
const activeSessions = yield prisma_1.prisma.session.findMany({
|
|
90
|
+
where: {
|
|
91
|
+
status: 'running',
|
|
92
|
+
endTime: null,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
if (activeSessions.length === 0) {
|
|
96
|
+
this.log.info('✅ No active sessions to recover.');
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
this.log.info(`📋 Found ${activeSessions.length} active sessions in database.`);
|
|
100
|
+
let recoveredCount = 0;
|
|
101
|
+
let failedCount = 0;
|
|
102
|
+
for (const dbSession of activeSessions) {
|
|
103
|
+
try {
|
|
104
|
+
// Get the device associated with this session
|
|
105
|
+
const device = yield device_store_1.DeviceStoreFactory.getStore().findDevice({
|
|
106
|
+
udid: dbSession.device_udid,
|
|
107
|
+
});
|
|
108
|
+
if (!device) {
|
|
109
|
+
this.log.warn(`⚠️ Session ${dbSession.id}: Device ${dbSession.device_udid} not found. Marking as failed.`);
|
|
110
|
+
yield this.markSessionAsFailed(dbSession.id, 'Device not found after hub restart');
|
|
111
|
+
failedCount++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// Check if this session was on THE CURRENT NODE (LocalSession)
|
|
115
|
+
const isLocalSession = dbSession.node_id === currentNodeId || device.nodeId === currentNodeId;
|
|
116
|
+
if (isLocalSession) {
|
|
117
|
+
// LocalSessions cannot be recovered - the Appium driver is gone
|
|
118
|
+
this.log.warn(`⏹️ Session ${dbSession.id}: Was a LocalSession on this node. Marking as failed (driver lost).`);
|
|
119
|
+
yield this.markSessionAsFailed(dbSession.id, 'Hub restarted - local Appium driver session was lost');
|
|
120
|
+
// Also unblock the device
|
|
121
|
+
yield device_store_1.DeviceStoreFactory.getStore().updateDevice(device.udid, device.host, {
|
|
122
|
+
busy: false,
|
|
123
|
+
session_id: undefined,
|
|
124
|
+
});
|
|
125
|
+
failedCount++;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// Parse session capabilities from JSON
|
|
129
|
+
let sessionResponse = {};
|
|
130
|
+
try {
|
|
131
|
+
sessionResponse = JSON.parse(dbSession.session_capabilities || '{}');
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
this.log.warn(`Session ${dbSession.id}: Could not parse session_capabilities`);
|
|
135
|
+
}
|
|
136
|
+
let desiredCaps = {};
|
|
137
|
+
try {
|
|
138
|
+
desiredCaps = JSON.parse(dbSession.desired_capabilities || '{}');
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
this.log.warn(`Session ${dbSession.id}: Could not parse desired_capabilities`);
|
|
142
|
+
}
|
|
143
|
+
const xenonCapabilities = (0, XenonCapabilityManager_1.getXenonCapabilities)({
|
|
144
|
+
alwaysMatch: desiredCaps,
|
|
145
|
+
firstMatch: [{}],
|
|
146
|
+
});
|
|
147
|
+
const baseUrl = (0, helpers_1.nodeUrl)(device, nodeBasePath);
|
|
148
|
+
// Create the appropriate session type
|
|
149
|
+
let recoveredSession;
|
|
150
|
+
if (device.cloud) {
|
|
151
|
+
// CloudSession
|
|
152
|
+
recoveredSession = new CloudSession_1.CloudSession({
|
|
153
|
+
sessionId: dbSession.id,
|
|
154
|
+
device: device,
|
|
155
|
+
sessionResponse: sessionResponse,
|
|
156
|
+
xenonOption: xenonCapabilities,
|
|
157
|
+
baseUrl: baseUrl,
|
|
158
|
+
});
|
|
159
|
+
this.log.info(`☁️ Recovered CloudSession ${dbSession.id} on ${device.cloud}`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// RemoteSession
|
|
163
|
+
recoveredSession = new RemoteSession_1.RemoteSession({
|
|
164
|
+
sessionId: dbSession.id,
|
|
165
|
+
device: device,
|
|
166
|
+
sessionResponse: sessionResponse,
|
|
167
|
+
xenonOption: xenonCapabilities,
|
|
168
|
+
baseUrl: baseUrl,
|
|
169
|
+
});
|
|
170
|
+
this.log.info(`🌐 Recovered RemoteSession ${dbSession.id} on node ${device.nodeId}`);
|
|
171
|
+
}
|
|
172
|
+
// Add to in-memory map
|
|
173
|
+
this.addSession(dbSession.id, recoveredSession);
|
|
174
|
+
recoveredCount++;
|
|
175
|
+
}
|
|
176
|
+
catch (sessionErr) {
|
|
177
|
+
this.log.error(`❌ Failed to recover session ${dbSession.id}: ${sessionErr.message}`);
|
|
178
|
+
yield this.markSessionAsFailed(dbSession.id, `Recovery failed: ${sessionErr.message}`);
|
|
179
|
+
failedCount++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
this.log.info(`✅ Session recovery complete. Recovered: ${recoveredCount}, Failed: ${failedCount}`);
|
|
183
|
+
return recoveredCount;
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
this.log.error(`❌ Session recovery error: ${err.message}`);
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Mark a session as failed in the database
|
|
193
|
+
*/
|
|
194
|
+
markSessionAsFailed(sessionId, reason) {
|
|
195
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
196
|
+
try {
|
|
197
|
+
yield prisma_1.prisma.session.update({
|
|
198
|
+
where: { id: sessionId },
|
|
199
|
+
data: {
|
|
200
|
+
status: 'failed',
|
|
201
|
+
endTime: new Date(),
|
|
202
|
+
failure_reason: reason,
|
|
203
|
+
failure_category: 'HUB_RESTART',
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
this.log.error(`Failed to mark session ${sessionId} as failed: ${err.message}`);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get statistics about current sessions
|
|
214
|
+
*/
|
|
215
|
+
getStats() {
|
|
216
|
+
const byType = {
|
|
217
|
+
local: 0,
|
|
218
|
+
remote: 0,
|
|
219
|
+
cloud: 0,
|
|
220
|
+
};
|
|
221
|
+
for (const session of Array.from(this.sessionMap.values())) {
|
|
222
|
+
const type = session.getType().toLowerCase();
|
|
223
|
+
byType[type] = (byType[type] || 0) + 1;
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
total: this.sessionMap.size,
|
|
227
|
+
byType,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
exports.SessionManager = SessionManager;
|
|
232
|
+
exports.SessionManager = SessionManager = __decorate([
|
|
233
|
+
(0, typedi_1.Service)()
|
|
234
|
+
], SessionManager);
|
|
235
|
+
// Export singleton for backward compatibility
|
|
236
|
+
// New code should use Container.get(SessionManager)
|
|
237
|
+
const typedi_2 = require("typedi");
|
|
238
|
+
exports.SESSION_MANAGER = typedi_2.Container.get(SessionManager);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.XenonSession = exports.HealthErrorType = exports.SessionHealthState = void 0;
|
|
4
|
+
var SessionHealthState;
|
|
5
|
+
(function (SessionHealthState) {
|
|
6
|
+
SessionHealthState["HEALTHY"] = "HEALTHY";
|
|
7
|
+
SessionHealthState["DEGRADED"] = "DEGRADED";
|
|
8
|
+
SessionHealthState["SUSPECT"] = "SUSPECT";
|
|
9
|
+
SessionHealthState["DEAD"] = "DEAD";
|
|
10
|
+
})(SessionHealthState || (exports.SessionHealthState = SessionHealthState = {}));
|
|
11
|
+
var HealthErrorType;
|
|
12
|
+
(function (HealthErrorType) {
|
|
13
|
+
HealthErrorType["NONE"] = "NONE";
|
|
14
|
+
HealthErrorType["UNSUPPORTED_ENDPOINT"] = "UNSUPPORTED_ENDPOINT";
|
|
15
|
+
HealthErrorType["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
|
|
16
|
+
HealthErrorType["SERVER_UNREACHABLE"] = "SERVER_UNREACHABLE";
|
|
17
|
+
HealthErrorType["DRIVER_ERROR"] = "DRIVER_ERROR";
|
|
18
|
+
HealthErrorType["TIMEOUT"] = "TIMEOUT";
|
|
19
|
+
})(HealthErrorType || (exports.HealthErrorType = HealthErrorType = {}));
|
|
20
|
+
class XenonSession {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.isStopping = false;
|
|
24
|
+
this.healthState = SessionHealthState.HEALTHY;
|
|
25
|
+
this.sessionId = options.sessionId;
|
|
26
|
+
this.xenonOption = options.xenonOption;
|
|
27
|
+
}
|
|
28
|
+
getId() {
|
|
29
|
+
return this.sessionId;
|
|
30
|
+
}
|
|
31
|
+
getDevice() {
|
|
32
|
+
return this.options.device;
|
|
33
|
+
}
|
|
34
|
+
getDeviceFarmOptions() {
|
|
35
|
+
return this.xenonOption;
|
|
36
|
+
}
|
|
37
|
+
getCapabilities() {
|
|
38
|
+
return this.options.sessionResponse;
|
|
39
|
+
}
|
|
40
|
+
getXenonOption(option, defaultValue = undefined) {
|
|
41
|
+
return this.xenonOption[option] ? this.xenonOption[option] : defaultValue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.XenonSession = XenonSession;
|