UNPKG

@nicodoggie/node-kiwi-tcms-api

Version:

Vibe-coded Node.js wrapper for Kiwi TCMS XML-RPC API. Use at your own risk.

227 lines 9.15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AxiosXmlRpcClient = void 0; const axios_1 = __importDefault(require("axios")); const axios_cookiejar_support_1 = require("axios-cookiejar-support"); const tough_cookie_1 = require("tough-cookie"); const xml2js_1 = require("xml2js"); class AxiosXmlRpcClient { client; url; cookieJar; constructor(config) { this.url = config.url; this.cookieJar = new tough_cookie_1.CookieJar(); // Create axios instance with cookie support this.client = (0, axios_cookiejar_support_1.wrapper)(axios_1.default.create({ timeout: config.timeout || 30000, headers: { 'Content-Type': 'text/xml', 'User-Agent': 'Node.js Kiwi TCMS Client (axios)', ...config.headers, }, // Axios handles redirects automatically maxRedirects: 5, validateStatus: (status) => status >= 200 && status < 300, // Enable cookie jar for session management jar: this.cookieJar, withCredentials: true, })); console.log('🍪 AxiosXmlRpcClient initialized with cookie support'); } async methodCall(method, params = []) { const xmlPayload = this.buildXmlRpcRequest(method, params); try { const response = await this.client.post(this.url, xmlPayload); if (!response.data || typeof response.data !== 'string') { throw new Error('Invalid response: expected XML string'); } return this.parseXmlRpcResponse(response.data); } catch (error) { if (error.response) { throw new Error(`XML-RPC call failed: ${error.response.status} ${error.response.statusText}`); } throw error; } } buildXmlRpcRequest(method, params) { let xml = '<?xml version="1.0"?>\n<methodCall>\n'; xml += ` <methodName>${this.escapeXml(method)}</methodName>\n`; xml += ' <params>\n'; for (const param of params) { xml += ' <param>\n'; xml += this.serializeValue(param, ' '); xml += ' </param>\n'; } xml += ' </params>\n'; xml += '</methodCall>'; return xml; } serializeValue(value, indent) { let xml = `${indent}<value>`; if (value === null || value === undefined) { xml += '<nil/>'; } else if (typeof value === 'string') { xml += `<string>${this.escapeXml(value)}</string>`; } else if (typeof value === 'number') { if (Number.isInteger(value)) { xml += `<int>${value}</int>`; } else { xml += `<double>${value}</double>`; } } else if (typeof value === 'boolean') { xml += `<boolean>${value ? '1' : '0'}</boolean>`; } else if (value instanceof Date) { xml += `<dateTime.iso8601>${value.toISOString()}</dateTime.iso8601>`; } else if (Array.isArray(value)) { xml += '<array><data>\n'; for (const item of value) { xml += this.serializeValue(item, indent + ' '); } xml += `${indent}</data></array>`; } else if (typeof value === 'object') { xml += '<struct>\n'; for (const [key, val] of Object.entries(value)) { xml += `${indent} <member>\n`; xml += `${indent} <name>${this.escapeXml(key)}</name>\n`; xml += this.serializeValue(val, indent + ' '); xml += `${indent} </member>\n`; } xml += `${indent}</struct>`; } xml += '</value>\n'; return xml; } async parseXmlRpcResponse(xmlString) { return new Promise((resolve, reject) => { (0, xml2js_1.parseString)(xmlString, { explicitArray: false, mergeAttrs: true }, (err, result) => { if (err) { reject(new Error(`XML parsing error: ${err.message}`)); return; } try { if (result.methodResponse) { if (result.methodResponse.fault) { // Handle fault response const fault = this.parseXmlRpcValue(result.methodResponse.fault.value); throw new Error(`XML-RPC Fault: ${fault.faultString} (Code: ${fault.faultCode})`); } if (result.methodResponse.params) { // Handle successful response const params = result.methodResponse.params; if (params.param) { const value = Array.isArray(params.param) ? params.param[0] : params.param; return resolve(this.parseXmlRpcValue(value.value)); } } // Handle empty response resolve(null); } else { reject(new Error('Invalid XML-RPC response format')); } } catch (error) { reject(error); } }); }); } parseXmlRpcValue(valueObj) { if (!valueObj || typeof valueObj !== 'object') { return valueObj; } // Handle different XML-RPC types if (valueObj.string !== undefined) { return valueObj.string; } if (valueObj.int !== undefined) { return parseInt(valueObj.int); } if (valueObj.i4 !== undefined) { return parseInt(valueObj.i4); } if (valueObj.double !== undefined) { return parseFloat(valueObj.double); } if (valueObj.boolean !== undefined) { return valueObj.boolean === '1' || valueObj.boolean === 1; } if (valueObj['dateTime.iso8601'] !== undefined) { return new Date(valueObj['dateTime.iso8601']); } if (valueObj.array !== undefined) { if (valueObj.array.data && valueObj.array.data.value) { const values = Array.isArray(valueObj.array.data.value) ? valueObj.array.data.value : [valueObj.array.data.value]; return values.map((v) => this.parseXmlRpcValue(v)); } return []; } if (valueObj.struct !== undefined) { const result = {}; if (valueObj.struct.member) { const members = Array.isArray(valueObj.struct.member) ? valueObj.struct.member : [valueObj.struct.member]; for (const member of members) { if (member.name && member.value !== undefined) { result[member.name] = this.parseXmlRpcValue(member.value); } } } return result; } // Handle XML-RPC nil values - convert to JavaScript null if (valueObj.nil !== undefined) { return null; } // Handle text nodes or direct values - preserve objects when possible if (typeof valueObj === 'string') { return valueObj; } // If it's an object but not a recognized XML-RPC type, check if it's a direct value // This handles cases where xml2js might parse values differently if (typeof valueObj === 'object' && valueObj !== null) { // Check if this is a text node (common in xml2js output) if ('_' in valueObj && Object.keys(valueObj).length === 1) { return valueObj._; } // Check if this is an XML-RPC nil value (alternative representation) if ('nil' in valueObj) { return null; } // If it has multiple properties or no special structure, treat as a complex object // Recursively process all properties to handle nested structures const result = {}; for (const [key, value] of Object.entries(valueObj)) { result[key] = this.parseXmlRpcValue(value); } return result; } // For any other types (number, boolean, etc), return as-is return valueObj; } escapeXml(str) { return str .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&apos;'); } } exports.AxiosXmlRpcClient = AxiosXmlRpcClient; //# sourceMappingURL=axios-xmlrpc-client.js.map