import soap from 'soap'; import { XMLParser } from 'fast-xml-parser'; import type { Client } from 'soap'; import type { SageConfig, SoapReadOptions, SoapQueryOptions, SoapResult, SoapMessage, SoapTechInfo, } from '../types/index.js'; export class SoapClient { private config: SageConfig; private client: Client | null = null; private xmlParser: XMLParser; constructor(config: SageConfig) { this.config = config; this.xmlParser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_', }); } private async getClient(): Promise { if (!this.client) { const wsdlUrl = `${this.config.url}/soap-wsdl/syracuse/collaboration/syracuse/CAdxWebServiceXmlCC?wsdl`; this.client = await soap.createClientAsync(wsdlUrl, {}); this.client.setSecurity( new soap.BasicAuthSecurity(this.config.user, this.config.password), ); const soapEndpoint = `${this.config.url}/soap-generic/syracuse/collaboration/syracuse/CAdxWebServiceXmlCC`; this.client.setEndpoint(soapEndpoint); } return this.client; } private buildCallContext() { return { codeLang: this.config.language, codeUser: '', password: '', poolAlias: this.config.poolAlias, poolId: '', requestConfig: 'adxwss.optreturn=JSON&adxwss.beautify=false', }; } // SOAP caveat: status returns as STRING "1", empty arrays as undefined private normalizeResult(raw: unknown): SoapResult { const r = raw as Record | undefined; return { status: parseInt(String(r?.status ?? '0'), 10), data: this.parseResultXml(r?.resultXml), messages: this.normalizeMessages(r?.messages), technicalInfos: this.normalizeTechInfos(r?.technicalInfos), }; } private parseResultXml(resultXml: unknown): unknown { if (!resultXml || typeof resultXml !== 'string') return null; try { return JSON.parse(resultXml); } catch { try { return this.xmlParser.parse(resultXml); } catch { return resultXml; } } } private normalizeMessages(raw: unknown): SoapMessage[] { if (!raw) return []; const arr = Array.isArray(raw) ? raw : [raw]; return arr.map((m: Record) => ({ type: parseInt(String(m?.type ?? '0'), 10), message: String(m?.message ?? ''), })); } private normalizeTechInfos(raw: unknown): SoapTechInfo[] { if (!raw) return []; const arr = Array.isArray(raw) ? raw : [raw]; return arr.map((t: Record) => ({ type: String(t?.type ?? ''), message: String(t?.message ?? ''), })); } async read(options: SoapReadOptions): Promise { const client = await this.getClient(); const [response] = await client.readAsync({ callContext: this.buildCallContext(), publicName: options.publicName, objectKeys: Object.entries(options.key).map(([key, value]) => ({ key, value, })), }); const res = response as Record; return this.normalizeResult(res?.readReturn ?? res); } async query(options: SoapQueryOptions): Promise { const client = await this.getClient(); const listSize = Math.min(options.listSize || 20, 200); const [response] = await client.queryAsync({ callContext: this.buildCallContext(), publicName: options.publicName, objectKeys: [], listSize, }); const res = response as Record; return this.normalizeResult(res?.queryReturn ?? res); } async getDescription(publicName: string): Promise { const client = await this.getClient(); const [response] = await client.getDescriptionAsync({ callContext: this.buildCallContext(), publicName, }); const res = response as Record; return this.normalizeResult(res?.getDescriptionReturn ?? res); } async healthCheck(): Promise<{ status: string; latencyMs: number }> { const start = Date.now(); try { await this.getClient(); return { status: 'connected', latencyMs: Date.now() - start }; } catch (error) { return { status: `error: ${error instanceof Error ? error.message : String(error)}`, latencyMs: Date.now() - start, }; } } }