// 환경 변수 const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api'; const SEDP_API_USER_ID = process.env.SEDP_API_USER_ID || 'EVCPUSER'; const SEDP_API_PASSWORD = process.env.SEDP_API_PASSWORD || 'evcpusr@2025'; // 토큰 캐시 (1시간 동안 유효) let cachedToken: string | null = null; let tokenExpiresAt: number | null = null; const TOKEN_VALIDITY_MS = 5 * 60 * 1000; // 5분 (밀리초) /** * SEDP API에서 인증 토큰을 요청합니다. * 내부 헬퍼 함수로, 실제 API 호출을 수행합니다. */ async function requestNewToken(): Promise { const response = await fetch( `${SEDP_API_BASE_URL}/Security/RequestTokenWithMembership`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'accept': '*/*' }, body: JSON.stringify({ UserID: SEDP_API_USER_ID, Password: SEDP_API_PASSWORD }) } ); if (!response.ok) { throw new Error(`SEDP 토큰 요청 실패: ${response.status} ${response.statusText}`); } // 응답이 직접 토큰 문자열인 경우 const tokenData = await response.text(); // 응답이 JSON 형식이면 파싱 try { const jsonData = JSON.parse(tokenData); if (typeof jsonData === 'string') { return jsonData; // JSON 문자열이지만 내용물이 토큰 문자열인 경우 } else if (jsonData.Token) { return jsonData.Token; // { Token: "..." } 형태인 경우 } else { console.warn('예상치 못한 토큰 응답 형식:', jsonData); // 가장 가능성 있는 필드를 찾아봄 for (const key of ['token', 'accessToken', 'access_token', 'Token', 'jwt']) { if (jsonData[key]) return jsonData[key]; } // 그래도 없으면 문자열로 변환 return JSON.stringify(jsonData); } } catch { // 파싱 실패 = 응답이 JSON이 아닌 순수 토큰 문자열 return tokenData.trim(); } } /** * SEDP API에서 인증 토큰을 가져옵니다. * 토큰을 1시간 동안 캐싱하여 재사용합니다. * 캐시된 토큰이 없거나 만료된 경우에만 새로운 토큰을 요청합니다. */ export async function getSEDPToken(): Promise { try { // 현재 시간 const now = Date.now(); // 캐시된 토큰이 있고 아직 유효한 경우 if (cachedToken && tokenExpiresAt && now < tokenExpiresAt) { console.log(`SEDP 토큰 캐시 사용 (만료까지 ${Math.round((tokenExpiresAt - now) / 1000 / 60)}분 남음)`); return cachedToken; } // 새로운 토큰 요청 console.log('SEDP 새 토큰 요청 중...'); const token = await requestNewToken(); // 캐시에 저장 cachedToken = token; tokenExpiresAt = now + TOKEN_VALIDITY_MS; console.log('SEDP 토큰 발급 완료 (1시간 유효)'); return token; } catch (error) { console.error('SEDP 토큰 가져오기 실패:', error); // 오류 발생 시 캐시 초기화 cachedToken = null; tokenExpiresAt = null; throw error; } } /** * 캐시된 토큰을 강제로 무효화합니다. * 토큰이 만료되었거나 인증 오류가 발생한 경우 호출할 수 있습니다. */ export function invalidateSEDPToken(): void { console.log('SEDP 토큰 캐시 무효화'); cachedToken = null; tokenExpiresAt = null; } /** * SEDP API에 인증된 요청을 보냅니다. */ export async function fetchSEDP(endpoint: string, options: RequestInit = {}): Promise { try { // 토큰 가져오기 const token = await getSEDPToken(); // 헤더 준비 const headers = { 'Content-Type': 'application/json', 'accept': '*/*', 'ApiKey': token, ...(options.headers || {}) }; // 요청 보내기 const response = await fetch(`${SEDP_API_BASE_URL}${endpoint}`, { ...options, headers }); if (!response.ok) { throw new Error(`SEDP API 요청 실패 (${endpoint}): ${response.status} ${response.statusText}`); } return response.json(); } catch (error) { console.error(`SEDP API 오류 (${endpoint}):`, error); throw error; } }