'use server' import { withSoapLogging } from "@/lib/soap/utils"; import { XMLBuilder } from 'fast-xml-parser'; // 기본 인증 정보 타입 export interface SoapAuthConfig { username?: string; password?: string; } // SOAP 전송 설정 타입 export interface SoapSendConfig { endpoint: string; envelope: Record; soapAction?: string; timeout?: number; retryCount?: number; retryDelay?: number; namespace?: string; // 네임스페이스를 동적으로 설정할 수 있도록 추가 } // 로깅 정보 타입 export interface SoapLogInfo { direction: 'INBOUND' | 'OUTBOUND'; system: string; interface: string; } // 전송 결과 타입 export interface SoapSendResult { success: boolean; message: string; responseText?: string; statusCode?: number; headers?: Record; } // 기본 환경변수에서 인증 정보 가져오기 function getDefaultAuth(): SoapAuthConfig { return { username: process.env.MDG_SOAP_USERNAME, password: process.env.MDG_SOAP_PASSWORD }; } // 공통 XML 빌더 생성 function createXmlBuilder() { return new XMLBuilder({ ignoreAttributes: false, format: true, attributeNamePrefix: '@_', textNodeName: '#text', suppressEmptyNode: true, suppressUnpairedNode: false, indentBy: ' ', processEntities: false, suppressBooleanAttributes: false, cdataPropName: false, tagValueProcessor: (name, val) => val, attributeValueProcessor: (name, val) => val }); } // SOAP Envelope 생성 function createSoapEnvelope( namespace: string, bodyContent: Record ): Record { return { 'soap:Envelope': { '@_xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/', '@_xmlns:ns0': namespace, 'soap:Body': bodyContent } }; } // XML 생성 export async function generateSoapXml( envelope: Record, xmlDeclaration: string = '\n' ): Promise { const builder = createXmlBuilder(); const xmlBody = builder.build(envelope); return xmlDeclaration + xmlBody; } // SOAP XML 전송 공통 함수 export async function sendSoapXml( config: SoapSendConfig, logInfo: SoapLogInfo, auth?: SoapAuthConfig ): Promise { try { // 인증 정보 설정 (기본값 사용) const authConfig = auth || getDefaultAuth(); // XML 생성 (네임스페이스를 동적으로 설정) const namespace = config.namespace || 'http://shi.samsung.co.kr/P2_MD/MDZ'; const soapEnvelope = createSoapEnvelope( namespace, config.envelope ); const xmlData = await generateSoapXml(soapEnvelope); console.log('📤 SOAP XML 전송 시작'); console.log('🔍 전송 XML (첫 500자):', xmlData.substring(0, 500)); const result = await withSoapLogging( logInfo.direction, logInfo.system, logInfo.interface, xmlData, async () => { // 헤더 설정 const headers: Record = { 'Content-Type': 'text/xml; charset=utf-8', 'SOAPAction': config.soapAction || 'http://sap.com/xi/WebService/soap1.1', }; // Basic Authentication 헤더 추가 if (authConfig.username && authConfig.password) { const credentials = Buffer.from(`${authConfig.username}:${authConfig.password}`).toString('base64'); headers['Authorization'] = `Basic ${credentials}`; console.log('🔐 Basic Authentication 헤더 추가 완료'); } else { console.warn('⚠️ SOAP 인증 정보가 설정되지 않았습니다.'); } // fetch 옵션 설정 const fetchOptions: RequestInit = { method: 'POST', headers, body: xmlData, }; // 타임아웃 설정 if (config.timeout) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), config.timeout); try { const response = await fetch(config.endpoint, { ...fetchOptions, signal: controller.signal }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error && error.name === 'AbortError') { throw new Error(`요청 타임아웃 (${config.timeout}ms)`); } throw error; } } else { return await fetch(config.endpoint, fetchOptions); } } ); // 응답 처리 const response = result as Response; const responseText = await response.text(); console.log('📥 SOAP 응답 수신:', response.status, response.statusText); console.log('🔍 응답 XML (첫 500자):', responseText.substring(0, 500)); // HTTP 상태 코드 확인 if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } // SOAP Fault 검사 if (responseText.includes('soap:Fault') || responseText.includes('SOAP:Fault')) { throw new Error(`SOAP Fault: ${responseText}`); } // 응답 헤더 수집 const responseHeaders: Record = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); return { success: true, message: '전송 성공', responseText, statusCode: response.status, headers: responseHeaders }; } catch (error) { console.error('❌ SOAP XML 전송 실패:', error); return { success: false, message: error instanceof Error ? error.message : 'Unknown error' }; } } // 재시도 로직이 포함된 SOAP 전송 함수 export async function sendSoapXmlWithRetry( config: SoapSendConfig, logInfo: SoapLogInfo, auth?: SoapAuthConfig ): Promise { const maxRetries = config.retryCount || 3; const retryDelay = config.retryDelay || 1000; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`🔄 SOAP 전송 시도 ${attempt}/${maxRetries}`); const result = await sendSoapXml(config, logInfo, auth); if (result.success) { return result; } // 마지막 시도가 아니면 재시도 if (attempt < maxRetries) { console.log(`⏳ ${retryDelay}ms 후 재시도...`); await new Promise(resolve => setTimeout(resolve, retryDelay)); } } catch (error) { console.error(`❌ SOAP 전송 시도 ${attempt} 실패:`, error); if (attempt === maxRetries) { return { success: false, message: `최대 재시도 횟수 초과: ${error instanceof Error ? error.message : 'Unknown error'}` }; } // 마지막 시도가 아니면 재시도 console.log(`⏳ ${retryDelay}ms 후 재시도...`); await new Promise(resolve => setTimeout(resolve, retryDelay)); } } return { success: false, message: '알 수 없는 오류로 전송 실패' }; } // 간단한 SOAP 전송 함수 (기본 설정 사용) export async function sendSimpleSoapXml( endpoint: string, bodyContent: Record, logInfo: SoapLogInfo, options?: { namespace?: string; soapAction?: string; auth?: SoapAuthConfig; timeout?: number; } ): Promise { const config: SoapSendConfig = { endpoint, envelope: bodyContent, soapAction: options?.soapAction, timeout: options?.timeout || 30000, // 기본 30초 namespace: options?.namespace, // 네임스페이스 옵션 추가 }; const auth = options?.auth || getDefaultAuth(); return await sendSoapXml(config, logInfo, auth); } // MDG 전용 SOAP 전송 함수 (기존 action.ts와 호환) export async function sendMdgSoapXml( bodyContent: Record, logInfo: SoapLogInfo, auth?: SoapAuthConfig ): Promise { const config: SoapSendConfig = { endpoint: "http://shii8dvddb01.hec.serp.shi.samsung.net:50000/sap/xi/engine?type=entry&version=3.0&Sender.Service=P2038_Q&Interface=http%3A%2F%2Fshi.samsung.co.kr%2FP2_MD%2FMDZ%5EP2MD3007_AO&QualityOfService=ExactlyOnce", envelope: bodyContent, soapAction: 'http://sap.com/xi/WebService/soap1.1', timeout: 60000, // MDG는 60초 타임아웃 namespace: 'http://shi.samsung.co.kr/P2_MD/MDZ', // MDG 전용 네임스페이스 명시 }; return await sendSoapXml(config, logInfo, auth); }