Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 1. API 주소 (http/https)
VITE_API_BASE_URL=https://dilemmai-idl.com

# 2. 웹소켓 주소 (ws/wss)
# 주소 뒤에 /ws/voice 등을 붙여서 쓰기 위해 도메인만 관리
VITE_WS_BASE_URL=wss://dilemmai-idl.com
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 29 additions & 2 deletions src/WebRTCProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,11 @@ function maskIceServersForLog(iceServers) {
// WebRTC Context 생성
const WebRTCContext = createContext();

/**
* 시그널링용 웹소켓 베이스 주소를 환경변수에서 가져옵니다.
*/
const WS_BASE = import.meta.env.VITE_WS_BASE_URL || 'wss://dilemmai-idl.com';

// 재연결 그레이스 상수 (ms)
const RECONNECT_GRACE_MS = 20000; // 20초

Expand Down Expand Up @@ -1292,7 +1297,7 @@ const WebRTCProvider = ({ children }) => {

// ----------------------------
// Offer 충돌(글레어) 처리용 유틸 (Perfect Negotiation - 간소화)
// - 서버가 peers/join을 누구에게 어떻게 브로드캐스트하든, 양쪽이 offer를 만들어도 안전하게 수렴하게
// - 서버가 peers/join을 누구에게 어떻게 브로드캐스트하든, 양쪽이 offer를 만들어도 안전하게 수락하게
// - user_id가 숫자일 가능성이 높으니 숫자 비교 우선, 아니면 문자열 비교
// ----------------------------
function comparePeerIds(a, b) {
Expand Down Expand Up @@ -1574,8 +1579,11 @@ const WebRTCProvider = ({ children }) => {

connectionAttemptedRef.current = true;

/**
* 하드코딩된 주소를 환경변수(VITE_WS_BASE_URL) 기반으로 변경
*/
const urlsToTry = [
`wss://dilemmai-idl.com/ws/signaling?room_code=${roomCode}&token=${token}`,
`${WS_BASE}/ws/signaling?room_code=${roomCode}&token=${token}`,
];

console.log(`🔌 [${providerId}] 시그널링 WebSocket 연결 시작 (User 토큰 기반)`);
Expand Down Expand Up @@ -2726,3 +2734,22 @@ useEffect(() => {
};

export default WebRTCProvider;

// 유틸함수
export function disconnectWebRTCVoice(peerConnectionsMap) {
if (!peerConnectionsMap) return;
const iterable = peerConnectionsMap instanceof Map
? peerConnectionsMap.values()
: Object.values(peerConnectionsMap);
for (const pc of iterable) {
try {
pc.getSenders().forEach(s => { if (s.track?.kind === 'audio') s.track.stop(); });
pc.close();
} catch (e) { console.error(e); }
}
}

/**
* 1. 상단에 WS_BASE 상수를 선언하고 VITE_WS_BASE_URL 환경변수를 적용함.
* 2. connectSignalingWebSocket 함수 내 하드코딩된 'wss://dilemmai-idl.com' 주소를 WS_BASE 변수로 대체함.
*/
29 changes: 17 additions & 12 deletions src/WebSocketProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { useNavigate } from 'react-router-dom';

const WebSocketContext = createContext();

/**
* 웹소켓 베이스 주소를 환경변수에서 가져옴.
*/
const WS_BASE = import.meta.env.VITE_WS_BASE_URL || 'wss://dilemmai-idl.com';

export const useWebSocket = () => {
const context = useContext(WebSocketContext);
if (!context) {
Expand Down Expand Up @@ -450,7 +455,10 @@ export const WebSocketProvider = ({ children }) => {

try {
console.log(`🔌 [${providerId}] WebSocket 연결 시도:`, currentSessionId);
const wsUrl = `wss://dilemmai-idl.com/ws/voice/${currentSessionId}?token=${accessToken}`;
/**
*하드코딩된 주소를 환경변수(VITE_WS_BASE_URL) 기반으로 변경
*/
const wsUrl = `${WS_BASE}/ws/voice/${currentSessionId}?token=${accessToken}`;
console.log(`🔗 [${providerId}] WebSocket URL:`, wsUrl.replace(accessToken, 'TOKEN_HIDDEN'));

const socket = new WebSocket(wsUrl);
Expand Down Expand Up @@ -482,7 +490,7 @@ export const WebSocketProvider = ({ children }) => {
reconnectAttemptsRef.current = 0;
setReconnectAttempts(0);
reconnectDelay.current = 1000;
finalizedRef.current = false;
finalizedRef.current = false;
if (reconnectTimer.current) {
clearTimeout(reconnectTimer.current);
reconnectTimer.current = null;
Expand All @@ -497,12 +505,6 @@ export const WebSocketProvider = ({ children }) => {
}
};
sendMessage(initPayload);
// pingIntervalRef.current = setInterval(() => {
// if (ws.current?.readyState === WebSocket.OPEN) {
// ws.current.send(JSON.stringify({ type: 'ping' }));
// console.log(`🏓 ping 전송`);
// }
// }, 30000);
};

socket.onmessage = (event) => {
Expand Down Expand Up @@ -582,17 +584,17 @@ export const WebSocketProvider = ({ children }) => {
finalizeDisconnection('네트워크 불안정으로 연결을 복구하지 못했습니다. 메인으로 돌아갑니다.');
}
};
} catch (error) { // ✅ connect() 내부 try-catch의 catch 추가
} catch (error) {
isConnecting.current = false;
if (!isReconnect) {
connectionAttempted.current = false;
}
console.error(`❌ [${providerId}] WebSocket 연결 실패:`, error);
setIsConnected(false);
}
}; // ✅ 여기서 connect 함수 끝
};

const disconnect = () => { // ✅ 이제 connect 밖의 동일 스코프
const disconnect = () => {
isManuallyDisconnected.current = true;
hasJoinedSession.current = false;

Expand All @@ -614,7 +616,7 @@ export const WebSocketProvider = ({ children }) => {
}
};


// 🔧 음성 세션 초기화 함수 중복 방지 강화
const initializeVoiceWebSocket = async (isHost = false) => {
// 🔧 가드 1: 이미 초기화 중
Expand Down Expand Up @@ -910,3 +912,6 @@ export const WebSocketProvider = ({ children }) => {
};

export default WebSocketProvider;


//하드코딩된 'wss://dilemmai-idl.com' 주소를 VITE_WS_BASE_URL 환경변수로 대체
43 changes: 15 additions & 28 deletions src/api/axiosInstance.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// api/axiosInstance.js
import axios from 'axios';
// Vite 환경변수로 API baseURL을 바꿀 수 있게 함 (로컬/스테이징/프로덕션 공용)
// 예) VITE_API_BASE_URL=http://localhost:8000
const API_BASE = (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_API_BASE_URL)
? String(import.meta.env.VITE_API_BASE_URL).replace(/\/+$/, '')
: 'https://dilemmai-idl.com';

/**
* 하드코딩된 주소를 환경변수로 분리
* Vite 환경이므로 import.meta.env를 사용
* .env 파일에 VITE_API_BASE_URL 설정이 없으면 기존 주소를 기본값으로 사용
*/
const API_BASE = (import.meta.env && import.meta.env.VITE_API_BASE_URL
? String(import.meta.env.VITE_API_BASE_URL)
: 'https://dilemmai-idl.com'
).replace(/\/+$/, '');

// 메인 axios 인스턴스 생성
const instance = axios.create({
Expand All @@ -15,6 +19,7 @@ const instance = axios.create({
// CORS 관련 설정 추가 (이미지 로드 문제 대응)
withCredentials: false, // 쿠키를 보내지 않으면 CORS가 더 관대함
});

// 상단 어딘가 공통 상수로 추가
const MAX_500_REFRESH_RETRY = 1; // 500에서 refresh 시도 최대 횟수 (원하면 2로 올려도 됨)

Expand Down Expand Up @@ -52,9 +57,7 @@ const refreshAccessToken = async () => {
if (!refreshToken) throw new Error('No refresh token available');

try {
// const { data } = await axios.post(`${API_BASE}/auth/refresh`, {
// refresh_token: refreshToken,
// });
// 하드코딩된 문자열 대신 변수화된 API_BASE 사용
const { data } = await axios.post(
`${API_BASE}/auth/refresh`, // 1. URL
{ // 2. body (data)
Expand All @@ -73,7 +76,7 @@ const refreshAccessToken = async () => {
// 1) 응답에서 받은 토큰들을 즉시 저장
if (data.access_token) localStorage.setItem('access_token', data.access_token);
if (data.refresh_token) localStorage.setItem('refresh_token', data.refresh_token);
if (data.token_type) localStorage.setItem('token_type', data.token_type);
if (data.token_type) localStorage.setItem('token_type', data.token_type);

// 2) axios 인스턴스 기본 헤더도 업데이트
const authValue = `${data.token_type || 'Bearer'} ${data.access_token}`;
Expand Down Expand Up @@ -193,21 +196,8 @@ instance.interceptors.response.use(
}
);

// export async function callChatbot({ step, input, context, prompt }) {
// const payload = { step, input, context, prompt };
// const { data } = await instance.post(
// "/chat/with-prompt",
// payload,
// {
// headers: {
// "Content-Type": "application/json",
// },
// }
// );
// // 서버 응답 예:
// // { "step": "question", "text": "...", "raw": {...} }
// return data;
// }
// export async function callChatbot({ step, input, context, prompt }) { ... }

export async function callChatbot({ session_id, user_input, step, variable, context }) {
const payload = {
session_id,
Expand All @@ -228,7 +218,4 @@ export async function callChatbot({ session_id, user_input, step, variable, cont
return data;
}




export default instance;
19 changes: 19 additions & 0 deletions src/assets/CardButton.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/PeopleIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading