Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3fc7a0b
Refactor: replace hardcoded URLs with environment variables
HIH-01 Feb 15, 2026
6a80dd2
Refactor: apply env variables and language updates
HIH-01 Feb 20, 2026
f5fb222
feat: complete IntroductionPopup design and multi-language logic
HIH-01 Feb 22, 2026
e6ce271
fix: resolve all merge conflicts and update localization
HIH-01 Feb 22, 2026
4475821
Merge remote-tracking branch 'upstream/main' into feature/env-refacto…
HIH-01 Feb 22, 2026
b4b2e86
충돌 해결 및 환경 변수/다국어 로직 병합 완료
HIH-01 Feb 22, 2026
39c2145
게임 소개 팝업 영문 번역 적용 및 이미지 교체
HIH-01 Feb 26, 2026
bb3771d
게임 소개 안내 팝업 글꼴 수정
HIH-01 Feb 27, 2026
bf41a9b
pretend 폰트 파일 로드 코드 추가
HIH-01 Feb 27, 2026
29b48a6
코드 정리 및 인트로 파트 코드 보완
HIH-01 Mar 1, 2026
a927bd2
음성 관련 로직 및 언어값 호출 일부 임시 보완
HIH-01 Mar 1, 2026
7a19047
캐릭터 설명 페이지 코드 추가 보완
HIH-01 Mar 1, 2026
d63effe
언어값 로드 방식 개선
HIH-01 Mar 1, 2026
923b9ec
캐릭터 설명 페이지 로드 오류 보완
HIH-01 Mar 1, 2026
b7989db
개인 캐릭터 설명 페이지 보완
HIH-01 Mar 1, 2026
89f7317
MateName import 경로 수정
HIH-01 Mar 2, 2026
e45607f
깃헙 오류 로그 표시 로직 추가
HIH-01 Mar 2, 2026
e70d7b2
오류 로그 확인용 커밋
HIH-01 Mar 2, 2026
64251ac
변수 스코프 문제로 인한 런타임 오류 해결 및 매핑 우선순위 보정
HIH-01 Mar 3, 2026
82a2a55
개인 캐릭터 소개 파트 텍스트 누락 오류 수정
HIH-01 Mar 3, 2026
cd8ce55
일부 영문 미출력 부분 개선 및 설정된 안드로이드 이름 정상 출력 적용
HIH-01 Mar 4, 2026
5691de1
category 호출 및 저장값 통일
HIH-01 Mar 11, 2026
24c20e2
waitingroom 영문 대응 이미지 에셋 추가
HIH-01 Mar 11, 2026
8a794f4
라운드 네비게이션 로직 보완 및 세션 데이터 정리 로직 업데이트
HIH-01 Mar 12, 2026
fe8f16e
Heder Frame UI 위치 조절 및 텍스트 출력 속도 조절
HIH-01 Mar 14, 2026
888a45c
텍스트 출력 속도 추가 조정
HIH-01 Mar 14, 2026
cd17aa5
엔딩 페이지 텍스트 출력 방식 변경 및 특정 페이지 UI 삭제 및 위치 조절
HIH-01 Mar 16, 2026
d0c7835
mictestpopup 창 닫기 관련 오류 수정
HIH-01 Mar 17, 2026
c3e56c6
사이드바 프로필 영문 텍스트 일부 수정 및 음성 파일 다운로드 로직 주석 처리
HIH-01 Mar 23, 2026
badddf2
OutPopup 알람 메시지 서버 호출 방식에서 프론트엔드 출력으로 변경
HIH-01 Mar 23, 2026
b207422
텍스트 수정 및 기타 오류 수정
HIH-01 Jun 11, 2026
ba86c30
텍스트 수정 및 텍스트 박스 유동성 전환
HIH-01 Jun 13, 2026
28289c5
이미지 에셋 교체 및 텍스트 수정
HIH-01 Jun 15, 2026
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
28 changes: 28 additions & 0 deletions .github/workflows/build-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 본 워크플로우는 GitHub Actions를 사용하여 코드 푸시 및 풀 리퀘스트 시 자동으로 빌드 체크를 수행함
#빌드 과정에서 발생하는 에러는 로그에 기록되어 깃헙 레포지토리-Actions 탭-워크플로우의 푸시 내역에서 확인 가능
name: Build Check

on:
push:
branches: [ "main", "feature/*" ] # 푸시할 때마다 실행
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20' # 프로젝트 Node 버전과 맞춤

- name: Install dependencies
run: npm install

- name: Run build check
run: npm run build # 여기서 에러가 나면 로그가 기록됩니다.
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.

98 changes: 69 additions & 29 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 @@ -1409,9 +1414,14 @@ const WebRTCProvider = ({ children }) => {
};

pc.onconnectionstatechange = () => {
console.log(`PC(${key}) connectionState=`, pc.connectionState);
console.log(`[${providerId}] PC(${key}) connectionState=`, pc.connectionState);

// ✅ 추가: 상태가 변할 때마다 리액트의 peerConnections 상태를 갱신합니다.
// 이 코드가 있어야 GameIntro의 peerCount가 0에서 2로 올라갑니다.
setPeerConnections(new Map(pcsRef.current));

if (['disconnected', 'failed', 'closed'].includes(pc.connectionState)) {
// 필요시 정리
// 필요 시 정리 로직 유지
}
};

Expand Down Expand Up @@ -1574,8 +1584,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 All @@ -1598,7 +1611,7 @@ const WebRTCProvider = ({ children }) => {
ws.close();
tryConnection(urlIndex + 1);
}
}, 3000);
}, 5000);
ws.onopen = () => {
clearTimeout(connectionTimeout);
console.log(`✅ [${providerId}] WebSocket 연결 성공 (signaling)`);
Expand Down Expand Up @@ -1635,15 +1648,15 @@ const WebRTCProvider = ({ children }) => {
if (msg.type === 'peers' && Array.isArray(msg.peers)) {
console.log('👥 [signaling] peers list:', msg.peers);
for (const otherId of msg.peers) {
if (!otherId) continue;
if (!otherId || String(otherId) === SELF()) continue;
// 레이스로 myPeerIdRef.current가 아직 null일 수 있으니 SELF() 기준으로 자기 자신 제외
if (String(otherId) === SELF()) continue;
// ✅ 원칙 (3): 글레어 방지 - userId 비교로 offer initiator 제한
// 🚨 임시 비활성화: 연결 테스트를 위해 글레어 방지를 우선 꺼둠
// if (!shouldInitiate(String(otherId))) {
// console.log(`⏭️ [signaling] 글레어 방지: ${SELF()} < ${otherId}, offer 스킵`);
// continue;
// }
if (!shouldInitiate(String(otherId))) {
console.log(`⏭️ [signaling] 글레어 방지: ${SELF()} < ${otherId}, offer 스킵`);
continue;
}
console.log(`📤 [signaling] peers → offer 생성 시작: ${SELF()} → ${otherId}`);
await createOfferTo(String(otherId));
}
Expand All @@ -1656,10 +1669,10 @@ const WebRTCProvider = ({ children }) => {
if (otherId === SELF()) return;
// ✅ 원칙 (3): 글레어 방지 - userId 비교로 offer initiator 제한
// 🚨 임시 비활성화: 연결 테스트를 위해 글레어 방지를 우선 꺼둠
// if (!shouldInitiate(otherId)) {
// console.log(`⏭️ [signaling] 글레어 방지: ${SELF()} < ${otherId}, offer 스킵 (join/joined)`);
// return;
// }
if (!shouldInitiate(otherId)) {
console.log(`⏭️ [signaling] 글레어 방지: ${SELF()} < ${otherId}, offer 스킵 (join/joined)`);
return;
}
console.log(`📤 [signaling] join/joined → offer 생성 시작: ${SELF()} → ${otherId}`);
await createOfferTo(otherId);
return;
Expand Down Expand Up @@ -2273,7 +2286,9 @@ const WebRTCProvider = ({ children }) => {
if (cancelled) return;
// 퇴장/종료 진행 중이면 절대 자동으로 녹음/초기화 재시작하지 않음 (레이스 방지)
if (voiceManager?.exitInProgress) return;

if (isInitialized || initializationPromiseRef.current) {
return;
}
// ✅ 0) WebRTC/세션 준비 전이라도 "로컬 녹음"은 먼저 켜서 시작점을 앞으로 당김
// - user가 말한 증상(마지막 1~2초만 녹음)은 보통 초반 init 실패로 발생
try {
Expand Down Expand Up @@ -2391,7 +2406,9 @@ const WebRTCProvider = ({ children }) => {
if (!(roomCode && nickname)) return;

if (!isReloadingGraceLocal()) return;

if (isInitialized || signalingConnected || initializationPromiseRef.current) {
return;
}
console.log(`♻️ [${providerId}] 페이지 새로고침 감지 — WebRTC 자동 재연결 시도 (grace)`);
const MAX_WAIT_MS = RECONNECT_GRACE_MS;
const RETRY_INTERVAL_MS = 2000;
Expand Down Expand Up @@ -2625,20 +2642,24 @@ const WebRTCProvider = ({ children }) => {
// 정리 useEffect (언마운트)
useEffect(() => {
return () => {
console.log(`🧹 [${providerId}] WebRTC Provider 정리 시작`);
peerConnections.forEach(pc => { pc.close(); });
if (signalingWsRef.current) {
signalingWsRef.current.close();
signalingWsRef.current = null;
if (voiceManager.exitInProgress) {
console.log(`🧹 [${providerId}] WebRTC Provider 정리 시작`);
peerConnections.forEach(pc => { pc.close(); });
if (signalingWsRef.current) {
signalingWsRef.current.close();
signalingWsRef.current = null;
}
const audioElements = document.querySelectorAll('audio[data-user-id]');
audioElements.forEach(audio => { audio.remove(); });
offerSentToRoles.current.clear();
offerReceivedFromRoles.current.clear();
pendingCandidates.current.clear();
console.log(`✅ [${providerId}] WebRTC Provider 정리 완료`);
} else {
console.log(`ℹ️ [${providerId}] WebRTC Provider 소프트 정리 (인스턴스 교체) - 연결 유지`);
}
const audioElements = document.querySelectorAll('audio[data-user-id]');
audioElements.forEach(audio => { audio.remove(); });
offerSentToRoles.current.clear();
offerReceivedFromRoles.current.clear();
pendingCandidates.current.clear();
console.log(`✅ [${providerId}] WebRTC Provider 정리 완료`);
};
}, []); // 마운트 시 한 번
}
}, [providerId, peerConnections]); // 마운트 시 한 번
// ----------------------------
// 디버그 유틸리티
// ----------------------------
Expand Down Expand Up @@ -2726,3 +2747,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 변수로 대체함.
*/
Loading
Loading