1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
// 환경 변수
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<string> {
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<string> {
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<unknown> {
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;
}
}
|