269 lines
11 KiB
JavaScript
269 lines
11 KiB
JavaScript
"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
|