@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,134 @@
|
|
|
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.APP_SERVICE = exports.AppService = void 0;
|
|
16
|
+
const prisma_1 = require("../../prisma");
|
|
17
|
+
const config_1 = require("../../config");
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
20
|
+
const appium_adb_1 = require("appium-adb");
|
|
21
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
22
|
+
const uuid_1 = require("uuid");
|
|
23
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
24
|
+
class AppService {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.ensureAppDir();
|
|
27
|
+
}
|
|
28
|
+
ensureAppDir() {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
try {
|
|
31
|
+
yield fs_extra_1.default.ensureDir(config_1.config.appsPath);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
logger_1.default.error(`Failed to create apps directory at ${config_1.config.appsPath}: ${err}`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
getApps() {
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
return yield prisma_1.prisma.app.findMany({
|
|
41
|
+
orderBy: { createdAt: 'desc' },
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
getAppById(id) {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
return yield prisma_1.prisma.app.findUnique({
|
|
48
|
+
where: { id },
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
getWDAApp() {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
return yield prisma_1.prisma.app.findFirst({
|
|
55
|
+
where: {
|
|
56
|
+
platform: 'ios',
|
|
57
|
+
OR: [
|
|
58
|
+
{ name: { contains: 'wda-signed' } },
|
|
59
|
+
{ name: { contains: 'wda-resign' } },
|
|
60
|
+
{ name: { contains: 'WebDriverAgent' } },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
orderBy: { createdAt: 'desc' },
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
deleteApp(id) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
const app = yield this.getAppById(id);
|
|
70
|
+
if (app) {
|
|
71
|
+
if (yield fs_extra_1.default.exists(app.filepath)) {
|
|
72
|
+
yield fs_extra_1.default.remove(app.filepath);
|
|
73
|
+
}
|
|
74
|
+
return yield prisma_1.prisma.app.delete({
|
|
75
|
+
where: { id },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
uploadApp(file) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
var _a;
|
|
83
|
+
yield this.ensureAppDir();
|
|
84
|
+
// Calculate MD5 to check for duplicates
|
|
85
|
+
const md5 = crypto_1.default.createHash('md5').update(file.data).digest('hex');
|
|
86
|
+
const existingApp = yield prisma_1.prisma.app.findUnique({ where: { md5 } });
|
|
87
|
+
if (existingApp) {
|
|
88
|
+
logger_1.default.info(`App with MD5 ${md5} already exists. Returning existing app.`);
|
|
89
|
+
return existingApp;
|
|
90
|
+
}
|
|
91
|
+
const id = (0, uuid_1.v4)();
|
|
92
|
+
const filename = file.name;
|
|
93
|
+
const extension = path_1.default.extname(filename).toLowerCase();
|
|
94
|
+
const filepath = path_1.default.join(config_1.config.appsPath, `${id}${extension}`);
|
|
95
|
+
yield fs_extra_1.default.writeFile(filepath, file.data);
|
|
96
|
+
let packageName;
|
|
97
|
+
let version;
|
|
98
|
+
let platform;
|
|
99
|
+
if (extension === '.apk') {
|
|
100
|
+
platform = 'android';
|
|
101
|
+
try {
|
|
102
|
+
const adb = yield appium_adb_1.ADB.createADB({});
|
|
103
|
+
const apkInfo = yield adb.getApkInfo(filepath);
|
|
104
|
+
packageName = apkInfo.name;
|
|
105
|
+
version = apkInfo.versionName || ((_a = apkInfo.versionCode) === null || _a === void 0 ? void 0 : _a.toString());
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
logger_1.default.warn(`Failed to extract metadata for APK ${filename}: ${err}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (extension === '.ipa') {
|
|
112
|
+
platform = 'ios';
|
|
113
|
+
// For IPA, we could use appium-ios-device or similar,
|
|
114
|
+
// but for now let's just mark it as ios.
|
|
115
|
+
}
|
|
116
|
+
return yield prisma_1.prisma.app.create({
|
|
117
|
+
data: {
|
|
118
|
+
id,
|
|
119
|
+
name: filename,
|
|
120
|
+
filename,
|
|
121
|
+
filepath,
|
|
122
|
+
mimetype: file.mimetype,
|
|
123
|
+
size: file.size,
|
|
124
|
+
packageName,
|
|
125
|
+
version,
|
|
126
|
+
platform,
|
|
127
|
+
md5,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.AppService = AppService;
|
|
134
|
+
exports.APP_SERVICE = new AppService();
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __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.analyzeSessionFailure = analyzeSessionFailure;
|
|
49
|
+
const prisma_1 = require("../../prisma");
|
|
50
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
51
|
+
const ERROR_PATTERNS = [
|
|
52
|
+
{
|
|
53
|
+
category: 'ELEMENT_NOT_FOUND',
|
|
54
|
+
patterns: [
|
|
55
|
+
'NoSuchElementError',
|
|
56
|
+
'unable to find an element',
|
|
57
|
+
'An element could not be located',
|
|
58
|
+
'no such element',
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
category: 'APP_CRASH',
|
|
63
|
+
patterns: [
|
|
64
|
+
'Appium crashed',
|
|
65
|
+
'process has died',
|
|
66
|
+
'activity has died',
|
|
67
|
+
'The application has crashed',
|
|
68
|
+
'Application not responding',
|
|
69
|
+
"org.openqa.selenium.WebDriverException: An unknown server-side error occurred while processing the command. Original error: The application under test with bundle id '.*' is not running or cannot be found",
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
category: 'TIMEOUT',
|
|
74
|
+
patterns: ['timeout', 'timed out', 'TimeoutException', 'New Command Timeout', 'socket hang up'],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
category: 'PERMISSION_BLOCKED',
|
|
78
|
+
patterns: ['Permission alert', 'Security alert', 'Always Allow', 'Allow while using app'],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
category: 'WDA_FAILURE',
|
|
82
|
+
patterns: [
|
|
83
|
+
'WebDriverAgent',
|
|
84
|
+
'WDA',
|
|
85
|
+
'xcodebuild failed',
|
|
86
|
+
'crashed with code',
|
|
87
|
+
'Unable to connect to WDA',
|
|
88
|
+
'Session does not exist',
|
|
89
|
+
'the session is not in a running state',
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
category: 'XENON_COMMAND_FAILURE',
|
|
94
|
+
patterns: ['Command failed', 'telemetry failed', 'interceptor error'],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
category: 'SYSTEM_OVERLOAD',
|
|
98
|
+
patterns: ['OutOfMemory', 'MemoryLimit', 'thermal throttling', 'too many open files'],
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
function analyzeSessionFailure(sessionId) {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
103
|
+
try {
|
|
104
|
+
const session = yield prisma_1.prisma.session.findUnique({
|
|
105
|
+
where: { id: sessionId },
|
|
106
|
+
include: {
|
|
107
|
+
SessionLog: { where: { is_error: true }, take: 5, orderBy: { createdAt: 'desc' } },
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
if (!session)
|
|
111
|
+
return;
|
|
112
|
+
const reason = session.failure_reason || '';
|
|
113
|
+
const logs_text = session.SessionLog.map((l) => `${l.title} ${l.response}`).join(' ');
|
|
114
|
+
const combined_text = (reason + ' ' + logs_text).toLowerCase();
|
|
115
|
+
let identifiedCategory = 'UNKNOWN';
|
|
116
|
+
for (const item of ERROR_PATTERNS) {
|
|
117
|
+
if (item.patterns.some((p) => new RegExp(p, 'i').test(combined_text))) {
|
|
118
|
+
identifiedCategory = item.category;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Special case for App Crash - check Logcat/Syslog if available
|
|
123
|
+
// For now we rely on the error response text which usually mentions "process has died"
|
|
124
|
+
logger_1.default.info(`[FailureAnalysis] Session ${sessionId} identified as ${identifiedCategory}`);
|
|
125
|
+
// AI Root-Cause Analysis
|
|
126
|
+
let aiAnalysis = null;
|
|
127
|
+
try {
|
|
128
|
+
const { AI_SERVICE } = yield Promise.resolve().then(() => __importStar(require('../../services/AIService')));
|
|
129
|
+
if (AI_SERVICE.isEnabled()) {
|
|
130
|
+
const lastLogs = yield prisma_1.prisma.log.findMany({
|
|
131
|
+
where: { session_id: sessionId, log_type: 'DEVICE' },
|
|
132
|
+
take: 50,
|
|
133
|
+
orderBy: { timestamp: 'desc' },
|
|
134
|
+
});
|
|
135
|
+
const lastCommands = yield prisma_1.prisma.sessionLog.findMany({
|
|
136
|
+
where: { session_id: sessionId },
|
|
137
|
+
take: 10,
|
|
138
|
+
orderBy: { createdAt: 'desc' },
|
|
139
|
+
});
|
|
140
|
+
// Find the last screenshot in logs
|
|
141
|
+
const lastScreenshotLog = lastCommands.find((l) => l.screenshot !== null);
|
|
142
|
+
aiAnalysis = yield AI_SERVICE.analyzeFailure({
|
|
143
|
+
sessionId,
|
|
144
|
+
failureReason: reason,
|
|
145
|
+
commandLogs: lastCommands.map((c) => {
|
|
146
|
+
var _a;
|
|
147
|
+
return ({
|
|
148
|
+
command: c.command_name,
|
|
149
|
+
success: c.is_success,
|
|
150
|
+
response: (_a = c.response) === null || _a === void 0 ? void 0 : _a.slice(0, 500), // Truncate long responses
|
|
151
|
+
});
|
|
152
|
+
}),
|
|
153
|
+
deviceLogs: lastLogs.map((l) => l.message),
|
|
154
|
+
screenshotPath: (lastScreenshotLog === null || lastScreenshotLog === void 0 ? void 0 : lastScreenshotLog.screenshot) || undefined,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (aiErr) {
|
|
159
|
+
logger_1.default.warn(`[FailureAnalysis] AI Analysis failed for ${sessionId}: ${aiErr.message}`);
|
|
160
|
+
}
|
|
161
|
+
yield prisma_1.prisma.session.update({
|
|
162
|
+
where: { id: sessionId },
|
|
163
|
+
data: {
|
|
164
|
+
failure_category: identifiedCategory,
|
|
165
|
+
ai_analysis: aiAnalysis,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
logger_1.default.error(`[FailureAnalysis] Failed to analyze session ${sessionId}: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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.getOrCreateNewBuild = getOrCreateNewBuild;
|
|
16
|
+
exports.getSessionById = getSessionById;
|
|
17
|
+
exports.updateSessionDetails = updateSessionDetails;
|
|
18
|
+
exports.cleanupZombieSessions = cleanupZombieSessions;
|
|
19
|
+
const prisma_1 = require("../../prisma");
|
|
20
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
21
|
+
function getOrCreateNewBuild(buildName) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
// Principal Logic: Find the LATEST build with this name to check for a "Warm Window"
|
|
24
|
+
const build = yield prisma_1.prisma.build.findFirst({
|
|
25
|
+
where: {
|
|
26
|
+
name: buildName,
|
|
27
|
+
},
|
|
28
|
+
orderBy: {
|
|
29
|
+
updatedAt: 'desc',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
if (build) {
|
|
33
|
+
const lastUpdate = new Date(build.updatedAt).getTime();
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const diffInMinutes = (now - lastUpdate) / (1000 * 60);
|
|
36
|
+
// If the build was active within the last 30 minutes, treat it as the "current" run.
|
|
37
|
+
if (diffInMinutes < 30) {
|
|
38
|
+
// Principal Optimization: "Touch" the build to refresh the window for subsequent sessions.
|
|
39
|
+
// This keeps the build 'alive' as long as sessions are trickling in.
|
|
40
|
+
return yield prisma_1.prisma.build.update({
|
|
41
|
+
where: { id: build.id },
|
|
42
|
+
data: { updatedAt: new Date() },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// If no build found OR the existing one is "stone cold" (>30 mins), create a fresh record
|
|
47
|
+
return yield prisma_1.prisma.build.create({
|
|
48
|
+
data: {
|
|
49
|
+
name: buildName,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function getSessionById(sessionId) {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
return yield prisma_1.prisma.session.findFirst({
|
|
57
|
+
where: {
|
|
58
|
+
id: sessionId,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function updateSessionDetails(sessionId, data) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
return yield prisma_1.prisma.session.update({
|
|
66
|
+
where: {
|
|
67
|
+
id: sessionId,
|
|
68
|
+
},
|
|
69
|
+
data,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Principal Protection: Marks all orphaned "running" sessions as FAILED.
|
|
75
|
+
* This should be called on plugin initialization to clear crashes from previous runs.
|
|
76
|
+
* @param ignoreSessionIds - Array of session IDs that were successfully recovered and should NOT be cleaned up.
|
|
77
|
+
*/
|
|
78
|
+
function cleanupZombieSessions() {
|
|
79
|
+
return __awaiter(this, arguments, void 0, function* (ignoreSessionIds = []) {
|
|
80
|
+
const whereClause = {
|
|
81
|
+
status: 'running',
|
|
82
|
+
};
|
|
83
|
+
if (ignoreSessionIds.length > 0) {
|
|
84
|
+
whereClause.id = { notIn: ignoreSessionIds };
|
|
85
|
+
}
|
|
86
|
+
const orphanedSessions = yield prisma_1.prisma.session.findMany({
|
|
87
|
+
where: whereClause,
|
|
88
|
+
});
|
|
89
|
+
if (orphanedSessions.length > 0) {
|
|
90
|
+
logger_1.default.info(`[Dashboard] Cleaning up ${orphanedSessions.length} zombie sessions...`);
|
|
91
|
+
yield prisma_1.prisma.session.updateMany({
|
|
92
|
+
where: whereClause,
|
|
93
|
+
data: {
|
|
94
|
+
status: 'failed',
|
|
95
|
+
failure_reason: 'Session interrupted (Server restart or process crash)',
|
|
96
|
+
endTime: new Date(),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Principal Repair: Assign sessions with no build_id to "Default Build"
|
|
101
|
+
const missingBuildSessions = yield prisma_1.prisma.session.count({
|
|
102
|
+
where: { build: null },
|
|
103
|
+
});
|
|
104
|
+
if (missingBuildSessions > 0) {
|
|
105
|
+
logger_1.default.info(`[Dashboard] Repairing ${missingBuildSessions} orphaned sessions...`);
|
|
106
|
+
const defaultBuild = yield getOrCreateNewBuild('Default Build');
|
|
107
|
+
yield prisma_1.prisma.session.updateMany({
|
|
108
|
+
where: { build_id: null },
|
|
109
|
+
data: { build_id: defaultBuild.id },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.CircuitBreaker = void 0;
|
|
16
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
17
|
+
const typedi_1 = require("typedi");
|
|
18
|
+
var BreakerState;
|
|
19
|
+
(function (BreakerState) {
|
|
20
|
+
BreakerState["CLOSED"] = "CLOSED";
|
|
21
|
+
BreakerState["OPEN"] = "OPEN";
|
|
22
|
+
BreakerState["HALF_OPEN"] = "HALF_OPEN";
|
|
23
|
+
})(BreakerState || (BreakerState = {}));
|
|
24
|
+
/**
|
|
25
|
+
* Principal Circuit Breaker
|
|
26
|
+
* Prevents cascading failures by temporary bypassing unstable remote nodes.
|
|
27
|
+
*/
|
|
28
|
+
let CircuitBreaker = class CircuitBreaker {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.stats = new Map();
|
|
31
|
+
this.logger = logger_1.default.scope('CircuitBreaker');
|
|
32
|
+
this.FAILURE_THRESHOLD = 3;
|
|
33
|
+
this.RECOVERY_TIMEOUT_MS = 60000; // 1 minute
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a node is currently healthy enough to receive requests.
|
|
37
|
+
* @param host The remote node host URL
|
|
38
|
+
*/
|
|
39
|
+
isOpen(host) {
|
|
40
|
+
const nodeStats = this.stats.get(host);
|
|
41
|
+
if (!nodeStats || nodeStats.state === BreakerState.CLOSED) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (nodeStats.state === BreakerState.OPEN) {
|
|
45
|
+
if (Date.now() > (nodeStats.nextRetryTime || 0)) {
|
|
46
|
+
this.logger.info(`🔄 Node ${host} entering HALF_OPEN state. Attempting recovery...`);
|
|
47
|
+
nodeStats.state = BreakerState.HALF_OPEN;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Records a failure for a specific node.
|
|
56
|
+
*/
|
|
57
|
+
recordFailure(host) {
|
|
58
|
+
const nodeStats = this.stats.get(host) || { state: BreakerState.CLOSED, failures: 0 };
|
|
59
|
+
nodeStats.failures++;
|
|
60
|
+
nodeStats.lastFailureTime = Date.now();
|
|
61
|
+
if (nodeStats.failures >= this.FAILURE_THRESHOLD) {
|
|
62
|
+
this.logger.error(`🚨 Node ${host} tripped! Circuit OPEN. Bypassing for ${this.RECOVERY_TIMEOUT_MS / 1000}s`);
|
|
63
|
+
nodeStats.state = BreakerState.OPEN;
|
|
64
|
+
nodeStats.nextRetryTime = Date.now() + this.RECOVERY_TIMEOUT_MS;
|
|
65
|
+
}
|
|
66
|
+
this.stats.set(host, nodeStats);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Records a success, resetting the circuit.
|
|
70
|
+
*/
|
|
71
|
+
recordSuccess(host) {
|
|
72
|
+
const nodeStats = this.stats.get(host);
|
|
73
|
+
if (nodeStats && nodeStats.state !== BreakerState.CLOSED) {
|
|
74
|
+
this.logger.info(`✅ Node ${host} circuit CLOSED. Recovery successful.`);
|
|
75
|
+
}
|
|
76
|
+
this.stats.set(host, { state: BreakerState.CLOSED, failures: 0 });
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
exports.CircuitBreaker = CircuitBreaker;
|
|
80
|
+
exports.CircuitBreaker = CircuitBreaker = __decorate([
|
|
81
|
+
(0, typedi_1.Service)(),
|
|
82
|
+
__metadata("design:paramtypes", [])
|
|
83
|
+
], CircuitBreaker);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
45
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
46
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
47
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
48
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
49
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
50
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
54
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
55
|
+
};
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.ConfigService = void 0;
|
|
58
|
+
const node_persist_1 = __importDefault(require("node-persist"));
|
|
59
|
+
const typedi_1 = require("typedi");
|
|
60
|
+
const helpers_1 = require("../helpers");
|
|
61
|
+
const SecurityService_1 = require("../services/SecurityService");
|
|
62
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
63
|
+
const fs_1 = __importDefault(require("fs"));
|
|
64
|
+
const CONFIG_DIR = (0, helpers_1.cachePath)('config');
|
|
65
|
+
let ConfigService = class ConfigService {
|
|
66
|
+
constructor(securityService) {
|
|
67
|
+
this.securityService = securityService;
|
|
68
|
+
this.sensitiveFields = ['databaseUrl'];
|
|
69
|
+
}
|
|
70
|
+
init() {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
if (!this.storage) {
|
|
73
|
+
if (!fs_1.default.existsSync(CONFIG_DIR)) {
|
|
74
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
this.storage = node_persist_1.default.create({ dir: CONFIG_DIR });
|
|
77
|
+
yield this.storage.init();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
loadConfig() {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
var _a;
|
|
84
|
+
yield this.init();
|
|
85
|
+
const config = (yield ((_a = this.storage) === null || _a === void 0 ? void 0 : _a.getItem('pluginArgs'))) || {};
|
|
86
|
+
// Decrypt sensitive fields
|
|
87
|
+
for (const field of this.sensitiveFields) {
|
|
88
|
+
if (config[field]) {
|
|
89
|
+
config[field] = yield this.securityService.decrypt(config[field]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return config;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
updateConfig(newConfig) {
|
|
96
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
var _a;
|
|
98
|
+
yield this.init();
|
|
99
|
+
const currentConfig = yield this.loadConfig();
|
|
100
|
+
try {
|
|
101
|
+
const mergedConfig = Object.assign(Object.assign({}, currentConfig), newConfig);
|
|
102
|
+
const toSave = Object.assign({}, mergedConfig);
|
|
103
|
+
// Encrypt sensitive fields before saving
|
|
104
|
+
for (const field of this.sensitiveFields) {
|
|
105
|
+
if (toSave[field]) {
|
|
106
|
+
toSave[field] = yield this.securityService.encrypt(toSave[field]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
yield ((_a = this.storage) === null || _a === void 0 ? void 0 : _a.setItem('pluginArgs', toSave));
|
|
110
|
+
// Update runtime Global Config and Process.env for immediate effect
|
|
111
|
+
const { updateConfig: updateGlobalConfig } = yield Promise.resolve().then(() => __importStar(require('../config')));
|
|
112
|
+
const runtimeUpdate = {};
|
|
113
|
+
if (newConfig.aiProvider)
|
|
114
|
+
runtimeUpdate.aiProvider = newConfig.aiProvider;
|
|
115
|
+
if (newConfig.aiModel)
|
|
116
|
+
runtimeUpdate.aiModel = newConfig.aiModel;
|
|
117
|
+
if (newConfig.aiBaseUrl)
|
|
118
|
+
runtimeUpdate.aiBaseUrl = newConfig.aiBaseUrl;
|
|
119
|
+
// Sync Keys to both Global Config and legacy Process.env
|
|
120
|
+
if (newConfig.geminiApiKey) {
|
|
121
|
+
runtimeUpdate.geminiApiKey = newConfig.geminiApiKey;
|
|
122
|
+
process.env.GEMINI_API_KEY = newConfig.geminiApiKey;
|
|
123
|
+
}
|
|
124
|
+
if (newConfig.openaiApiKey) {
|
|
125
|
+
runtimeUpdate.openaiApiKey = newConfig.openaiApiKey;
|
|
126
|
+
process.env.OPENAI_API_KEY = newConfig.openaiApiKey;
|
|
127
|
+
}
|
|
128
|
+
if (newConfig.anthropicApiKey) {
|
|
129
|
+
runtimeUpdate.anthropicApiKey = newConfig.anthropicApiKey;
|
|
130
|
+
process.env.ANTHROPIC_API_KEY = newConfig.anthropicApiKey;
|
|
131
|
+
}
|
|
132
|
+
if (Object.keys(runtimeUpdate).length > 0) {
|
|
133
|
+
updateGlobalConfig(runtimeUpdate);
|
|
134
|
+
}
|
|
135
|
+
logger_1.default.info('Updated plugin configuration (encrypted at rest)');
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
logger_1.default.error(`Persistence failure in ConfigService: ${err.message}`);
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
resetConfig() {
|
|
144
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
145
|
+
var _a;
|
|
146
|
+
yield this.init();
|
|
147
|
+
yield ((_a = this.storage) === null || _a === void 0 ? void 0 : _a.clear());
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
exports.ConfigService = ConfigService;
|
|
152
|
+
exports.ConfigService = ConfigService = __decorate([
|
|
153
|
+
(0, typedi_1.Service)(),
|
|
154
|
+
__metadata("design:paramtypes", [SecurityService_1.SecurityService])
|
|
155
|
+
], ConfigService);
|