"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.pangolinClient = void 0; const env_1 = require("../config/env"); const logger_1 = require("../utils/logger"); // --- Client --- class PangolinClient { get baseUrl() { return env_1.env.PANGOLIN_API_URL; } get apiKey() { return env_1.env.PANGOLIN_API_KEY; } get orgId() { return env_1.env.PANGOLIN_ORG_ID; } get configured() { return !!(this.baseUrl && this.apiKey && this.orgId); } async request(method, path, body) { if (!this.baseUrl || !this.apiKey) { throw new Error('Pangolin API not configured. Set PANGOLIN_API_URL and PANGOLIN_API_KEY.'); } const url = `${this.baseUrl}${path}`; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 10000); try { const res = await fetch(url, { method, headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(`Pangolin API ${method} ${path} returned ${res.status}: ${text}`); } const contentType = res.headers.get('content-type') || ''; if (contentType.includes('application/json')) { return await res.json(); } return {}; } finally { clearTimeout(timeout); } } async healthCheck() { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); try { const res = await fetch(`${this.baseUrl}/`, { headers: { 'Authorization': `Bearer ${this.apiKey}` }, signal: controller.signal, }); return res.ok; } finally { clearTimeout(timeout); } } catch (err) { logger_1.logger.warn('Pangolin health check failed:', err instanceof Error ? err.message : err); return false; } } // --- Sites --- async listSites() { const res = await this.request('GET', `/org/${this.orgId}/sites`); // Handle direct array (edge case) if (Array.isArray(res)) { logger_1.logger.info('listSites: received direct array'); return res; } const obj = res; // Official Pangolin format: { data: { sites: [...], pagination: {...} } } if (obj.data && typeof obj.data === 'object') { const dataObj = obj.data; if (Array.isArray(dataObj.sites)) { logger_1.logger.info(`listSites: extracted ${dataObj.sites.length} sites from data.sites`); return dataObj.sites; } } // Fallback: { sites: [...] } or { data: [...] } if (Array.isArray(obj.sites)) { logger_1.logger.info(`listSites: extracted ${obj.sites.length} sites from sites`); return obj.sites; } if (Array.isArray(obj.data)) { logger_1.logger.info(`listSites: extracted ${obj.data.length} sites from data`); return obj.data; } logger_1.logger.warn('listSites: could not extract sites array from response, returning empty'); return []; } async listExitNodes() { try { const res = await this.request('GET', `/org/${this.orgId}/exit-nodes`); // Handle direct array (edge case) if (Array.isArray(res)) { logger_1.logger.info('listExitNodes: received direct array'); return res.filter(node => node && typeof node.exitNodeId === 'string' && typeof node.name === 'string' && typeof node.online === 'boolean'); } const obj = res; // Official Pangolin format: { data: { exitNodes: [...], pagination: {...} } } if (obj.data && typeof obj.data === 'object') { const dataObj = obj.data; if (Array.isArray(dataObj.exitNodes)) { logger_1.logger.info(`listExitNodes: extracted ${dataObj.exitNodes.length} exit nodes from data.exitNodes`); return dataObj.exitNodes.filter(node => node && typeof node.exitNodeId === 'string' && typeof node.name === 'string' && typeof node.online === 'boolean'); } } // Fallback: { exitNodes: [...] } or { data: [...] } if (Array.isArray(obj.exitNodes)) { logger_1.logger.info(`listExitNodes: extracted ${obj.exitNodes.length} exit nodes from exitNodes`); return obj.exitNodes.filter(node => node && typeof node.exitNodeId === 'string' && typeof node.name === 'string' && typeof node.online === 'boolean'); } if (Array.isArray(obj.data)) { logger_1.logger.info(`listExitNodes: extracted ${obj.data.length} exit nodes from data`); return obj.data.filter(node => node && typeof node.exitNodeId === 'string' && typeof node.name === 'string' && typeof node.online === 'boolean'); } logger_1.logger.warn('listExitNodes: could not extract exit nodes array from response, returning empty'); return []; } catch (err) { // Exit nodes endpoint not available (404) - this is OK for self-hosted if (err instanceof Error && err.message.includes('404')) { logger_1.logger.info('Pangolin exit-nodes endpoint not available (self-hosted mode without separate exit nodes)'); return []; } // Other errors - log but don't fail logger_1.logger.warn('Failed to fetch exit nodes:', err); return []; } } async getSite(siteId) { return this.request('GET', `/site/${siteId}`); } async createSite(data) { return this.request('PUT', `/org/${this.orgId}/site`, data); } async deleteSite(siteId) { await this.request('DELETE', `/site/${siteId}`); } // --- Resources --- async listResources() { const res = await this.request('GET', `/org/${this.orgId}/site-resources`); // DEBUG: Log full response structure logger_1.logger.info(`listResources raw response: ${JSON.stringify(res, null, 2)}`); // Handle direct array (edge case) if (Array.isArray(res)) { logger_1.logger.info(`listResources: received direct array with ${res.length} items`); if (res.length > 0) { logger_1.logger.info(`First resource: ${JSON.stringify(res[0], null, 2)}`); } return res; } const obj = res; // Official Pangolin format: { data: { resources: [...], pagination: {...} } } if (obj.data && typeof obj.data === 'object') { const dataObj = obj.data; if (Array.isArray(dataObj.resources)) { logger_1.logger.info(`listResources: extracted ${dataObj.resources.length} resources from data.resources`); if (dataObj.resources.length > 0) { logger_1.logger.info(`First resource from data.resources: ${JSON.stringify(dataObj.resources[0], null, 2)}`); } return dataObj.resources; } } // Fallback: { resources: [...] } or { data: [...] } if (Array.isArray(obj.resources)) { logger_1.logger.info(`listResources: extracted ${obj.resources.length} resources from .resources`); if (obj.resources.length > 0) { logger_1.logger.info(`First resource from .resources: ${JSON.stringify(obj.resources[0], null, 2)}`); } return obj.resources; } if (Array.isArray(obj.data)) { logger_1.logger.info(`listResources: extracted ${obj.data.length} resources from .data`); if (obj.data.length > 0) { logger_1.logger.info(`First resource from .data: ${JSON.stringify(obj.data[0], null, 2)}`); } return obj.data; } logger_1.logger.warn('listResources: could not extract resources array from response, returning empty'); return []; } async getResource(resourceId) { return this.request('GET', `/site-resource/${resourceId}`); } async createResource(data) { // Use different endpoints for different resource types const endpoint = data.type === 'http' ? `/org/${this.orgId}/site-resource` // HTTP/HTTPS resources : `/org/${this.orgId}/resource`; // TCP/UDP resources return this.request('PUT', endpoint, data); } async updateResource(resourceId, data) { return this.request('POST', `/site-resource/${resourceId}`, data); } async deleteResource(resourceId) { await this.request('DELETE', `/site-resource/${resourceId}`); } async createTarget(resourceId, data) { return this.request('POST', `/resource/${resourceId}/target`, data); } // --- Domains --- async listDomains() { const res = await this.request('GET', `/org/${this.orgId}/domains`); // Handle direct array if (Array.isArray(res)) { return res; } const obj = res; // Check nested data.domains if (obj.data && typeof obj.data === 'object') { const dataObj = obj.data; if (Array.isArray(dataObj.domains)) { return dataObj.domains; } } // Fallback if (Array.isArray(obj.domains)) { return obj.domains; } if (Array.isArray(obj.data)) { return obj.data; } logger_1.logger.warn('listDomains: could not extract domains array, returning empty'); return []; } async listResourcesForSite(siteId) { const res = await this.request('GET', `/org/${this.orgId}/site/${siteId}/resources`); return Array.isArray(res) ? res : (res.data || []); } async getResourceByNiceId(siteId, niceId) { return this.request('GET', `/org/${this.orgId}/site/${siteId}/resource/nice/${niceId}`); } // --- Certificates --- async getCertificate(domainId, domain) { return this.request('GET', `/org/${this.orgId}/certificate/${domainId}/${domain}`); } async updateCertificate(certId, data) { return this.request('POST', `/certificate/${certId}`, data); } // --- Clients --- async listClients(siteResourceId) { const res = await this.request('GET', `/site-resource/${siteResourceId}/clients`); return Array.isArray(res) ? res : (res.data || []); } } exports.pangolinClient = new PangolinClient(); //# sourceMappingURL=pangolin.client.js.map