Skip to content

[FEAT] 웹 소켓 통신을 위한 기반 코드 추가#436

Open
i-meant-to-be wants to merge 20 commits intodevelopfrom
feat/#433
Open

[FEAT] 웹 소켓 통신을 위한 기반 코드 추가#436
i-meant-to-be wants to merge 20 commits intodevelopfrom
feat/#433

Conversation

@i-meant-to-be
Copy link
Copy Markdown
Contributor

@i-meant-to-be i-meant-to-be commented Mar 28, 2026

🚩 연관 이슈

closed #433

📝 작업 내용

웹 소켓 통신을 위한 기반 코드를 추가합니다! 크게 아래의 내용들로 구성되어 있습니다:

  • 소켓 구현을 위한 타입 정의
  • 소켓 메시지 전송을 위한 페이로드 인터페이스 정의
  • 소켓 연결을 담당하는 SocketManager 구현
  • React 단에서 소켓 사용을 위해 React 훅 useSocket 구현
  • 테스트 코드 추가

또한 대부분의 함수에 JSDoc을 통해 자세한 함수 사용 방법을 남겨 두었습니다. 아무래도 웹 소켓 관련 기술 논의가 저랑 콜리 사이에서만 진행되었고 그 내용이 프론트에 명시적으로 공유된 게 아니기 때문에, 코드 리뷰 또는 향후 제가 작업한 소켓 코드들을 재활용하실 때 모르는 내용이 있으실까봐 최대한 설명을 상세히 적어 두었으니 참고 바랍니다. 그럼에도 궁금한 점이 있으시다면, PR 리뷰나 Discord를 통해 편하게 질의 남겨주세요.

소켓 구현을 위한 타입 정의

소켓 통신만을 위한 타입과 인터페이스를 src/apis/sockets/type.ts에 정의했습니다. 소켓 이벤트는 크게 타이머 데이터가 담기는 "타이머 이벤트"와 타이머 데이터가 담기지 않는 "비-타이머 이벤트"로 나뉘는데요. 각 이벤트들에 대한 설명은 아래와 같습니다:

타이머 이벤트 타입 TimerEventTypes

  • NEXT 다음 차례로 넘기기
  • STOP 타이머 정지
  • BEFORE 이전 차례로 돌리기
  • PLAY 타이머 시작
  • RESET 타이머 초기화
  • TEAM_SWITCH 자유 토론 중 팀 전환

비-타이머 이벤트 타입 NonTimerEventType

  • FINISHED 토론 종료
  • ERROR 서버 측에서 발생한 오류

소켓 메시지 전송을 위한 페이로드 인터페이스 정의

{
  "eventType": "NEXT",
  "data" : {
	  "timerType": "NORMAL",
	  "sequence": 2,
	  "currentTeam": null,
	  "remainingTime": 180
  }
}

{
  "eventType": "FINISHED",
  "data" : null
}

소켓 통신 시 서버와 주고받을 메시지의 형식을 src/apis/sockets/type.ts에 정의했습니다. 기본적으로 모든 페이로드는 이벤트와 관계 없이 아래와 공통 구조를 가집니다:

공통 메시지 구조 SocketMessage

export type SocketMessage =
  | {
      eventType: TimerEventTypes;
      data: TimerDataPayload;
    }
  | {
      eventType: NonTimerEventType;
      data: null;
    };

주목하셔야 할 부분은 타이머 이벤트 타입에 따라 data가 달라진다는 거예요! 만약 이벤트가 타이머 정보를 필요로 하지 않는 NonTimerEventType일 경우 datanull로 지정돼요. 그러나 타이머를 조작하는 등 타이머 정보를 필요로 하는 경우, data는 바로 아래에 있는 타이머 데이터를 기술하는 TimerDataPayload로 지정됩니다.

타이머 정보를 담는 페이로드 TimerDataPayload

export interface TimerDataPayload {
  /** 타이머 유형 (자유토론, 일반) */
  timerType: Omit<TimeBoxType, 'FEEDBACK'>;

  /** 현재 타이머 순서
   * - 0부터 시작
   * - `NEXT`, `BEFORE` 이벤트의 경우, 발언해야 할 차례의 타이머 순서를 의미
   */
  sequence: number;

  /** 현재 발언하고 있는 팀의 진영 */
  currentTeam: Stance;

  /** 남은 시간 (초 단위) */
  remainingTime: number;
}

소켓 연결을 담당하는 SocketManager 구현

소켓 연결을 직접 담당하는 SocketManager를 src/apis/sockets/SocketManager.ts에 구현했습니다. 이 매니저는 아래와 같은 기술적 특징을 지녀요:

싱글톤 구현

class SocketManager {
  // 인스턴스를 클래스 수준 전역 범위에 선언
  private static instance: SocketManager;

  // 싱글톤 패턴 적용
  // - 생성자를 차단하여 외부에서의 객체 생성을 막음
  private constructor() {}

  // - 오직 클래스 수준 변수에서만 인스턴스에 호출할 수 있게 제한
  public static getInstance(): SocketManager {
    if (!SocketManager.instance) {
      SocketManager.instance = new SocketManager();
    }
    return SocketManager.instance;
  }

  // 나머지 생략...
}

이 매니저는 싱글톤으로 작성되어 있습니다. 생성자는 외부에서 접근 불가능하게 private로 막혀 있고, 클래스 수준 함수인 getInstance를 통해서만 인스턴스를 공급받을 수 있어요.

소켓 설정 SocketOptions

소켓 사용 시 정책을 코드 안에서 유연하게 바꿀 수 있도록 인터페이스 SocketOptions를 추가했습니다. 각 옵션과 설명은 아래와 같아요:

  • maxRetires 최대 재시도 횟수 (기본값 3회)
  • baseRetryDelayMs 기본 재시도 대기 시간 (기본값 1000 ms)
  • heartbeatInMs 수신 하트비트 주기 (기본값 10000 ms)
  • heartbeatOutMs 발신 하트비트 주기 (기본값 10000 ms)

참고로, 이 모든 기본값들은 콜리와의 회의를 통해 저희 서비스 정책으로 고정된 것들입니다. 아마 웬만해서는 변동 가능성이 없을 것 같아요.

재시도 정책

재시도는 아래 정책에 따릅니다:

  • 최초 시도 후 3회까지 재시도 가능
  • 지나친 재시도 요청으로 인한 서버 부하를 막기 위해, 재시도에 쿨타임이 있음
  • 재시도 쿨타임은 (0, 최대 재시도 상한선) 사이 난수로 결정 (풀 지터 방식)
  • 최대 재시도 상한선은 1초 * 2 ^ 누적 재시도 횟수로 계산

또한 재시도를 위해 SocketManager와 이를 사용하는 훅 useSocket 간에 관찰자 패턴(observer pattern)을 적용했습니다.

  • 관찰자 (observer) | 훅 useSocket과 훅을 통해 수립된 구독들
  • 발행자 (publisher) | SocketManager

동작 방식은 아래와 같습니다:

  • 최초 구독 수립 시, 백업 리스트에 구독 내용(채널 이름과 채널에서 메시지 수신 시 실행할 콜백)을 저장
  • 동시에 관찰자는 발행자에게 리스너를 등록, 연결 수립 시 알림을 달라고 요청
  • 연결이 끊기고 이후 재연결 성공
  • 이 때 관찰자가 발행자에게 등록한 리스너가 활성화, 백업 리스트에 있던 모든 구독을 다시 복구함

React 단에서 소켓 사용을 위해 React 훅 useSocket 구현

React에서 소켓을 사용하기 위해 SocketManager를 더 고수준에서 다루는 훅 useSocket을 src/hooks/useSockets.ts에 추가했습니다.

이 훅은 SocketManager가 지원하는 여러 함수를 React 컴포넌트 수준에서 안전히 다룰 수 있게 해 주며, 무엇보다도 useEffect에 언마운트 시 소켓 연결을 끊도록 명시적으로 지정해 주어, 언마운트나 페이지 이동 시에도 메모리 누수가 발생하지 않게 구현되어 있습니다.

그 외 변경 사항

보안 문제 해결을 위한 패키지 의존성 업데이트 진행

🏞️ 스크린샷 (선택)

없음

🗣️ 리뷰 요구사항 (선택)

로직 및 테스트 스위트에 문제 없을지 확인해주시면 감사하겠습니다!

Summary by CodeRabbit

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 애플리케이션 전역 WebSocket 통신 관리자 및 재연결(백오프) 로직 추가
    • 컴포넌트용 소켓 훅으로 구독/구독해제 및 메시지 발행 지원
  • 개선 사항

    • Mock 서비스 워커 버전 업데이트 (2.12.7 → 2.12.14)
  • 테스트

    • 연결 수명주기, 재시도 백오프, 구독/발행 동작 검증 테스트 추가
  • Chores

    • 런타임 및 타입 지원 의존성 추가 (STOMP/SockJS 관련)

@i-meant-to-be i-meant-to-be requested review from jaeml06 and useon March 28, 2026 07:53
@i-meant-to-be i-meant-to-be self-assigned this Mar 28, 2026
@i-meant-to-be i-meant-to-be added feat 기능 개발 chore 파일 내부가 아닌 파일 자체에 대한 변경 (디렉토리 이동, 파일 이름 변경, 파일 삭제 등) test 테스트 코드 관련 labels Mar 28, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 28, 2026

🚀 Preview 배포 완료!

환경 URL
Preview 열기
API Dev 환경

PR이 닫히면 자동으로 정리됩니다.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a STOMP-based WebSocket communication system, featuring a singleton SocketManager for global connection handling and a useSocket React hook for component-level interactions. The implementation includes automated reconnection logic with exponential backoff and comprehensive unit tests. Key feedback includes addressing a potential issue where the hook's cleanup logic might prematurely disconnect the global socket for other components, removing unused configuration options like jitterMs, restricting debug logs to development environments, and making STOMP headers optional in the publish function to match the underlying API.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

STOMP 기반 WebSocket 클라이언트 스택이 추가되었습니다: 런타임/타입 의존성 추가, 싱글톤 SocketManager, 메시지 타입 정의, React 훅(useSocket), 관련 단위 테스트 및 MockServiceWorker 버전 업데이트가 포함됩니다.

Changes

Cohort / File(s) Summary
의존성
package.json
@stomp/stompjs, sockjs-client 런타임 의존성 추가 및 @types/sockjs-client 개발 의존성 추가.
Mock 설정
public/mockServiceWorker.js
PACKAGE_VERSION 상수를 2.12.72.12.14로 업데이트.
타입 정의
src/apis/sockets/type.ts
타이머 이벤트/비타이머 이벤트 유니온, TimerDataPayloadSocketMessage 판별 유니온 추가.
SocketManager 구현
src/apis/sockets/SocketManager.ts
싱글톤 STOMP-over-WebSocket 연결 관리자 추가: connect/disconnect, subscribe/publish, 재연결(지수 백오프 + 재시도 제한), heartbeat, onConnect 콜백 및 내부 상태 관리.
React Hook
src/hooks/useSocket.ts
컴포넌트 스코프 구독 추적(Map), socketManager 래퍼 함수(connect, disconnect, subscribe, unsubscribe, publish), 재연결 시 구독 재등록 및 언마운트 정리.
테스트
src/apis/sockets/SocketManager.test.ts
Vitest 테스트 추가: sockjs-client@stomp/stompjs 모킹, 연결/해제 수명주기, 재연결 백오프 로직, 구독/발행 동작 검증.

Sequence Diagram(s)

sequenceDiagram
    actor React as React Component
    participant Hook as useSocket Hook
    participant Manager as SocketManager
    participant STOMP as STOMP Client
    participant Server as WebSocket Server

    React->>Hook: connect(options)
    Hook->>Manager: connect(options)
    Manager->>STOMP: new Client(), configure, activate()
    STOMP->>Server: WebSocket 연결 / STOMP CONNECT
    Server-->>STOMP: CONNECTED
    STOMP-->>Manager: onConnect
    Manager-->>Hook: onConnect 이벤트 전달

    React->>Hook: subscribe(destination, cb)
    Hook->>Manager: subscribe(destination, cb)
    Manager->>STOMP: client.subscribe(destination, cb)
    Server-->>STOMP: MESSAGE
    STOMP-->>Manager: callback 호출
    Manager-->>Hook: 메시지 전달

    React->>Hook: publish(dest, msg)
    Hook->>Manager: publish(dest, msg)
    Manager->>STOMP: client.publish({destination: dest, body: JSON.stringify(msg)})

    Note over STOMP,Manager: 연결 종료 처리
    STOMP->>Manager: onWebSocketClose
    Manager->>Manager: calculateBackoffDelay() & set reconnectDelay
    Manager->>STOMP: activate() 재시도

    React->>Hook: disconnect()
    Hook->>Manager: disconnect()
    Manager->>STOMP: deactivate()
    Manager-->>Hook: 구독 정리 완료
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • useon

Poem

🐰 깡충, 새 소켓이 열렸네
토끼가 팅커벨처럼 부지런히 폴링해요 ✨
메시지 오가니 심장이 뛰고
백오프 숨고르기, 다시 뛰어봐요
🥕🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 웹 소켓 통신을 위한 기반 코드 추가라는 주요 변경 사항을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed PR은 #433의 모든 주요 목표를 충족합니다: stompjs 라이브러리 도입(@stomp/stompjs 추가), 응답 형식 및 메시지 규격 정의(SocketMessage, TimerDataPayload), 연결/재시도/하트비트 관리(SocketManager), 프론트용 훅 구현(useSocket), 테스트 코드 추가.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 #433의 STOMP 베이스 코드 추가 목표와 직접 관련되어 있습니다: 타입 정의, SocketManager 구현, useSocket 훅, 테스트 추가, 의존성 업데이트가 모두 웹 소켓 통신 기반 구축을 위한 범위 내 변경입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#433

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/apis/sockets/SocketManager.ts (1)

18-19: jitterMs는 현재 dead config입니다.

Line 19Line 37에서 공개한 jitterMsLine 217-221의 계산식에서 한 번도 쓰이지 않아, 호출자가 값을 바꿔도 재연결 동작이 달라지지 않습니다. 풀 지터만 유지할 계획이면 이 옵션과 문서를 제거하고, 아니라면 계산식에 실제로 반영해 주세요.

Also applies to: 35-40, 217-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apis/sockets/SocketManager.ts` around lines 18 - 19, The jitterMs option
declared on the SocketManager (property jitterMs) is unused in the reconnect
delay computation around the reconnect logic (currently at the block handling
delay math), so either remove the jitterMs option and its docs if you intend to
keep only full random jitter, or modify the reconnect delay calculation in the
SocketManager reconnect/backoff routine to incorporate jitterMs; locate the
class SocketManager and update the function that computes the reconnection delay
(the block around the existing delay math used at reconnect time) to apply a
random offset scaled by this.jitterMs (or use it as max jitter) so
caller-provided jitterMs actually affects retry timing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apis/sockets/SocketManager.test.ts`:
- Around line 38-45: The mock client starts with active = true which masks the
duplicate-connection guard; change the MockClient so its active property
initializes to false and ensure the activate and deactivate mocks actually
toggle that property (i.e., wrap or replace activate/deactivate vi.fn()
implementations to set this.active = true/false respectively) so tests exercise
real state transitions for MockClient and let connect()/disconnect() guards be
validated.

In `@src/apis/sockets/type.ts`:
- Around line 19-21: The TimerDataPayload interface uses Omit on a string union
which is invalid; replace the type on the timerType property to use
Exclude<TimeBoxType, 'FEEDBACK'> so that timerType is narrowed from TimeBoxType
to 'NORMAL' | 'TIME_BASED' (update the TimerDataPayload interface and the
timerType property accordingly).

In `@src/hooks/useSocket.ts`:
- Around line 72-84: The cleanup currently calls the global
socketManager.disconnect() inside useSocket's effect cleanup, which will close
the shared singleton connection for all consumers; remove the
socketManager.disconnect() call from the return cleanup in useSocket so the hook
only unsubscribes its own subscriptions (subscriptions.current.forEach(...);
subscriptions.current.clear()) and leave connection lifecycle to the
higher-level owner/provider (or add an explicit ownership check if you intend
this hook to sometimes own the socket). Ensure the unique symbols referenced are
useEffect/useSocket, subscriptions.current, and socketManager.disconnect.
- Around line 21-24: The publish callback in useSocket is overly strict by
requiring headers, which prevents header-less publishes; update the useCallback
signature for publish to accept headers?: StompHeaders (making headers optional)
and ensure the forwarding call uses the same parameters when calling
SocketManager.publish so the hook's types match SocketManager.publish and allow
calls without headers.
- Around line 48-58: The current dedupe uses
subscriptions.current.has(destination) which blocks resubscribe after a
disconnect because the old entry remains; update the subscribe callback to
retrieve the existing subscription (subscriptions.current.get(destination)) and
only skip creating a new subscription if that existing subscription is still
active (e.g., check a truthy active property or absence of a closed flag /
presence of an unsubscribe method on the stored object). If the stored
subscription is inactive or closed, call socketManager.subscribe(destination,
callback) to create a new one and replace the entry in subscriptions.current;
reference the subscribe function, subscriptions.current, and
socketManager.subscribe to locate and change the logic.

---

Nitpick comments:
In `@src/apis/sockets/SocketManager.ts`:
- Around line 18-19: The jitterMs option declared on the SocketManager (property
jitterMs) is unused in the reconnect delay computation around the reconnect
logic (currently at the block handling delay math), so either remove the
jitterMs option and its docs if you intend to keep only full random jitter, or
modify the reconnect delay calculation in the SocketManager reconnect/backoff
routine to incorporate jitterMs; locate the class SocketManager and update the
function that computes the reconnection delay (the block around the existing
delay math used at reconnect time) to apply a random offset scaled by
this.jitterMs (or use it as max jitter) so caller-provided jitterMs actually
affects retry timing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 520c108b-671d-488b-8e66-8d4bc06908ea

📥 Commits

Reviewing files that changed from the base of the PR and between 8aaff3a and 1b7ebfd.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • package.json
  • public/mockServiceWorker.js
  • src/apis/sockets/SocketManager.test.ts
  • src/apis/sockets/SocketManager.ts
  • src/apis/sockets/type.ts
  • src/hooks/useSocket.ts

Copy link
Copy Markdown
Contributor

@jaeml06 jaeml06 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 리뷰: 4가지 항목에 대한 피드백입니다.

Copy link
Copy Markdown
Contributor

@jaeml06 jaeml06 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 한번 확인해주세요.

@i-meant-to-be i-meant-to-be requested a review from jaeml06 March 30, 2026 03:03
@i-meant-to-be
Copy link
Copy Markdown
Contributor Author

i-meant-to-be commented Mar 30, 2026

/noti @jaeml06 짚어주신 부분 반영하였습니다! 빠른 리뷰 감사합니당 ~_~

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apis/sockets/SocketManager.ts`:
- Around line 69-75: The new connect() call must start a fresh retry session:
reset the retryCount to zero when initiating a new connection (inside
SocketManager.connect() right after merging currentOptions and/or when a fresh
client connection begins) so previous exhausted retries aren't carried forward;
ensure the same reset is applied where manual reconnects are kicked off (related
code around handleReconnection() and the reconnection start path referenced near
lines 183-190) so handleReconnection() uses the new zeroed retryCount for its
retry loop.
- Around line 211-215: The current calculateBackoffDelay can return 0 (disabling
reconnect in `@stomp/stompjs`); update calculateBackoffDelay to clamp the result
to a minimum of 1ms: compute maxExponentialDelay as now (using
this.currentOptions.baseRetryDelayMs and currentRetryCount), ensure
maxExponentialDelay is at least 1, then return Math.max(1,
Math.floor(Math.random() * maxExponentialDelay)). Reference the
calculateBackoffDelay method and this.currentOptions.baseRetryDelayMs when
making the change.

In `@src/hooks/useSocket.ts`:
- Around line 27-29: The hook currently calls socketManager.connect(options) in
the connect useCallback but only cleans up local subscriptions, leaving
callbacks like onConnect registered inside the singleton socketManager and
causing stale callbacks after unmount; update the hook so it registers a
reconnect/onConnect handler tied to the hook lifecycle and removes it on cleanup
— e.g., have connect (in useCallback) register a wrapper callback with
socketManager (or call a socketManager.connect variant that returns an
unsubscribe/token) and store that token in hook state, then remove the handler
in the effect cleanup by calling the corresponding socketManager.off/unsubscribe
with that token; ensure any places referenced (connect useCallback,
socketManager.connect, onConnect handlers and the cleanup logic around lines
noted) consistently register and deregister the reconnect handler so callbacks
don’t persist after unmount.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e02894f2-ff35-4401-9779-d4f7ce10a796

📥 Commits

Reviewing files that changed from the base of the PR and between 1b7ebfd and f9469ab.

📒 Files selected for processing (4)
  • src/apis/sockets/SocketManager.test.ts
  • src/apis/sockets/SocketManager.ts
  • src/apis/sockets/type.ts
  • src/hooks/useSocket.ts
✅ Files skipped from review due to trivial changes (1)
  • src/apis/sockets/type.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/apis/sockets/SocketManager.test.ts

Copy link
Copy Markdown
Contributor

@jaeml06 jaeml06 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다. 반영감사합니다. 추가로 코드래빗이 리뷰가 달려있으니 그 부분만 한번 확인해주시면 감사하겠습니다.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apis/sockets/SocketManager.ts`:
- Around line 110-113: The onWebSocketClose handler currently reads and mutates
mutable this.client so a late-arriving close event from an old connection can
corrupt a new session's retry state; modify the reconnection/close handlers to
capture the specific Client instance when you create it (e.g., const client =
this.client or pass the instance into onWebSocketClose) and have
onWebSocketClose operate only on that captured client instead of this.client,
and ensure you unregister/remove the old handlers when replacing the client so
retryCount and reconnectDelay on the new Client are not touched by stale events
(apply the same pattern where similar handlers are registered around lines
referenced by 139-146 and 206-225).
- Around line 26-27: The declared onConnect? callback in SocketManager is never
invoked: although connect() stores the option and connectListeners are called on
success, onConnect is omitted; modify the successful connection flow inside the
connect() method (and the same success paths referenced by connectListeners at
the other locations) to call this.onConnect?.() after or before invoking
connectListeners to ensure the documented completion hook runs; ensure optional
chaining and null checks around onConnect so it remains optional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 545e3796-869b-4a89-8388-0a24d68766d2

📥 Commits

Reviewing files that changed from the base of the PR and between f9469ab and ddf4f80.

📒 Files selected for processing (3)
  • src/apis/sockets/SocketManager.test.ts
  • src/apis/sockets/SocketManager.ts
  • src/hooks/useSocket.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/useSocket.ts
  • src/apis/sockets/SocketManager.test.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/apis/sockets/SocketManager.ts (1)

26-32: 불필요한 Omit 타입 사용

SocketOptions 인터페이스에 onConnect 속성이 없으므로 Omit<SocketOptions, 'onConnect'>는 불필요합니다. 코드 가독성을 위해 단순화하는 것이 좋겠습니다.

♻️ 제안 수정
-const DEFAULT_OPTIONS: Required<Omit<SocketOptions, 'onConnect'>> = {
+const DEFAULT_OPTIONS: Required<SocketOptions> = {
   maxRetries: 3,
   baseRetryDelayMs: 1000,
   heartbeatInMs: 10000,
   heartbeatOutMs: 10000,
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apis/sockets/SocketManager.ts` around lines 26 - 32, The DEFAULT_OPTIONS
constant currently uses an unnecessary Omit (Required<Omit<SocketOptions,
'onConnect'>>): remove the Omit and simplify the type to Required<SocketOptions>
(or an appropriate Required<Pick<SocketOptions,
'maxRetries'|'baseRetryDelayMs'|'heartbeatInMs'|'heartbeatOutMs'>> if you want
to be explicit), update the DEFAULT_OPTIONS declaration to use that simplified
type, and ensure the object shape remains unchanged; reference the
DEFAULT_OPTIONS constant and the SocketOptions type when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apis/sockets/SocketManager.ts`:
- Around line 102-103: The code builds wsUrl from
import.meta.env.VITE_API_BASE_URL without validating it, which yields
"undefined/ws" if the env var is missing; in SocketManager.ts locate the wsUrl
construction (symbol: wsUrl) and add a check that
import.meta.env.VITE_API_BASE_URL is a non-empty string, and if not either throw
a clear Error or call the module's logger (or throw new Error) with a message
like "VITE_API_BASE_URL is not set" before attempting to use wsUrl; ensure the
check prevents creating the WebSocket URL and downstream connection attempts
when the env is invalid.
- Around line 214-222: When max retries are exceeded in SocketManager (check:
retryCount vs currentOptions.maxRetries), deactivate() alone can still allow the
stale client's onWebSocketClose to fire and re-enter handleReconnection; after
calling this.client.deactivate() set this.client = null (or otherwise mark the
manager as permanently closed and remove the client's listeners) to prevent the
stale client from passing the `this.client !== newClient` check and
re-triggering reconnection logic (update the block that sets
this.client.reconnectDelay and calls this.client.deactivate()).

---

Nitpick comments:
In `@src/apis/sockets/SocketManager.ts`:
- Around line 26-32: The DEFAULT_OPTIONS constant currently uses an unnecessary
Omit (Required<Omit<SocketOptions, 'onConnect'>>): remove the Omit and simplify
the type to Required<SocketOptions> (or an appropriate
Required<Pick<SocketOptions,
'maxRetries'|'baseRetryDelayMs'|'heartbeatInMs'|'heartbeatOutMs'>> if you want
to be explicit), update the DEFAULT_OPTIONS declaration to use that simplified
type, and ensure the object shape remains unchanged; reference the
DEFAULT_OPTIONS constant and the SocketOptions type when making this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7ee7100-a84b-47b0-b775-e86d3b7ec44d

📥 Commits

Reviewing files that changed from the base of the PR and between ddf4f80 and 3aaea89.

📒 Files selected for processing (1)
  • src/apis/sockets/SocketManager.ts

@i-meant-to-be
Copy link
Copy Markdown
Contributor Author

/noti @jaeml06

바쁘실 것으로 알아 죄송합니다만, 로직에 조금 규모 있는 변경이 있어 리뷰를 다시 요청드립니다. 변경 사항을 요약해드리면 안정적인 재시도 및 구독 유지를 위한 관찰자 패턴(observer pattern) 도입이며, 보셔야 할 코드는 다음과 같습니다:

  • src/apis/sockets/SocketManager.ts 54~82번 줄
  • src/hooks/useSocket.ts 58번 줄 이후 전부

위에 적어둔 부분만 확인해주시면 됩니다! 다른 부분에 대한 노티를 드려야 할 정도로 큰 변경은 이것 말고는 없습니다.

@i-meant-to-be i-meant-to-be requested a review from jaeml06 March 30, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore 파일 내부가 아닌 파일 자체에 대한 변경 (디렉토리 이동, 파일 이름 변경, 파일 삭제 등) feat 기능 개발 test 테스트 코드 관련

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[FEAT] STOMP 베이스 코드 추가

2 participants