"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 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; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var CoreService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.CoreService = void 0; const common_1 = require("@nestjs/common"); const microservices_1 = require("@nestjs/microservices"); const uuid_1 = require("uuid"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const request_context_interceptor_1 = require("../interceptors/request-context.interceptor"); let CoreService = CoreService_1 = class CoreService { constructor(client) { this.client = client; this.logger = new common_1.Logger(CoreService_1.name); this.serviceName = process.env.SERVICE_NAME || 'plugin'; this.heartbeatInterval = null; } /** * Спеціальний метод для виконання запитів до Ядра з автоматичним прокиданням авторизації */ async fetchCore(endpoint, options = {}) { const coreUrl = process.env.CORE_BACKEND_URL || 'http://localhost:3001'; const store = request_context_interceptor_1.requestContext.getStore(); const authHeader = store?.get('authorization'); const headers = { 'Content-Type': 'application/json', ...(authHeader ? { 'Authorization': authHeader } : {}), ...(options.headers || {}), }; this.logger.log(`[CoreRequest] Sending request to ${endpoint} (Auth: ${!!authHeader})`); const response = await fetch(`${coreUrl}${endpoint}`, { ...options, headers, }); if (!response.ok) { this.logger.error(`[CoreRequest] Request to ${endpoint} failed with status ${response.status}`); } return response.json(); } // ... (решта методів register, emit, request залишаються без змін) async register() { const coreUrl = process.env.CORE_BACKEND_URL || 'http://localhost:3001'; const possiblePaths = [ path.resolve(process.cwd(), '..', 'manifest.json'), path.resolve(process.cwd(), 'manifest.json'), ]; let manifestPath = ''; for (const p of possiblePaths) { if (fs.existsSync(p)) { manifestPath = p; break; } } if (!manifestPath) return; // Dynamically calculate the backendUrl const port = process.env.PORT || '3002'; const serviceName = process.env.SERVICE_NAME || 'plugin'; const isDocker = coreUrl.includes('core-backend') || (!coreUrl.includes('localhost') && !coreUrl.includes('127.0.0.1')); const backendUrl = process.env.PLUGIN_BACKEND_URL || (isDocker ? `http://${serviceName}:${port}` : `http://localhost:${port}`); let registered = false; while (!registered) { try { const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); if (process.env.PUBLIC_URL) { let p = '/remoteEntry.js'; if (manifest.remoteEntry) { try { if (manifest.remoteEntry.startsWith('http://') || manifest.remoteEntry.startsWith('https://')) { p = new URL(manifest.remoteEntry).pathname; } else { p = manifest.remoteEntry; } } catch (e) { p = manifest.remoteEntry; } } const publicUrl = process.env.PUBLIC_URL.endsWith('/') ? process.env.PUBLIC_URL.slice(0, -1) : process.env.PUBLIC_URL; const pathname = p.startsWith('/') ? p : '/' + p; manifest.remoteEntry = `${publicUrl}${pathname}`; } manifest.backendUrl = backendUrl; const response = await fetch(`${coreUrl}/api/plugins/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(manifest), }); if (response.ok) { this.logger.log(`Plugin registered successfully!`); registered = true; this.startHeartbeatLoop(manifest.id, backendUrl); } else { await new Promise(resolve => setTimeout(resolve, 5000)); } } catch (error) { await new Promise(resolve => setTimeout(resolve, 5000)); } } } startHeartbeatLoop(pluginId, backendUrl) { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); } this.heartbeatInterval = setInterval(async () => { try { const coreUrl = process.env.CORE_BACKEND_URL || 'http://localhost:3001'; this.logger.log(`[Heartbeat] Sending heartbeat for ${pluginId}`); const response = await fetch(`${coreUrl}/api/plugins/heartbeat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: pluginId, backendUrl }), }); if (response.ok) { const resBody = await response.json(); const data = resBody && resBody.data !== undefined ? resBody.data : resBody; if (data && data.registered === false) { this.logger.warn(`[Heartbeat] Core does not recognize plugin ${pluginId}. Re-registering.`); this.register(); } else { this.logger.log(`[Heartbeat] Heartbeat acknowledged by core.`); } } else { this.logger.error(`[Heartbeat] Failed to send heartbeat. Status: ${response.status}`); } } catch (error) { this.logger.error(`[Heartbeat] Error sending heartbeat:`, error.message || error); } }, 30 * 1000); // 30 seconds } async emit(pattern, payload) { const event = { eventId: (0, uuid_1.v4)(), pattern, payload, source: this.serviceName, timestamp: Date.now(), }; this.client.emit(pattern, event).subscribe({ error: (err) => this.logger.error(`Failed to emit event ${pattern}:`, err), }); } async auditLog(action, resource, details) { const store = request_context_interceptor_1.requestContext.getStore(); const authHeader = store?.get('authorization'); const ipAddress = store?.get('ip'); const userAgent = store?.get('userAgent'); let userId; if (authHeader && authHeader.startsWith('Bearer ')) { try { const token = authHeader.split(' ')[1]; const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = Buffer.from(base64, 'base64').toString(); const payload = JSON.parse(jsonPayload); userId = payload.sub || payload.userId || payload.id; } catch (e) { this.logger.error('Failed to decode JWT for audit log', e); } } await this.emit('system.audit.log', { userId, action, resource, details, ipAddress, userAgent, }); } async getGlobalData(key) { const response = await this.fetchCore(`/api/store/${key}`, { headers: { 'x-plugin-id': this.serviceName, }, }); if (response.success) { if (response.data === null || response.data === undefined) { return null; } try { return JSON.parse(response.data); } catch (e) { return response.data; } } return null; } async setGlobalData(key, value) { const stringValue = typeof value === 'string' ? value : JSON.stringify(value); await this.fetchCore(`/api/store/${key}`, { method: 'POST', body: JSON.stringify({ value: stringValue }), headers: { 'x-plugin-id': this.serviceName, }, }); } async deleteGlobalData(key) { await this.fetchCore(`/api/store/${key}`, { method: 'DELETE', headers: { 'x-plugin-id': this.serviceName, }, }); } async sendNotification(payload) { await this.emit('system.notification.send', { userId: payload.userId, title: payload.title, message: payload.message, icon: payload.icon, image: payload.image, details: payload.details, }); } /** * Отримати назву БД, наданої ядром (має сенс після виклику setupDatabase) */ getDbName() { return process.env.PLUGIN_DB_NAME || process.env.DB_DATABASE || 'ultimate_crm'; } /** * Зареєструвати БД плагіна в ядрі (створює БД p_, якщо ще не існує). * Викликати ДО створення NestJS app, щоб TypeORM міг підхопити PLUGIN_DB_NAME. */ static async setupDatabase(options) { const coreUrl = process.env.CORE_BACKEND_URL || 'http://localhost:3001'; const response = await fetch(`${coreUrl}/api/db/setup`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pluginId: options.pluginId }), }); if (!response.ok) { throw new Error(`Database setup failed: ${response.statusText}`); } const result = await response.json(); const data = result.data || result; const dbName = data.dbName; if (!dbName) throw new Error('Database name not returned from core'); process.env.PLUGIN_DB_NAME = dbName; if (data.host) process.env.PLUGIN_DB_HOST = data.host; if (data.port) process.env.PLUGIN_DB_PORT = String(data.port); if (data.username) process.env.PLUGIN_DB_USER = data.username; if (data.password) process.env.PLUGIN_DB_PASSWORD = data.password; return dbName; } }; exports.CoreService = CoreService; exports.CoreService = CoreService = CoreService_1 = __decorate([ (0, common_1.Injectable)(), __param(0, (0, common_1.Inject)('RABBITMQ_CLIENT')), __metadata("design:paramtypes", [microservices_1.ClientProxy]) ], CoreService);