@vibexnpm/talkx 2.3.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +40 -1
- package/dist/talkflow-sdk.esm.js +1 -1
- package/dist/talkflow-sdk.esm.js.map +1 -1
- package/dist/talkflow-sdk.standalone.js +1 -1
- package/dist/talkflow-sdk.standalone.js.map +1 -1
- package/dist/talkflow-sdk.umd.js +1 -1
- package/dist/talkflow-sdk.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/chat/ChatClient.js +169 -0
- package/src/constants.js +2 -0
- package/src/talkflow/eventForwarding.js +1 -0
- package/types/index.d.ts +40 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"talkflow-sdk.standalone.js","sources":["../src/utils/EventEmitter.js","../src/constants.js","../src/utils/Logger.js","../src/utils/ApiClient.js","../src/utils/jwtUtils.js","../node_modules/@stomp/stompjs/esm6/byte.js","../node_modules/@stomp/stompjs/esm6/frame-impl.js","../node_modules/@stomp/stompjs/esm6/parser.js","../node_modules/@stomp/stompjs/esm6/types.js","../node_modules/@stomp/stompjs/esm6/ticker.js","../node_modules/@stomp/stompjs/esm6/versions.js","../node_modules/@stomp/stompjs/esm6/stomp-handler.js","../node_modules/@stomp/stompjs/esm6/augment-websocket.js","../node_modules/@stomp/stompjs/esm6/client.js","../node_modules/sockjs-client/lib/utils/random.js","../node_modules/sockjs-client/lib/utils/browser-crypto.js","../node_modules/sockjs-client/lib/utils/event.js","../node_modules/url-parse/index.js","../node_modules/requires-port/index.js","../node_modules/querystringify/index.js","../node_modules/sockjs-client/lib/utils/url.js","../node_modules/inherits/inherits_browser.js","../node_modules/sockjs-client/lib/event/eventtarget.js","../node_modules/sockjs-client/lib/event/emitter.js","../node_modules/sockjs-client/lib/transport/websocket.js","../node_modules/sockjs-client/lib/transport/browser/websocket.js","../node_modules/sockjs-client/lib/transport/lib/sender-receiver.js","../node_modules/sockjs-client/lib/transport/lib/buffered-sender.js","../node_modules/sockjs-client/lib/transport/lib/polling.js","../node_modules/sockjs-client/lib/transport/lib/ajax-based.js","../node_modules/sockjs-client/lib/transport/receiver/xhr.js","../node_modules/sockjs-client/lib/transport/browser/abstract-xhr.js","../node_modules/sockjs-client/lib/transport/sender/xhr-cors.js","../node_modules/sockjs-client/lib/transport/sender/xhr-local.js","../node_modules/sockjs-client/lib/utils/browser.js","../node_modules/sockjs-client/lib/transport/xhr-streaming.js","../node_modules/sockjs-client/lib/transport/sender/xdr.js","../node_modules/sockjs-client/lib/transport/xdr-streaming.js","../node_modules/sockjs-client/lib/transport/browser/eventsource.js","../node_modules/sockjs-client/lib/transport/eventsource.js","../node_modules/sockjs-client/lib/transport/receiver/eventsource.js","../node_modules/sockjs-client/lib/version.js","../node_modules/sockjs-client/lib/utils/iframe.js","../node_modules/sockjs-client/lib/transport/iframe.js","../node_modules/sockjs-client/lib/utils/object.js","../node_modules/sockjs-client/lib/transport/lib/iframe-wrap.js","../node_modules/sockjs-client/lib/transport/htmlfile.js","../node_modules/sockjs-client/lib/transport/receiver/htmlfile.js","../node_modules/sockjs-client/lib/transport/xhr-polling.js","../node_modules/sockjs-client/lib/transport/xdr-polling.js","../node_modules/sockjs-client/lib/transport/sender/jsonp.js","../node_modules/sockjs-client/lib/transport/jsonp-polling.js","../node_modules/sockjs-client/lib/transport/receiver/jsonp.js","../node_modules/sockjs-client/lib/transport-list.js","../node_modules/sockjs-client/lib/shims.js","../node_modules/sockjs-client/lib/utils/escape.js","../node_modules/sockjs-client/lib/utils/transport.js","../node_modules/sockjs-client/lib/utils/log.js","../node_modules/sockjs-client/lib/event/event.js","../node_modules/sockjs-client/lib/location.js","../node_modules/sockjs-client/lib/info-ajax.js","../node_modules/sockjs-client/lib/info-iframe-receiver.js","../node_modules/sockjs-client/lib/info-receiver.js","../node_modules/sockjs-client/lib/transport/sender/xhr-fake.js","../node_modules/sockjs-client/lib/info-iframe.js","../node_modules/sockjs-client/lib/iframe-bootstrap.js","../node_modules/sockjs-client/lib/facade.js","../node_modules/sockjs-client/lib/main.js","../node_modules/sockjs-client/lib/event/close.js","../node_modules/sockjs-client/lib/event/trans-message.js","../node_modules/sockjs-client/lib/entry.js","../src/core/ConnectionManager.js","../src/chat/ChatClient.js","../src/webrtc/MediaStreamManager.js","../src/webrtc/PeerConnectionManager.js","../src/webrtc/WebRTCClient.js","../src/push/PushManager.js","../src/talkflow/eventForwarding.js","../src/talkflow/delegates.js","../src/TalkFlowClient.js","../src/talkflow/session.js"],"sourcesContent":["/**\r\n * EventEmitter 유틸리티\r\n * 이벤트 기반 통신을 위한 간단한 구현\r\n */\r\n\r\nclass EventEmitter {\r\n constructor() {\r\n this._events = new Map();\r\n }\r\n\r\n /**\r\n * 이벤트 리스너 등록\r\n * @param {string} event - 이벤트 이름\r\n * @param {Function} listener - 콜백 함수\r\n * @returns {Function} - 구독 해제 함수\r\n */\r\n on(event, listener) {\r\n if (!this._events.has(event)) {\r\n this._events.set(event, new Set());\r\n }\r\n this._events.get(event).add(listener);\r\n\r\n // 구독 해제 함수 반환\r\n return () => this.off(event, listener);\r\n }\r\n\r\n /**\r\n * 일회성 이벤트 리스너 등록\r\n * @param {string} event - 이벤트 이름\r\n * @param {Function} listener - 콜백 함수\r\n * @returns {Function} - 구독 해제 함수\r\n */\r\n once(event, listener) {\r\n const onceWrapper = (...args) => {\r\n this.off(event, onceWrapper);\r\n listener.apply(this, args);\r\n };\r\n onceWrapper._originalListener = listener;\r\n return this.on(event, onceWrapper);\r\n }\r\n\r\n /**\r\n * 이벤트 리스너 제거\r\n * @param {string} event - 이벤트 이름\r\n * @param {Function} listener - 콜백 함수\r\n */\r\n off(event, listener) {\r\n const listeners = this._events.get(event);\r\n if (listeners) {\r\n // once로 등록된 리스너도 제거 가능하도록\r\n for (const l of listeners) {\r\n if (l === listener || l._originalListener === listener) {\r\n listeners.delete(l);\r\n break;\r\n }\r\n }\r\n if (listeners.size === 0) {\r\n this._events.delete(event);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 이벤트 발생.\r\n * payload 가 없는 이벤트({@code loggedOut}, {@code pushEnabled} 등) 는 {@code data} 생략 가능.\r\n * @param {string} event - 이벤트 이름\r\n * @param {*} [data] - 이벤트 데이터 (선택)\r\n */\r\n emit(event, data) {\r\n const listeners = this._events.get(event);\r\n if (listeners) {\r\n listeners.forEach(listener => {\r\n try {\r\n listener(data);\r\n } catch (error) {\r\n console.error(`Error in event listener for '${event}':`, error);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * 특정 이벤트의 모든 리스너 제거 (이벤트명 생략 시 전체 제거)\r\n * @param {string} [event] - 이벤트 이름 (선택)\r\n */\r\n removeAllListeners(event) {\r\n if (event) {\r\n this._events.delete(event);\r\n } else {\r\n this._events.clear();\r\n }\r\n }\r\n\r\n /**\r\n * 이벤트 리스너 수 반환\r\n * @param {string} event - 이벤트 이름\r\n * @returns {number}\r\n */\r\n listenerCount(event) {\r\n const listeners = this._events.get(event);\r\n return listeners ? listeners.size : 0;\r\n }\r\n\r\n /**\r\n * 등록된 이벤트 이름 목록\r\n * @returns {string[]}\r\n */\r\n eventNames() {\r\n return Array.from(this._events.keys());\r\n }\r\n}\r\n\r\nexport default EventEmitter;\r\n","/**\r\n * TalkFlow SDK Constants\r\n * 클라이언트 상태, 에러 타입, 시그널 타입 등 상수 정의\r\n */\r\n\r\n/**\r\n * 클라이언트 연결 상태\r\n */\r\nexport const ConnectionState = {\r\n DISCONNECTED: 'disconnected',\r\n CONNECTING: 'connecting',\r\n CONNECTED: 'connected',\r\n RECONNECTING: 'reconnecting',\r\n ERROR: 'error'\r\n};\r\n\r\n/**\r\n * 에러 타입\r\n */\r\nexport const ErrorTypes = {\r\n // Connection errors\r\n CONNECTION_FAILED: 'CONNECTION_FAILED',\r\n CONNECTION_LOST: 'CONNECTION_LOST',\r\n CONNECTION_TIMEOUT: 'CONNECTION_TIMEOUT',\r\n\r\n // Authentication errors\r\n JWT_INVALID: 'JWT_INVALID',\r\n JWT_EXPIRED: 'JWT_EXPIRED',\r\n JWT_PARSE_FAILED: 'JWT_PARSE_FAILED',\r\n UNAUTHORIZED: 'UNAUTHORIZED',\r\n\r\n // API errors\r\n API_ERROR: 'API_ERROR',\r\n API_TIMEOUT: 'API_TIMEOUT',\r\n\r\n // Chat errors\r\n CHAT_ROOM_NOT_FOUND: 'CHAT_ROOM_NOT_FOUND',\r\n CHAT_MESSAGE_FAILED: 'CHAT_MESSAGE_FAILED',\r\n CHAT_SUBSCRIPTION_FAILED: 'CHAT_SUBSCRIPTION_FAILED',\r\n\r\n // WebRTC errors\r\n MEDIA_ACCESS_DENIED: 'MEDIA_ACCESS_DENIED',\r\n SCREEN_SHARE_DENIED: 'SCREEN_SHARE_DENIED',\r\n PEER_CONNECTION_FAILED: 'PEER_CONNECTION_FAILED',\r\n ICE_CONNECTION_FAILED: 'ICE_CONNECTION_FAILED',\r\n SIGNALING_FAILED: 'SIGNALING_FAILED',\r\n DEVICE_SWITCH_FAILED: 'DEVICE_SWITCH_FAILED',\r\n ENUMERATE_DEVICES_FAILED: 'ENUMERATE_DEVICES_FAILED',\r\n CALL_ROOM_NOT_FOUND: 'CALL_ROOM_NOT_FOUND',\r\n\r\n // General errors\r\n INVALID_STATE: 'INVALID_STATE',\r\n UNKNOWN_ERROR: 'UNKNOWN_ERROR'\r\n};\r\n\r\n/**\r\n * WebRTC 시그널 타입 (서버 WebRTCMessageType과 일치)\r\n */\r\nexport const SignalTypes = {\r\n // Offer/Answer (WebRTC SDP)\r\n CALL_OFFER: 'call_offer',\r\n CALL_ANSWER: 'call_answer',\r\n\r\n // ICE\r\n ICE_CANDIDATE: 'ice_candidate',\r\n\r\n // Room events\r\n JOIN_ROOM: 'join_room',\r\n LEAVE_ROOM: 'leave_room',\r\n PEER_JOINED: 'peer_joined',\r\n PEER_LEFT: 'peer_left',\r\n\r\n // Media state\r\n VIDEO_STATE_CHANGED: 'video_state_changed',\r\n AUDIO_STATE_CHANGED: 'audio_state_changed',\r\n SCREEN_SHARE_STARTED: 'screen_share_started',\r\n SCREEN_SHARE_ENDED: 'screen_share_ended',\r\n\r\n // Call events (UI 레벨)\r\n CALL_REQUEST: 'call_request',\r\n CALL_ACCEPT: 'call_accept',\r\n CALL_REJECT: 'call_reject',\r\n CALL_CANCEL: 'call_cancel',\r\n CALL_END: 'call_end',\r\n CALL_INVITATION: 'call_invitation',\r\n CALL_BUSY: 'call_busy'\r\n};\r\n\r\n/**\r\n * 채팅 메시지 타입\r\n */\r\nexport const ChatMessageType = {\r\n TEXT: 'TEXT',\r\n IMAGE: 'IMAGE',\r\n FILE: 'FILE',\r\n VIDEO: 'VIDEO',\r\n AUDIO: 'AUDIO',\r\n SYSTEM: 'SYSTEM'\r\n};\r\n\r\n/**\r\n * 메시지 발신자 타입 — 서버 {@code SenderType} 과 일치.\r\n *\r\n * <ul>\r\n * <li>{@code USER} — 사람 사용자가 보낸 메시지 (기존 모든 메시지의 default)</li>\r\n * <li>{@code ASSISTANT} — AI 어시스턴트 페르소나가 생성한 메시지. {@code userId} 자리는 페르소나 ID</li>\r\n * </ul>\r\n *\r\n * <p>서버 응답에서 {@code senderType} 필드가 없으면 {@code USER} 로 간주 (기존 데이터 graceful).</p>\r\n */\r\nexport const SenderType = {\r\n USER: 'USER',\r\n ASSISTANT: 'ASSISTANT'\r\n};\r\n\r\n/**\r\n * 어시스턴트 페르소나 분류 — 서버 {@code PersonaRole} enum 과 일치 (14개).\r\n *\r\n * <p>{@code PM} 은 PM_BACKSTAGE 방의 단일 PM Front 전용 — 기존 14인 PERSONA_MULTI 라인업과 별개 구성이며\r\n * {@code PROJECT_MANAGEMENT}(PM 비서)와도 다르다.</p>\r\n */\r\nexport const PersonaRole = {\r\n LEGAL_ADVISOR: 'LEGAL_ADVISOR',\r\n MARKETING: 'MARKETING',\r\n PRODUCT_PLANNING: 'PRODUCT_PLANNING',\r\n HR: 'HR',\r\n FINANCE: 'FINANCE',\r\n CUSTOMER_SUPPORT: 'CUSTOMER_SUPPORT',\r\n SALES: 'SALES',\r\n ENGINEERING: 'ENGINEERING',\r\n DATA_ANALYST: 'DATA_ANALYST',\r\n PROJECT_MANAGEMENT: 'PROJECT_MANAGEMENT',\r\n RESEARCH: 'RESEARCH',\r\n TRANSLATION: 'TRANSLATION',\r\n DESIGN: 'DESIGN',\r\n // PM_BACKSTAGE 전용 — PM Front 단일 응답 (PERSONA_MULTI 14인과 별개)\r\n PM: 'PM'\r\n};\r\n\r\n/**\r\n * 채팅 요약 포맷 — 서버 {@code SummarizeFormat} enum 과 일치 (5개).\r\n *\r\n * <p>{@link ChatClient#summarizeWithAssistant} 의 {@code format} 인자.\r\n * 서버가 대소문자 무관하게 매핑하며, 미지정/알 수 없는 값이면 {@code SHORT} 로 처리.</p>\r\n *\r\n * <ul>\r\n * <li>{@code MINUTES} — 회의록 (참석/안건/핵심 논의/결정사항)</li>\r\n * <li>{@code SHORT} — 짧은 요약 (몇 문장 핵심). 기본값.</li>\r\n * <li>{@code TIMELINE} — 시간순 타임라인</li>\r\n * <li>{@code ACTIONS} — 액션 아이템 (task/owner/기한)</li>\r\n * <li>{@code OPTIONS} — 옵션 비교 (논의된 선택지별 장단점)</li>\r\n * </ul>\r\n */\r\nexport const SummarizeFormat = {\r\n MINUTES: 'MINUTES',\r\n SHORT: 'SHORT',\r\n TIMELINE: 'TIMELINE',\r\n ACTIONS: 'ACTIONS',\r\n OPTIONS: 'OPTIONS'\r\n};\r\n\r\n\r\n/**\r\n * 채팅방 타입 (서버 {@code ChatRoomType} enum 과 일치).\r\n *\r\n * <ul>\r\n * <li>{@code DIRECT} — 1:1 채팅방</li>\r\n * <li>{@code GROUP} — 공개 그룹 채팅방</li>\r\n * <li>{@code PRIVATE_GROUP} — 비밀 그룹 채팅방 (입장 시 비밀번호 필요)</li>\r\n * <li>{@code TEAM} — 팀 채팅방 (초대 전용 — 직접 입장 불가, 멤버는 입장 시점과 무관하게\r\n * 방 전체 히스토리 열람. 비참여자에게는 탐색/전체 목록에서도 숨김. 비밀번호 없음)</li>\r\n * </ul>\r\n */\r\nexport const ChatRoomType = {\r\n DIRECT: 'DIRECT',\r\n GROUP: 'GROUP',\r\n PRIVATE_GROUP: 'PRIVATE_GROUP',\r\n TEAM: 'TEAM'\r\n};\r\n\r\n/**\r\n * 어시스턴트 참여 모드 (서버 {@code AssistantMode} enum 과 일치).\r\n *\r\n * <ul>\r\n * <li>{@code GENERAL}: 자동 라우팅 + @mention 모두 활성</li>\r\n * <li>{@code PEOPLE_ONLY}: AI 응답 전면 차단</li>\r\n * <li>{@code CALL_ONLY}: @mention / 명시 호출 시만 응답</li>\r\n * </ul>\r\n */\r\nexport const AssistantMode = {\r\n GENERAL: 'GENERAL',\r\n PEOPLE_ONLY: 'PEOPLE_ONLY',\r\n CALL_ONLY: 'CALL_ONLY'\r\n};\r\n\r\n/**\r\n * 방의 AI 사용 방식 (상위 SSOT) — 서버 {@code RoomAiType} enum 과 일치.\r\n *\r\n * <p>{@link ChatClient#createGroupRoom} 의 {@code roomAiType} 으로 방 생성 시 의도를 명시한다.\r\n * 미지정 시 서버가 API 키 권한(capability) + 첨부 페르소나로 파생한다 (레거시 호환).\r\n * <b>단, PERSONA_MULTI 권한이 없는 PM 전용 키는 미지정 시 {@code NONE}(사람 방)으로 파생되므로</b>\r\n * {@link ChatClient#getRoomAiMeta} 로 확인 후 고른 타입을 명시 전송할 것.</p>\r\n *\r\n * <ul>\r\n * <li>{@code NONE}: AI 미사용 (순수 사람 채팅). {@code invitedAssistantPersonaIds}/{@code assistantMode} 동시 지정 불가.</li>\r\n * <li>{@code PERSONA_MULTI}: 다중 페르소나, LLM 이 판단해 호출. {@code assistantMode} 로 세부 제어. (현재 유일 활성)</li>\r\n * <li>{@code PM_BACKSTAGE}: PM + 백스테이지. PM 단일 응답 구현됨 — {@code engagementIntensity} 로 개입 강도 제어. 백스테이지 위임은 후속.</li>\r\n * </ul>\r\n */\r\nexport const RoomAiType = {\r\n NONE: 'NONE',\r\n PERSONA_MULTI: 'PERSONA_MULTI',\r\n PM_BACKSTAGE: 'PM_BACKSTAGE'\r\n};\r\n\r\n/**\r\n * PM_BACKSTAGE 방의 PM 개입 강도 (서버 {@code EngagementIntensity} enum 과 일치).\r\n *\r\n * <p>{@link ChatClient#createGroupRoom} / {@code updateGroupRoom} 의 {@code engagementIntensity}.\r\n * <b>PM_BACKSTAGE 방 전용</b> — 그 외 방 타입에 지정 시 서버가 거절한다. 14인 PERSONA_MULTI 는\r\n * 쿨다운 + Planner 자동 판단이라 무관하며, 이 값은 PM 단일 응답의 개입 빈도만 제어한다.</p>\r\n *\r\n * <ul>\r\n * <li>{@code QUIET}: 보수적 — 명확한 질문/도움 요청에만 PM 응답</li>\r\n * <li>{@code NORMAL}: 기본 — 업무 신호에 응답, 잡담엔 침묵</li>\r\n * <li>{@code ACTIVE}: 적극 — 가벼운 신호에도 응답</li>\r\n * </ul>\r\n */\r\nexport const EngagementIntensity = {\r\n QUIET: 'QUIET',\r\n NORMAL: 'NORMAL',\r\n ACTIVE: 'ACTIVE'\r\n};\r\n\r\n/**\r\n * PM 프롬프트 레이어 차원 — 서버 {@code PmPromptLayerScope} enum 과 일치.\r\n *\r\n * <p>전역 PM 프롬프트 위에 합성되는 추가 지시문 2종. 합성 순서: 전역 → PROJECT → ROOM.\r\n * SDK 가 다루는 것은 ROOM(방 레이어 — {@link ChatClient#getRoomPmPrompt} 등)이며,\r\n * PROJECT(회사 레이어)는 어드민 콘솔 영역이다.</p>\r\n */\r\nexport const PmPromptLayerScope = {\r\n /** 회사(프로젝트) 레이어 — 어드민 콘솔(PROJECT_ADMIN) 관리. */\r\n PROJECT: 'PROJECT',\r\n /** 방 레이어 — 방 주인(OWNER) 관리. PM_BACKSTAGE 방 전용. */\r\n ROOM: 'ROOM'\r\n};\r\n\r\n/**\r\n * PM 프롬프트 레이어 수정 주체 (감사 추적) — 서버 {@code PmPromptLayerEditorType} enum 과 일치.\r\n */\r\nexport const PmPromptLayerEditorType = {\r\n /** 플랫폼 운영자 (대리 수정). */\r\n SUPER_ADMIN: 'SUPER_ADMIN',\r\n /** 회사 관리자 — PROJECT 레이어. */\r\n PROJECT_ADMIN: 'PROJECT_ADMIN',\r\n /** 방 주인(OWNER) — ROOM 레이어. */\r\n ROOM_OWNER: 'ROOM_OWNER'\r\n};\r\n\r\n/**\r\n * 채팅방 리스트 실시간 업데이트 이벤트 타입 (카톡 스타일).\r\n *\r\n * 서버 RoomListEventType enum 과 일치해야 함.\r\n *\r\n * @example\r\n * client.on('roomListUpdate', (event) => {\r\n * switch (event.eventType) {\r\n * case RoomListEventType.MESSAGE_RECEIVED:\r\n * // 해당 방을 최상단으로, unread +1 (본인 메시지는 제외)\r\n * break;\r\n * case RoomListEventType.ROOM_LEFT:\r\n * // actorId === currentUserId 이면 리스트에서 제거\r\n * break;\r\n * }\r\n * });\r\n */\r\nexport const RoomListEventType = {\r\n /** 새 메시지 수신 → 리스트 최상단 이동, unread 카운트 +1 (본인 메시지 제외). */\r\n MESSAGE_RECEIVED: 'MESSAGE_RECEIVED',\r\n /** 현재 lastMessage 삭제 → 리스트 preview 갱신, unread 카운트 변경 없음. */\r\n MESSAGE_DELETED: 'MESSAGE_DELETED',\r\n /** 현재 lastMessage 편집 → 리스트 preview 새 content 로 갱신, unread 카운트 변경 없음. */\r\n MESSAGE_UPDATED: 'MESSAGE_UPDATED',\r\n /** 방 생성 (Direct/Group) → 참가자 리스트에 신규 item 추가. */\r\n ROOM_CREATED: 'ROOM_CREATED',\r\n /** 기존 방에 누군가 입장 → 참가자 수 갱신. */\r\n ROOM_JOINED: 'ROOM_JOINED',\r\n /** 방 퇴장 → 나간 본인 리스트에서는 제거, 남은 참가자는 참가자 수 감소. */\r\n ROOM_LEFT: 'ROOM_LEFT',\r\n /** 방 정보 변경 (이름, 설명 등). 미래 확장용. */\r\n ROOM_UPDATED: 'ROOM_UPDATED',\r\n /**\r\n * 방장에 의한 단순 추방 → 대상은 리스트에서 방 제거, 남은 참가자는 참가자 수 감소.\r\n * 재입장 가능 (ban 아님). actorId 는 추방한 방장, members 에 추방된 사용자 정보.\r\n */\r\n ROOM_KICKED: 'ROOM_KICKED',\r\n /** 방장에 의한 추방 + 영구 차단 → payload 는 ROOM_KICKED 와 동일, 재입장 거부됨. */\r\n ROOM_BANNED: 'ROOM_BANNED',\r\n /** 메시지 보관 기간 정리 → cutoffTime 이전 메시지를 UI에서 제거. */\r\n MESSAGE_RETENTION_CLEANUP: 'MESSAGE_RETENTION_CLEANUP'\r\n};\r\n\r\n/**\r\n * WebSocket 경로\r\n */\r\nexport const WebSocketPaths = {\r\n // Endpoints\r\n SOCKJS_ENDPOINT: '/ws-chat',\r\n NATIVE_ENDPOINT: '/ws-chat-native',\r\n\r\n // Prefixes\r\n APP_PREFIX: '/app',\r\n TOPIC_PREFIX: '/topic',\r\n QUEUE_PREFIX: '/queue',\r\n USER_PREFIX: '/user',\r\n\r\n // Chat destinations\r\n getChatDestination: (roomId) => `/topic/chat/${roomId}`,\r\n getChatReadDestination: (roomId) => `/topic/chat/${roomId}/read`,\r\n getChatTypingDestination: (roomId) => `/topic/chat/${roomId}/typing`,\r\n\r\n // Room list (카톡 스타일 리스트 실시간 업데이트)\r\n // 서버가 convertAndSendToUser(userId, \"/queue/rooms\", event) 로 전송\r\n // 클라이언트는 \"/user/queue/rooms\" 구독 (Spring STOMP 가 세션별 자동 격리)\r\n ROOM_LIST_USER_DESTINATION: '/user/queue/rooms',\r\n\r\n // WebRTC destinations\r\n getWebRTCDestination: (roomId) => `/topic/webrtc/${roomId}`,\r\n getWebRTCUserDestination: () => `/user/queue/webrtc`,\r\n\r\n // Send destinations\r\n CHAT_SEND: '/app/chat/send',\r\n CHAT_READ: '/app/chat/read',\r\n CHAT_TYPING: '/app/chat/typing',\r\n WEBRTC_SIGNAL: '/app/webrtc/signal'\r\n};\r\n\r\n/**\r\n * 환경 타입\r\n */\r\nexport const Environment = {\r\n DEVELOPMENT: 'development',\r\n STAGING: 'staging',\r\n PRODUCTION: 'production'\r\n};\r\n\r\n/**\r\n * 환경별 서버 설정\r\n */\r\nexport const Endpoints = {\r\n [Environment.DEVELOPMENT]: {\r\n serverUrl: 'https://dev-chat.apiorbit.net',\r\n wsEndpoint: '/ws-chat'\r\n },\r\n [Environment.STAGING]: {\r\n serverUrl: 'https://stg-api.talk-x.app',\r\n wsEndpoint: '/ws-chat'\r\n },\r\n [Environment.PRODUCTION]: {\r\n serverUrl: 'https://prod-api.talk-x.app',\r\n wsEndpoint: '/ws-chat'\r\n }\r\n};\r\n\r\n/**\r\n * 기본 설정값\r\n */\r\nexport const DefaultConfig = {\r\n // 기본 환경 (production)\r\n environment: Environment.PRODUCTION,\r\n\r\n // Connection\r\n reconnectDelay: 5000,\r\n maxReconnectAttempts: 10,\r\n heartbeatIncoming: 10000,\r\n heartbeatOutgoing: 10000,\r\n\r\n // API\r\n apiTimeout: 30000,\r\n\r\n // WebRTC\r\n iceServers: [\r\n { urls: 'stun:stun.l.google.com:19302' },\r\n { urls: 'stun:stun1.l.google.com:19302' }\r\n ],\r\n\r\n // Logging\r\n logLevel: 2 // WARN\r\n};\r\n\r\n/**\r\n * 환경에 따른 서버 URL 반환\r\n * @param {string} env - Environment 값\r\n * @returns {string} 서버 URL\r\n */\r\nexport function getServerUrl(env) {\r\n const endpoint = Endpoints[env] || Endpoints[Environment.PRODUCTION];\r\n return endpoint.serverUrl;\r\n}\r\n\r\n/**\r\n * 로그 레벨\r\n */\r\nexport const LogLevel = {\r\n DEBUG: 0,\r\n INFO: 1,\r\n WARN: 2,\r\n ERROR: 3,\r\n NONE: 4\r\n};\r\n","/**\r\n * Logger 유틸리티\r\n * 로그 레벨에 따른 콘솔 출력 관리\r\n */\r\n\r\nimport { LogLevel } from '../constants.js';\r\n\r\nclass Logger {\r\n /**\r\n * @param {number} [level] - 로그 레벨\r\n * @param {string} [prefix] - 로그 접두사\r\n */\r\n constructor(level = LogLevel.WARN, prefix = 'TalkFlow') {\r\n this.level = level;\r\n this.prefix = prefix;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level - 로그 레벨\r\n */\r\n setLevel(level) {\r\n this.level = level;\r\n }\r\n\r\n /**\r\n * 로그 접두사 설정\r\n * @param {string} prefix - 로그 접두사\r\n */\r\n setPrefix(prefix) {\r\n this.prefix = prefix;\r\n }\r\n\r\n /**\r\n * 로그 포맷 생성\r\n * @private\r\n * @param {string} level - 로그 레벨 문자열\r\n * @param {...*} args - 로그 인자들\r\n * @returns {Array} 포맷된 로그 배열\r\n */\r\n _format(level, ...args) {\r\n const timestamp = new Date().toISOString();\r\n return [`[${timestamp}] [${level}] [${this.prefix}]`, ...args];\r\n }\r\n\r\n /**\r\n * 디버그 로그 출력\r\n * @param {...*} args - 로그 인자들\r\n */\r\n debug(...args) {\r\n if (this.level <= LogLevel.DEBUG) {\r\n console.debug(...this._format('DEBUG', ...args));\r\n }\r\n }\r\n\r\n /**\r\n * 정보 로그 출력\r\n * @param {...*} args - 로그 인자들\r\n */\r\n info(...args) {\r\n if (this.level <= LogLevel.INFO) {\r\n console.info(...this._format('INFO', ...args));\r\n }\r\n }\r\n\r\n /**\r\n * 경고 로그 출력\r\n * @param {...*} args - 로그 인자들\r\n */\r\n warn(...args) {\r\n if (this.level <= LogLevel.WARN) {\r\n console.warn(...this._format('WARN', ...args));\r\n }\r\n }\r\n\r\n /**\r\n * 에러 로그 출력\r\n * @param {...*} args - 로그 인자들\r\n */\r\n error(...args) {\r\n if (this.level <= LogLevel.ERROR) {\r\n console.error(...this._format('ERROR', ...args));\r\n }\r\n }\r\n}\r\n\r\nexport default Logger;\r\nexport { LogLevel };\r\n","/**\r\n * API Client\r\n * REST API 호출을 위한 HTTP 클라이언트\r\n */\r\n\r\nimport { ErrorTypes, DefaultConfig } from '../constants.js';\r\nimport Logger from './Logger.js';\r\n\r\nclass ApiClient {\r\n /**\r\n * @param {Object} options\r\n * @param {string} options.baseUrl - API 기본 URL\r\n * @param {string} options.apiKey - API 키\r\n * @param {string} options.projectId - 프로젝트 ID\r\n * @param {string} options.jwtToken - JWT 토큰\r\n * @param {number} [options.timeout] - 요청 타임아웃 (ms)\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\r\n this.apiKey = options.apiKey;\r\n this.projectId = options.projectId;\r\n this.jwtToken = options.jwtToken;\r\n this.timeout = options.timeout || DefaultConfig.apiTimeout;\r\n\r\n this.logger = new Logger(options.logLevel, 'ApiClient');\r\n }\r\n\r\n /**\r\n * JWT 토큰 업데이트.\r\n * logout 경로에서 토큰 제거를 위해 {@code null} 도 전달 가능.\r\n * @param {string|null} token\r\n */\r\n setJwtToken(token) {\r\n this.jwtToken = token;\r\n }\r\n\r\n /**\r\n * 공통 헤더 생성\r\n * @returns {Object}\r\n */\r\n _getHeaders() {\r\n const headers = {\r\n 'Content-Type': 'application/json',\r\n 'X-API-KEY': this.apiKey,\r\n 'X-PROJECT-ID': this.projectId\r\n };\r\n\r\n if (this.jwtToken) {\r\n headers['Authorization'] = this.jwtToken.startsWith('Bearer ')\r\n ? this.jwtToken\r\n : `Bearer ${this.jwtToken}`;\r\n }\r\n\r\n return headers;\r\n }\r\n\r\n /**\r\n * HTTP 요청 실행\r\n * @param {string} method - HTTP 메서드\r\n * @param {string} path - API 경로\r\n * @param {Object} [options] - 옵션\r\n * @param {Object} [options.body] - 요청 본문\r\n * @param {Object} [options.params] - URL 파라미터\r\n * @returns {Promise<Object>}\r\n */\r\n async request(method, path, options = {}) {\r\n const { body, params } = options;\r\n\r\n // URL 빌드\r\n let url = `${this.baseUrl}${path}`;\r\n if (params) {\r\n const searchParams = new URLSearchParams();\r\n Object.entries(params).forEach(([key, value]) => {\r\n if (value !== undefined && value !== null) {\r\n searchParams.append(key, value);\r\n }\r\n });\r\n const queryString = searchParams.toString();\r\n if (queryString) {\r\n url += `?${queryString}`;\r\n }\r\n }\r\n\r\n // AbortController for timeout\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\r\n\r\n let response;\r\n try {\r\n this.logger.debug(`${method} ${url}`, body ? { body } : '');\r\n\r\n response = await fetch(url, {\r\n method,\r\n headers: this._getHeaders(),\r\n body: body ? JSON.stringify(body) : undefined,\r\n signal: controller.signal\r\n });\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n\r\n if (error.name === 'AbortError') {\r\n const timeoutError = new Error('Request timeout');\r\n timeoutError.code = ErrorTypes.API_TIMEOUT;\r\n throw timeoutError;\r\n }\r\n\r\n this.logger.error(`API Error: ${method} ${url}`, error);\r\n throw error;\r\n }\r\n\r\n clearTimeout(timeoutId);\r\n\r\n // 응답 파싱\r\n const contentType = response.headers.get('content-type');\r\n let data;\r\n\r\n if (contentType && contentType.includes('application/json')) {\r\n data = await response.json();\r\n } else {\r\n data = await response.text();\r\n }\r\n\r\n // 에러 처리\r\n if (!response.ok) {\r\n const error = new Error(data?.message || `HTTP ${response.status}`);\r\n error.code = ErrorTypes.API_ERROR;\r\n error.status = response.status;\r\n error.response = data;\r\n this.logger.error(`API Error: ${method} ${url}`, error);\r\n throw error;\r\n }\r\n\r\n this.logger.debug(`Response ${response.status}:`, data);\r\n return data;\r\n }\r\n\r\n // HTTP 메서드 헬퍼\r\n\r\n /**\r\n * GET 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [params] - URL 파라미터\r\n * @returns {Promise<Object>}\r\n */\r\n get(path, params) {\r\n return this.request('GET', path, { params });\r\n }\r\n\r\n /**\r\n * POST 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [body] - 요청 본문\r\n * @param {Object} [params] - URL 파라미터\r\n * @returns {Promise<Object>}\r\n */\r\n post(path, body, params) {\r\n return this.request('POST', path, { body, params });\r\n }\r\n\r\n /**\r\n * PUT 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [body] - 요청 본문\r\n * @returns {Promise<Object>}\r\n */\r\n put(path, body) {\r\n return this.request('PUT', path, { body });\r\n }\r\n\r\n /**\r\n * PATCH 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [body] - 요청 본문\r\n * @returns {Promise<Object>}\r\n */\r\n patch(path, body) {\r\n return this.request('PATCH', path, { body });\r\n }\r\n\r\n /**\r\n * DELETE 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [params] - URL 파라미터\r\n * @returns {Promise<Object>}\r\n */\r\n delete(path, params) {\r\n return this.request('DELETE', path, { params });\r\n }\r\n\r\n /**\r\n * multipart/form-data 파일 업로드.\r\n *\r\n * <p>{@code fetch} 가 업로드 진행률을 지원하지 않아 {@code XMLHttpRequest} 사용.\r\n * Content-Type 은 브라우저가 boundary 를 포함해 자동 세팅하므로 명시하지 않는다.</p>\r\n *\r\n * @param {string} path - API 경로 (baseUrl 뒤에 붙음)\r\n * @param {File|Blob} file - 업로드할 파일\r\n * @param {Object} [options]\r\n * @param {string} [options.fieldName='file'] - multipart field 이름 (서버 @RequestParam 과 일치)\r\n * @param {Function} [options.onProgress] - {@code ({loaded, total, percent}) => void} — 업로드 진행률 콜백\r\n * @param {AbortSignal} [options.signal] - 업로드 취소용 AbortSignal\r\n * @param {number} [options.timeout=600000] - 타임아웃 (ms). 기본 10분.\r\n * @returns {Promise<Object>} 서버 응답 JSON (SuccessResponse 래퍼 포함)\r\n */\r\n upload(path, file, options = {}) {\r\n const {\r\n fieldName = 'file',\r\n onProgress,\r\n signal,\r\n timeout = 600_000\r\n } = options;\r\n\r\n return new Promise((resolve, reject) => {\r\n const url = `${this.baseUrl}${path}`;\r\n const xhr = new XMLHttpRequest();\r\n const form = new FormData();\r\n form.append(fieldName, file);\r\n\r\n xhr.open('POST', url);\r\n xhr.timeout = timeout;\r\n\r\n // Content-Type 은 XHR 이 multipart boundary 포함해 자동 세팅 — 명시 금지\r\n xhr.setRequestHeader('X-API-KEY', this.apiKey);\r\n xhr.setRequestHeader('X-PROJECT-ID', this.projectId);\r\n if (this.jwtToken) {\r\n xhr.setRequestHeader(\r\n 'Authorization',\r\n this.jwtToken.startsWith('Bearer ') ? this.jwtToken : `Bearer ${this.jwtToken}`\r\n );\r\n }\r\n\r\n if (typeof onProgress === 'function') {\r\n xhr.upload.onprogress = (e) => {\r\n if (e.lengthComputable) {\r\n onProgress({\r\n loaded: e.loaded,\r\n total: e.total,\r\n percent: Math.round((e.loaded / e.total) * 100)\r\n });\r\n }\r\n };\r\n }\r\n\r\n // 외부 AbortSignal 과 연동 (취소 지원)\r\n const onAbort = () => xhr.abort();\r\n if (signal) {\r\n if (signal.aborted) {\r\n reject(new Error('Upload cancelled'));\r\n return;\r\n }\r\n signal.addEventListener('abort', onAbort);\r\n }\r\n const cleanupSignal = () => {\r\n if (signal) signal.removeEventListener('abort', onAbort);\r\n };\r\n\r\n xhr.onload = () => {\r\n cleanupSignal();\r\n let parsed = xhr.responseText;\r\n const contentType = xhr.getResponseHeader('content-type') || '';\r\n if (contentType.includes('application/json')) {\r\n try { parsed = JSON.parse(xhr.responseText); } catch { /* keep text */ }\r\n }\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n this.logger.debug(`Upload ${xhr.status}:`, parsed);\r\n resolve(parsed);\r\n } else {\r\n const error = new Error(parsed?.message || `HTTP ${xhr.status}`);\r\n error.code = ErrorTypes.API_ERROR;\r\n error.status = xhr.status;\r\n error.response = parsed;\r\n this.logger.error(`Upload Error: POST ${url}`, error);\r\n reject(error);\r\n }\r\n };\r\n\r\n xhr.onerror = () => {\r\n cleanupSignal();\r\n const err = new Error('Network error during upload');\r\n err.code = ErrorTypes.API_ERROR;\r\n this.logger.error(`Upload Network Error: POST ${url}`, err);\r\n reject(err);\r\n };\r\n\r\n xhr.ontimeout = () => {\r\n cleanupSignal();\r\n const err = new Error('Upload timeout');\r\n err.code = ErrorTypes.API_TIMEOUT;\r\n this.logger.error(`Upload Timeout: POST ${url}`, err);\r\n reject(err);\r\n };\r\n\r\n xhr.onabort = () => {\r\n cleanupSignal();\r\n reject(new Error('Upload cancelled'));\r\n };\r\n\r\n this.logger.debug(`POST ${url} (multipart, ${file.size} bytes)`);\r\n xhr.send(form);\r\n });\r\n }\r\n}\r\n\r\nexport default ApiClient;\r\n","/**\r\n * JWT 유틸리티 함수\r\n * JWT 토큰 파싱 및 검증\r\n */\r\n\r\nimport { ErrorTypes } from '../constants.js';\r\n\r\n/**\r\n * Bearer 접두사 제거\r\n * @param {string} token - JWT 토큰\r\n * @returns {string}\r\n */\r\nfunction stripBearer(token) {\r\n if (!token || typeof token !== 'string') {\r\n return '';\r\n }\r\n return token.replace(/^Bearer\\s+/i, '');\r\n}\r\n\r\n/**\r\n * Base64URL 디코딩 (UTF-8 안전)\r\n * @param {string} str - Base64URL 인코딩된 문자열\r\n * @returns {string}\r\n */\r\nfunction base64UrlDecode(str) {\r\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\r\n const padding = base64.length % 4;\r\n if (padding) {\r\n base64 += '='.repeat(4 - padding);\r\n }\r\n // atob()은 Latin-1 기반이라 멀티바이트 UTF-8(한국어 등)이 깨짐\r\n // → Uint8Array 변환 후 TextDecoder로 UTF-8 디코딩\r\n const binaryString = atob(base64);\r\n const bytes = Uint8Array.from(binaryString, c => c.charCodeAt(0));\r\n return new TextDecoder().decode(bytes);\r\n}\r\n\r\n/**\r\n * JWT 토큰 검증 및 파싱\r\n * @param {string} jwtToken - JWT 토큰 (Bearer 접두사 포함 가능)\r\n * @param {Object} [options] - 옵션\r\n * @param {number} [options.bufferSeconds=30] - 만료 버퍼 시간 (초)\r\n * @param {boolean} [options.validateExpiry=true] - 만료 시간 검증 여부\r\n * @returns {{userId: string, payload: Object}}\r\n * @throws {Error}\r\n */\r\nexport function validateAndParseJWT(jwtToken, options = {}) {\r\n const { bufferSeconds = 30, validateExpiry = true } = options;\r\n\r\n if (!jwtToken || typeof jwtToken !== 'string') {\r\n const error = new Error('JWT token is required');\r\n error.code = ErrorTypes.JWT_INVALID;\r\n throw error;\r\n }\r\n\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n const error = new Error('Invalid JWT format: expected 3 parts separated by dots');\r\n error.code = ErrorTypes.JWT_INVALID;\r\n throw error;\r\n }\r\n\r\n let payload;\r\n try {\r\n payload = JSON.parse(base64UrlDecode(parts[1]));\r\n } catch (e) {\r\n const error = new Error('Invalid JWT: failed to decode payload');\r\n error.code = ErrorTypes.JWT_PARSE_FAILED;\r\n error.originalError = e;\r\n throw error;\r\n }\r\n\r\n if (validateExpiry && payload.exp) {\r\n const expiryTime = payload.exp * 1000;\r\n const bufferTime = bufferSeconds * 1000;\r\n const now = Date.now();\r\n\r\n if (expiryTime < now + bufferTime) {\r\n const error = new Error(\r\n expiryTime < now\r\n ? 'JWT token expired'\r\n : `JWT token expires within ${bufferSeconds} seconds`\r\n );\r\n error.code = ErrorTypes.JWT_EXPIRED;\r\n error.expiredAt = new Date(expiryTime).toISOString();\r\n throw error;\r\n }\r\n }\r\n\r\n if (payload.iat) {\r\n const issuedAt = payload.iat * 1000;\r\n const clockSkew = 60 * 1000;\r\n\r\n if (issuedAt > Date.now() + clockSkew) {\r\n const error = new Error('JWT token issued in the future');\r\n error.code = ErrorTypes.JWT_INVALID;\r\n throw error;\r\n }\r\n }\r\n\r\n const userId = payload.userId || payload.sub || payload.user_id;\r\n\r\n if (!userId) {\r\n const error = new Error('Cannot extract userId from JWT: missing userId, sub, or user_id claim');\r\n error.code = ErrorTypes.JWT_INVALID;\r\n throw error;\r\n }\r\n\r\n return { userId, payload };\r\n}\r\n\r\n/**\r\n * JWT에서 userId만 추출\r\n * @param {string} jwtToken - JWT 토큰\r\n * @returns {string|null}\r\n */\r\nexport function extractUserIdFromJWT(jwtToken) {\r\n try {\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n return null;\r\n }\r\n\r\n const payload = JSON.parse(base64UrlDecode(parts[1]));\r\n return payload.userId || payload.sub || payload.user_id || null;\r\n } catch (error) {\r\n console.error('Failed to extract userId from JWT:', error);\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * JWT 만료 여부 확인\r\n * @param {string} jwtToken - JWT 토큰\r\n * @param {number} [bufferSeconds=0] - 버퍼 시간 (초)\r\n * @returns {boolean}\r\n */\r\nexport function isJWTExpired(jwtToken, bufferSeconds = 0) {\r\n try {\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n return true;\r\n }\r\n\r\n const payload = JSON.parse(base64UrlDecode(parts[1]));\r\n\r\n if (!payload.exp) {\r\n return false;\r\n }\r\n\r\n const expiryTime = payload.exp * 1000;\r\n const bufferTime = bufferSeconds * 1000;\r\n\r\n return expiryTime < Date.now() + bufferTime;\r\n } catch (error) {\r\n console.error('Failed to check JWT expiration:', error);\r\n return true;\r\n }\r\n}\r\n\r\n/**\r\n * JWT 남은 유효 시간 반환\r\n * @param {string} jwtToken - JWT 토큰\r\n * @returns {number} - 밀리초, 만료 시 음수\r\n */\r\nexport function getJWTRemainingTime(jwtToken) {\r\n try {\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n return -1;\r\n }\r\n\r\n const payload = JSON.parse(base64UrlDecode(parts[1]));\r\n\r\n if (!payload.exp) {\r\n return Infinity;\r\n }\r\n\r\n return (payload.exp * 1000) - Date.now();\r\n } catch (error) {\r\n console.error('Failed to get JWT remaining time:', error);\r\n return -1;\r\n }\r\n}\r\n\r\n/**\r\n * JWT payload 디코딩 (검증 없이)\r\n * @param {string} jwtToken - JWT 토큰\r\n * @returns {Object|null}\r\n */\r\nexport function decodeJWTPayload(jwtToken) {\r\n try {\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n return null;\r\n }\r\n\r\n return JSON.parse(base64UrlDecode(parts[1]));\r\n } catch (error) {\r\n console.error('Failed to decode JWT payload:', error);\r\n return null;\r\n }\r\n}\r\n","/**\n * Some byte values, used as per STOMP specifications.\n *\n * Part of `@stomp/stompjs`.\n *\n * @internal\n */\nexport const BYTE = {\n // LINEFEED byte (octet 10)\n LF: '\\x0A',\n // NULL byte (octet 0)\n NULL: '\\x00',\n};\n//# sourceMappingURL=byte.js.map","import { BYTE } from './byte.js';\n/**\n * Frame class represents a STOMP frame.\n *\n * @internal\n */\nexport class FrameImpl {\n /**\n * body of the frame\n */\n get body() {\n if (!this._body && this.isBinaryBody) {\n this._body = new TextDecoder().decode(this._binaryBody);\n }\n return this._body || '';\n }\n /**\n * body as Uint8Array\n */\n get binaryBody() {\n if (!this._binaryBody && !this.isBinaryBody) {\n this._binaryBody = new TextEncoder().encode(this._body);\n }\n // At this stage it will definitely have a valid value\n return this._binaryBody;\n }\n /**\n * Frame constructor. `command`, `headers` and `body` are available as properties.\n *\n * @internal\n */\n constructor(params) {\n const { command, headers, body, binaryBody, escapeHeaderValues, skipContentLengthHeader, } = params;\n this.command = command;\n this.headers = Object.assign({}, headers || {});\n if (binaryBody) {\n this._binaryBody = binaryBody;\n this.isBinaryBody = true;\n }\n else {\n this._body = body || '';\n this.isBinaryBody = false;\n }\n this.escapeHeaderValues = escapeHeaderValues || false;\n this.skipContentLengthHeader = skipContentLengthHeader || false;\n }\n /**\n * deserialize a STOMP Frame from raw data.\n *\n * @internal\n */\n static fromRawFrame(rawFrame, escapeHeaderValues) {\n const headers = {};\n const trim = (str) => str.replace(/^\\s+|\\s+$/g, '');\n // In case of repeated headers, as per standards, first value need to be used\n for (const header of rawFrame.headers.reverse()) {\n const idx = header.indexOf(':');\n const key = trim(header[0]);\n let value = trim(header[1]);\n if (escapeHeaderValues &&\n rawFrame.command !== 'CONNECT' &&\n rawFrame.command !== 'CONNECTED') {\n value = FrameImpl.hdrValueUnEscape(value);\n }\n headers[key] = value;\n }\n return new FrameImpl({\n command: rawFrame.command,\n headers,\n binaryBody: rawFrame.binaryBody,\n escapeHeaderValues,\n });\n }\n /**\n * @internal\n */\n toString() {\n return this.serializeCmdAndHeaders();\n }\n /**\n * serialize this Frame in a format suitable to be passed to WebSocket.\n * If the body is string the output will be string.\n * If the body is binary (i.e. of type Unit8Array) it will be serialized to ArrayBuffer.\n *\n * @internal\n */\n serialize() {\n const cmdAndHeaders = this.serializeCmdAndHeaders();\n if (this.isBinaryBody) {\n return FrameImpl.toUnit8Array(cmdAndHeaders, this._binaryBody).buffer;\n }\n else {\n return cmdAndHeaders + this._body + BYTE.NULL;\n }\n }\n serializeCmdAndHeaders() {\n const lines = [this.command];\n if (this.skipContentLengthHeader) {\n delete this.headers['content-length'];\n }\n for (const name of Object.keys(this.headers || {})) {\n const value = this.headers[name];\n if (this.escapeHeaderValues &&\n this.command !== 'CONNECT' &&\n this.command !== 'CONNECTED') {\n lines.push(`${name}:${FrameImpl.hdrValueEscape(`${value}`)}`);\n }\n else {\n lines.push(`${name}:${value}`);\n }\n }\n if (this.isBinaryBody ||\n (!this.isBodyEmpty() && !this.skipContentLengthHeader)) {\n lines.push(`content-length:${this.bodyLength()}`);\n }\n return lines.join(BYTE.LF) + BYTE.LF + BYTE.LF;\n }\n isBodyEmpty() {\n return this.bodyLength() === 0;\n }\n bodyLength() {\n const binaryBody = this.binaryBody;\n return binaryBody ? binaryBody.length : 0;\n }\n /**\n * Compute the size of a UTF-8 string by counting its number of bytes\n * (and not the number of characters composing the string)\n */\n static sizeOfUTF8(s) {\n return s ? new TextEncoder().encode(s).length : 0;\n }\n static toUnit8Array(cmdAndHeaders, binaryBody) {\n const uint8CmdAndHeaders = new TextEncoder().encode(cmdAndHeaders);\n const nullTerminator = new Uint8Array([0]);\n const uint8Frame = new Uint8Array(uint8CmdAndHeaders.length + binaryBody.length + nullTerminator.length);\n uint8Frame.set(uint8CmdAndHeaders);\n uint8Frame.set(binaryBody, uint8CmdAndHeaders.length);\n uint8Frame.set(nullTerminator, uint8CmdAndHeaders.length + binaryBody.length);\n return uint8Frame;\n }\n /**\n * Serialize a STOMP frame as per STOMP standards, suitable to be sent to the STOMP broker.\n *\n * @internal\n */\n static marshall(params) {\n const frame = new FrameImpl(params);\n return frame.serialize();\n }\n /**\n * Escape header values\n */\n static hdrValueEscape(str) {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\n/g, '\\\\n')\n .replace(/:/g, '\\\\c');\n }\n /**\n * UnEscape header values\n */\n static hdrValueUnEscape(str) {\n return str\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\c/g, ':')\n .replace(/\\\\\\\\/g, '\\\\');\n }\n}\n//# sourceMappingURL=frame-impl.js.map","/**\n * @internal\n */\nconst NULL = 0;\n/**\n * @internal\n */\nconst LF = 10;\n/**\n * @internal\n */\nconst CR = 13;\n/**\n * @internal\n */\nconst COLON = 58;\n/**\n * This is an evented, rec descent parser.\n * A stream of Octets can be passed and whenever it recognizes\n * a complete Frame or an incoming ping it will invoke the registered callbacks.\n *\n * All incoming Octets are fed into _onByte function.\n * Depending on current state the _onByte function keeps changing.\n * Depending on the state it keeps accumulating into _token and _results.\n * State is indicated by current value of _onByte, all states are named as _collect.\n *\n * STOMP standards https://stomp.github.io/stomp-specification-1.2.html\n * imply that all lengths are considered in bytes (instead of string lengths).\n * So, before actual parsing, if the incoming data is String it is converted to Octets.\n * This allows faithful implementation of the protocol and allows NULL Octets to be present in the body.\n *\n * There is no peek function on the incoming data.\n * When a state change occurs based on an Octet without consuming the Octet,\n * the Octet, after state change, is fed again (_reinjectByte).\n * This became possible as the state change can be determined by inspecting just one Octet.\n *\n * There are two modes to collect the body, if content-length header is there then it by counting Octets\n * otherwise it is determined by NULL terminator.\n *\n * Following the standards, the command and headers are converted to Strings\n * and the body is returned as Octets.\n * Headers are returned as an array and not as Hash - to allow multiple occurrence of an header.\n *\n * This parser does not use Regular Expressions as that can only operate on Strings.\n *\n * It handles if multiple STOMP frames are given as one chunk, a frame is split into multiple chunks, or\n * any combination there of. The parser remembers its state (any partial frame) and continues when a new chunk\n * is pushed.\n *\n * Typically the higher level function will convert headers to Hash, handle unescaping of header values\n * (which is protocol version specific), and convert body to text.\n *\n * Check the parser.spec.js to understand cases that this parser is supposed to handle.\n *\n * Part of `@stomp/stompjs`.\n *\n * @internal\n */\nexport class Parser {\n constructor(onFrame, onIncomingPing) {\n this.onFrame = onFrame;\n this.onIncomingPing = onIncomingPing;\n this._encoder = new TextEncoder();\n this._decoder = new TextDecoder();\n this._token = [];\n this._initState();\n }\n parseChunk(segment, appendMissingNULLonIncoming = false) {\n let chunk;\n if (typeof segment === 'string') {\n chunk = this._encoder.encode(segment);\n }\n else {\n chunk = new Uint8Array(segment);\n }\n // See https://github.com/stomp-js/stompjs/issues/89\n // Remove when underlying issue is fixed.\n //\n // Send a NULL byte, if the last byte of a Text frame was not NULL.F\n if (appendMissingNULLonIncoming && chunk[chunk.length - 1] !== 0) {\n const chunkWithNull = new Uint8Array(chunk.length + 1);\n chunkWithNull.set(chunk, 0);\n chunkWithNull[chunk.length] = 0;\n chunk = chunkWithNull;\n }\n // tslint:disable-next-line:prefer-for-of\n for (let i = 0; i < chunk.length; i++) {\n const byte = chunk[i];\n this._onByte(byte);\n }\n }\n // The following implements a simple Rec Descent Parser.\n // The grammar is simple and just one byte tells what should be the next state\n _collectFrame(byte) {\n if (byte === NULL) {\n // Ignore\n return;\n }\n if (byte === CR) {\n // Ignore CR\n return;\n }\n if (byte === LF) {\n // Incoming Ping\n this.onIncomingPing();\n return;\n }\n this._onByte = this._collectCommand;\n this._reinjectByte(byte);\n }\n _collectCommand(byte) {\n if (byte === CR) {\n // Ignore CR\n return;\n }\n if (byte === LF) {\n this._results.command = this._consumeTokenAsUTF8();\n this._onByte = this._collectHeaders;\n return;\n }\n this._consumeByte(byte);\n }\n _collectHeaders(byte) {\n if (byte === CR) {\n // Ignore CR\n return;\n }\n if (byte === LF) {\n this._setupCollectBody();\n return;\n }\n this._onByte = this._collectHeaderKey;\n this._reinjectByte(byte);\n }\n _reinjectByte(byte) {\n this._onByte(byte);\n }\n _collectHeaderKey(byte) {\n if (byte === COLON) {\n this._headerKey = this._consumeTokenAsUTF8();\n this._onByte = this._collectHeaderValue;\n return;\n }\n this._consumeByte(byte);\n }\n _collectHeaderValue(byte) {\n if (byte === CR) {\n // Ignore CR\n return;\n }\n if (byte === LF) {\n this._results.headers.push([\n this._headerKey,\n this._consumeTokenAsUTF8(),\n ]);\n this._headerKey = undefined;\n this._onByte = this._collectHeaders;\n return;\n }\n this._consumeByte(byte);\n }\n _setupCollectBody() {\n const contentLengthHeader = this._results.headers.filter((header) => {\n return header[0] === 'content-length';\n })[0];\n if (contentLengthHeader) {\n this._bodyBytesRemaining = parseInt(contentLengthHeader[1], 10);\n this._onByte = this._collectBodyFixedSize;\n }\n else {\n this._onByte = this._collectBodyNullTerminated;\n }\n }\n _collectBodyNullTerminated(byte) {\n if (byte === NULL) {\n this._retrievedBody();\n return;\n }\n this._consumeByte(byte);\n }\n _collectBodyFixedSize(byte) {\n // It is post decrement, so that we discard the trailing NULL octet\n if (this._bodyBytesRemaining-- === 0) {\n this._retrievedBody();\n return;\n }\n this._consumeByte(byte);\n }\n _retrievedBody() {\n this._results.binaryBody = this._consumeTokenAsRaw();\n try {\n this.onFrame(this._results);\n }\n catch (e) {\n console.log(`Ignoring an exception thrown by a frame handler. Original exception: `, e);\n }\n this._initState();\n }\n // Rec Descent Parser helpers\n _consumeByte(byte) {\n this._token.push(byte);\n }\n _consumeTokenAsUTF8() {\n return this._decoder.decode(this._consumeTokenAsRaw());\n }\n _consumeTokenAsRaw() {\n const rawResult = new Uint8Array(this._token);\n this._token = [];\n return rawResult;\n }\n _initState() {\n this._results = {\n command: undefined,\n headers: [],\n binaryBody: undefined,\n };\n this._token = [];\n this._headerKey = undefined;\n this._onByte = this._collectFrame;\n }\n}\n//# sourceMappingURL=parser.js.map","/**\n * Possible states for the IStompSocket\n */\nexport var StompSocketState;\n(function (StompSocketState) {\n StompSocketState[StompSocketState[\"CONNECTING\"] = 0] = \"CONNECTING\";\n StompSocketState[StompSocketState[\"OPEN\"] = 1] = \"OPEN\";\n StompSocketState[StompSocketState[\"CLOSING\"] = 2] = \"CLOSING\";\n StompSocketState[StompSocketState[\"CLOSED\"] = 3] = \"CLOSED\";\n})(StompSocketState || (StompSocketState = {}));\n/**\n * Possible activation state\n */\nexport var ActivationState;\n(function (ActivationState) {\n ActivationState[ActivationState[\"ACTIVE\"] = 0] = \"ACTIVE\";\n ActivationState[ActivationState[\"DEACTIVATING\"] = 1] = \"DEACTIVATING\";\n ActivationState[ActivationState[\"INACTIVE\"] = 2] = \"INACTIVE\";\n})(ActivationState || (ActivationState = {}));\n/**\n * Possible reconnection wait time modes\n */\nexport var ReconnectionTimeMode;\n(function (ReconnectionTimeMode) {\n ReconnectionTimeMode[ReconnectionTimeMode[\"LINEAR\"] = 0] = \"LINEAR\";\n ReconnectionTimeMode[ReconnectionTimeMode[\"EXPONENTIAL\"] = 1] = \"EXPONENTIAL\";\n})(ReconnectionTimeMode || (ReconnectionTimeMode = {}));\n/**\n * Possible ticker strategies for outgoing heartbeat ping\n */\nexport var TickerStrategy;\n(function (TickerStrategy) {\n TickerStrategy[\"Interval\"] = \"interval\";\n TickerStrategy[\"Worker\"] = \"worker\";\n})(TickerStrategy || (TickerStrategy = {}));\n//# sourceMappingURL=types.js.map","import { TickerStrategy } from './types.js';\nexport class Ticker {\n constructor(_interval, _strategy = TickerStrategy.Interval, _debug) {\n this._interval = _interval;\n this._strategy = _strategy;\n this._debug = _debug;\n this._workerScript = `\n var startTime = Date.now();\n setInterval(function() {\n self.postMessage(Date.now() - startTime);\n }, ${this._interval});\n `;\n }\n start(tick) {\n this.stop();\n if (this.shouldUseWorker()) {\n this.runWorker(tick);\n }\n else {\n this.runInterval(tick);\n }\n }\n stop() {\n this.disposeWorker();\n this.disposeInterval();\n }\n shouldUseWorker() {\n return (typeof Worker !== 'undefined' && this._strategy === TickerStrategy.Worker);\n }\n runWorker(tick) {\n this._debug('Using runWorker for outgoing pings');\n if (!this._worker) {\n this._worker = new Worker(URL.createObjectURL(new Blob([this._workerScript], { type: 'text/javascript' })));\n this._worker.onmessage = message => tick(message.data);\n }\n }\n runInterval(tick) {\n this._debug('Using runInterval for outgoing pings');\n if (!this._timer) {\n const startTime = Date.now();\n this._timer = setInterval(() => {\n tick(Date.now() - startTime);\n }, this._interval);\n }\n }\n disposeWorker() {\n if (this._worker) {\n this._worker.terminate();\n delete this._worker;\n this._debug('Outgoing ping disposeWorker');\n }\n }\n disposeInterval() {\n if (this._timer) {\n clearInterval(this._timer);\n delete this._timer;\n this._debug('Outgoing ping disposeInterval');\n }\n }\n}\n//# sourceMappingURL=ticker.js.map","/**\n * Supported STOMP versions\n *\n * Part of `@stomp/stompjs`.\n */\nexport class Versions {\n /**\n * Takes an array of versions, typical elements '1.2', '1.1', or '1.0'\n *\n * You will be creating an instance of this class if you want to override\n * supported versions to be declared during STOMP handshake.\n */\n constructor(versions) {\n this.versions = versions;\n }\n /**\n * Used as part of CONNECT STOMP Frame\n */\n supportedVersions() {\n return this.versions.join(',');\n }\n /**\n * Used while creating a WebSocket\n */\n protocolVersions() {\n return this.versions.map(x => `v${x.replace('.', '')}.stomp`);\n }\n}\n/**\n * Indicates protocol version 1.0\n */\nVersions.V1_0 = '1.0';\n/**\n * Indicates protocol version 1.1\n */\nVersions.V1_1 = '1.1';\n/**\n * Indicates protocol version 1.2\n */\nVersions.V1_2 = '1.2';\n/**\n * @internal\n */\nVersions.default = new Versions([\n Versions.V1_2,\n Versions.V1_1,\n Versions.V1_0,\n]);\n//# sourceMappingURL=versions.js.map","import { augmentWebsocket } from './augment-websocket.js';\nimport { BYTE } from './byte.js';\nimport { FrameImpl } from './frame-impl.js';\nimport { Parser } from './parser.js';\nimport { Ticker } from './ticker.js';\nimport { StompSocketState, } from './types.js';\nimport { Versions } from './versions.js';\n/**\n * The STOMP protocol handler\n *\n * Part of `@stomp/stompjs`.\n *\n * @internal\n */\nexport class StompHandler {\n get connectedVersion() {\n return this._connectedVersion;\n }\n get connected() {\n return this._connected;\n }\n constructor(_client, _webSocket, config) {\n this._client = _client;\n this._webSocket = _webSocket;\n this._connected = false;\n this._serverFrameHandlers = {\n // [CONNECTED Frame](https://stomp.github.com/stomp-specification-1.2.html#CONNECTED_Frame)\n CONNECTED: frame => {\n this.debug(`connected to server ${frame.headers.server}`);\n this._connected = true;\n this._connectedVersion = frame.headers.version;\n // STOMP version 1.2 needs header values to be escaped\n if (this._connectedVersion === Versions.V1_2) {\n this._escapeHeaderValues = true;\n }\n this._setupHeartbeat(frame.headers);\n this.onConnect(frame);\n },\n // [MESSAGE Frame](https://stomp.github.com/stomp-specification-1.2.html#MESSAGE)\n MESSAGE: frame => {\n // the callback is registered when the client calls\n // `subscribe()`.\n // If there is no registered subscription for the received message,\n // the default `onUnhandledMessage` callback is used that the client can set.\n // This is useful for subscriptions that are automatically created\n // on the browser side (e.g. [RabbitMQ's temporary\n // queues](https://www.rabbitmq.com/stomp.html)).\n const subscription = frame.headers.subscription;\n const onReceive = this._subscriptions[subscription] || this.onUnhandledMessage;\n // bless the frame to be a Message\n const message = frame;\n const client = this;\n const messageId = this._connectedVersion === Versions.V1_2\n ? message.headers.ack\n : message.headers['message-id'];\n // add `ack()` and `nack()` methods directly to the returned frame\n // so that a simple call to `message.ack()` can acknowledge the message.\n message.ack = (headers = {}) => {\n return client.ack(messageId, subscription, headers);\n };\n message.nack = (headers = {}) => {\n return client.nack(messageId, subscription, headers);\n };\n onReceive(message);\n },\n // [RECEIPT Frame](https://stomp.github.com/stomp-specification-1.2.html#RECEIPT)\n RECEIPT: frame => {\n const callback = this._receiptWatchers[frame.headers['receipt-id']];\n if (callback) {\n callback(frame);\n // Server will acknowledge only once, remove the callback\n delete this._receiptWatchers[frame.headers['receipt-id']];\n }\n else {\n this.onUnhandledReceipt(frame);\n }\n },\n // [ERROR Frame](https://stomp.github.com/stomp-specification-1.2.html#ERROR)\n ERROR: frame => {\n this.onStompError(frame);\n },\n };\n // used to index subscribers\n this._counter = 0;\n // subscription callbacks indexed by subscriber's ID\n this._subscriptions = {};\n // receipt-watchers indexed by receipts-ids\n this._receiptWatchers = {};\n this._partialData = '';\n this._escapeHeaderValues = false;\n this._lastServerActivityTS = Date.now();\n this.debug = config.debug;\n this.stompVersions = config.stompVersions;\n this.connectHeaders = config.connectHeaders;\n this.disconnectHeaders = config.disconnectHeaders;\n this.heartbeatIncoming = config.heartbeatIncoming;\n this.heartbeatToleranceMultiplier = config.heartbeatGracePeriods;\n this.heartbeatOutgoing = config.heartbeatOutgoing;\n this.splitLargeFrames = config.splitLargeFrames;\n this.maxWebSocketChunkSize = config.maxWebSocketChunkSize;\n this.forceBinaryWSFrames = config.forceBinaryWSFrames;\n this.logRawCommunication = config.logRawCommunication;\n this.appendMissingNULLonIncoming = config.appendMissingNULLonIncoming;\n this.discardWebsocketOnCommFailure = config.discardWebsocketOnCommFailure;\n this.onConnect = config.onConnect;\n this.onDisconnect = config.onDisconnect;\n this.onStompError = config.onStompError;\n this.onWebSocketClose = config.onWebSocketClose;\n this.onWebSocketError = config.onWebSocketError;\n this.onUnhandledMessage = config.onUnhandledMessage;\n this.onUnhandledReceipt = config.onUnhandledReceipt;\n this.onUnhandledFrame = config.onUnhandledFrame;\n this.onHeartbeatReceived = config.onHeartbeatReceived;\n this.onHeartbeatLost = config.onHeartbeatLost;\n }\n start() {\n const parser = new Parser(\n // On Frame\n rawFrame => {\n const frame = FrameImpl.fromRawFrame(rawFrame, this._escapeHeaderValues);\n // if this.logRawCommunication is set, the rawChunk is logged at this._webSocket.onmessage\n if (!this.logRawCommunication) {\n this.debug(`<<< ${frame}`);\n }\n const serverFrameHandler = this._serverFrameHandlers[frame.command] || this.onUnhandledFrame;\n serverFrameHandler(frame);\n }, \n // On Incoming Ping\n () => {\n this.debug('<<< PONG');\n this.onHeartbeatReceived();\n });\n this._webSocket.onmessage = (evt) => {\n this.debug('Received data');\n this._lastServerActivityTS = Date.now();\n if (this.logRawCommunication) {\n const rawChunkAsString = evt.data instanceof ArrayBuffer\n ? new TextDecoder().decode(evt.data)\n : evt.data;\n this.debug(`<<< ${rawChunkAsString}`);\n }\n parser.parseChunk(evt.data, this.appendMissingNULLonIncoming);\n };\n this._webSocket.onclose = (closeEvent) => {\n this.debug(`Connection closed to ${this._webSocket.url}`);\n this._cleanUp();\n this.onWebSocketClose(closeEvent);\n };\n this._webSocket.onerror = (errorEvent) => {\n this.onWebSocketError(errorEvent);\n };\n this._webSocket.onopen = () => {\n // Clone before updating\n const connectHeaders = Object.assign({}, this.connectHeaders);\n this.debug('Web Socket Opened...');\n connectHeaders['accept-version'] = this.stompVersions.supportedVersions();\n connectHeaders['heart-beat'] = [\n this.heartbeatOutgoing,\n this.heartbeatIncoming,\n ].join(',');\n this._transmit({ command: 'CONNECT', headers: connectHeaders });\n };\n }\n _setupHeartbeat(headers) {\n if (headers.version !== Versions.V1_1 &&\n headers.version !== Versions.V1_2) {\n return;\n }\n // It is valid for the server to not send this header\n // https://stomp.github.io/stomp-specification-1.2.html#Heart-beating\n if (!headers['heart-beat']) {\n return;\n }\n // heart-beat header received from the server looks like:\n //\n // heart-beat: sx, sy\n const [serverOutgoing, serverIncoming] = headers['heart-beat']\n .split(',')\n .map((v) => parseInt(v, 10));\n if (this.heartbeatOutgoing !== 0 && serverIncoming !== 0) {\n const ttl = Math.max(this.heartbeatOutgoing, serverIncoming);\n this.debug(`send PING every ${ttl}ms`);\n this._pinger = new Ticker(ttl, this._client.heartbeatStrategy, this.debug);\n this._pinger.start(() => {\n if (this._webSocket.readyState === StompSocketState.OPEN) {\n this._webSocket.send(BYTE.LF);\n this.debug('>>> PING');\n }\n });\n }\n if (this.heartbeatIncoming !== 0 && serverOutgoing !== 0) {\n const ttl = Math.max(this.heartbeatIncoming, serverOutgoing);\n this.debug(`check PONG every ${ttl}ms`);\n this._ponger = setInterval(() => {\n const delta = Date.now() - this._lastServerActivityTS;\n // We wait multiple grace periods to be flexible on window's setInterval calls\n if (delta > ttl * this.heartbeatToleranceMultiplier) {\n this.debug(`did not receive server activity for the last ${delta}ms`);\n this.onHeartbeatLost();\n this._closeOrDiscardWebsocket();\n }\n }, ttl);\n }\n }\n _closeOrDiscardWebsocket() {\n if (this.discardWebsocketOnCommFailure) {\n this.debug('Discarding websocket, the underlying socket may linger for a while');\n this.discardWebsocket();\n }\n else {\n this.debug('Issuing close on the websocket');\n this._closeWebsocket();\n }\n }\n forceDisconnect() {\n if (this._webSocket) {\n if (this._webSocket.readyState === StompSocketState.CONNECTING ||\n this._webSocket.readyState === StompSocketState.OPEN) {\n this._closeOrDiscardWebsocket();\n }\n }\n }\n _closeWebsocket() {\n this._webSocket.onmessage = () => { }; // ignore messages\n this._webSocket.close();\n }\n discardWebsocket() {\n if (typeof this._webSocket.terminate !== 'function') {\n augmentWebsocket(this._webSocket, (msg) => this.debug(msg));\n }\n // @ts-ignore - this method will be there at this stage\n this._webSocket.terminate();\n }\n _transmit(params) {\n const { command, headers, body, binaryBody, skipContentLengthHeader } = params;\n const frame = new FrameImpl({\n command,\n headers,\n body,\n binaryBody,\n escapeHeaderValues: this._escapeHeaderValues,\n skipContentLengthHeader,\n });\n let rawChunk = frame.serialize();\n if (this.logRawCommunication) {\n this.debug(`>>> ${rawChunk}`);\n }\n else {\n this.debug(`>>> ${frame}`);\n }\n if (this.forceBinaryWSFrames && typeof rawChunk === 'string') {\n rawChunk = new TextEncoder().encode(rawChunk);\n }\n if (typeof rawChunk !== 'string' || !this.splitLargeFrames) {\n this._webSocket.send(rawChunk);\n }\n else {\n let out = rawChunk;\n while (out.length > 0) {\n const chunk = out.substring(0, this.maxWebSocketChunkSize);\n out = out.substring(this.maxWebSocketChunkSize);\n this._webSocket.send(chunk);\n this.debug(`chunk sent = ${chunk.length}, remaining = ${out.length}`);\n }\n }\n }\n dispose() {\n if (this.connected) {\n try {\n // clone before updating\n const disconnectHeaders = Object.assign({}, this.disconnectHeaders);\n if (!disconnectHeaders.receipt) {\n disconnectHeaders.receipt = `close-${this._counter++}`;\n }\n this.watchForReceipt(disconnectHeaders.receipt, frame => {\n this._closeWebsocket();\n this._cleanUp();\n this.onDisconnect(frame);\n });\n this._transmit({ command: 'DISCONNECT', headers: disconnectHeaders });\n }\n catch (error) {\n this.debug(`Ignoring error during disconnect ${error}`);\n }\n }\n else {\n if (this._webSocket.readyState === StompSocketState.CONNECTING ||\n this._webSocket.readyState === StompSocketState.OPEN) {\n this._closeWebsocket();\n }\n }\n }\n _cleanUp() {\n this._connected = false;\n if (this._pinger) {\n this._pinger.stop();\n this._pinger = undefined;\n }\n if (this._ponger) {\n clearInterval(this._ponger);\n this._ponger = undefined;\n }\n }\n publish(params) {\n const { destination, headers, body, binaryBody, skipContentLengthHeader } = params;\n const hdrs = Object.assign({ destination }, headers);\n this._transmit({\n command: 'SEND',\n headers: hdrs,\n body,\n binaryBody,\n skipContentLengthHeader,\n });\n }\n watchForReceipt(receiptId, callback) {\n this._receiptWatchers[receiptId] = callback;\n }\n subscribe(destination, callback, headers = {}) {\n headers = Object.assign({}, headers);\n if (!headers.id) {\n headers.id = `sub-${this._counter++}`;\n }\n headers.destination = destination;\n this._subscriptions[headers.id] = callback;\n this._transmit({ command: 'SUBSCRIBE', headers });\n const client = this;\n return {\n id: headers.id,\n unsubscribe(hdrs) {\n return client.unsubscribe(headers.id, hdrs);\n },\n };\n }\n unsubscribe(id, headers = {}) {\n headers = Object.assign({}, headers);\n delete this._subscriptions[id];\n headers.id = id;\n this._transmit({ command: 'UNSUBSCRIBE', headers });\n }\n begin(transactionId) {\n const txId = transactionId || `tx-${this._counter++}`;\n this._transmit({\n command: 'BEGIN',\n headers: {\n transaction: txId,\n },\n });\n const client = this;\n return {\n id: txId,\n commit() {\n client.commit(txId);\n },\n abort() {\n client.abort(txId);\n },\n };\n }\n commit(transactionId) {\n this._transmit({\n command: 'COMMIT',\n headers: {\n transaction: transactionId,\n },\n });\n }\n abort(transactionId) {\n this._transmit({\n command: 'ABORT',\n headers: {\n transaction: transactionId,\n },\n });\n }\n ack(messageId, subscriptionId, headers = {}) {\n headers = Object.assign({}, headers);\n if (this._connectedVersion === Versions.V1_2) {\n headers.id = messageId;\n }\n else {\n headers['message-id'] = messageId;\n }\n headers.subscription = subscriptionId;\n this._transmit({ command: 'ACK', headers });\n }\n nack(messageId, subscriptionId, headers = {}) {\n headers = Object.assign({}, headers);\n if (this._connectedVersion === Versions.V1_2) {\n headers.id = messageId;\n }\n else {\n headers['message-id'] = messageId;\n }\n headers.subscription = subscriptionId;\n return this._transmit({ command: 'NACK', headers });\n }\n}\n//# sourceMappingURL=stomp-handler.js.map","/**\n * @internal\n */\nexport function augmentWebsocket(webSocket, debug) {\n webSocket.terminate = function () {\n const noOp = () => { };\n // set all callbacks to no op\n this.onerror = noOp;\n this.onmessage = noOp;\n this.onopen = noOp;\n const ts = new Date();\n const id = Math.random().toString().substring(2, 8); // A simulated id\n const origOnClose = this.onclose;\n // Track delay in actual closure of the socket\n this.onclose = closeEvent => {\n const delay = new Date().getTime() - ts.getTime();\n debug(`Discarded socket (#${id}) closed after ${delay}ms, with code/reason: ${closeEvent.code}/${closeEvent.reason}`);\n };\n this.close();\n origOnClose?.call(webSocket, {\n code: 4001,\n reason: `Quick discarding socket (#${id}) without waiting for the shutdown sequence.`,\n wasClean: false,\n });\n };\n}\n//# sourceMappingURL=augment-websocket.js.map","import { StompHandler } from './stomp-handler.js';\nimport { ActivationState, ReconnectionTimeMode, StompSocketState, TickerStrategy, } from './types.js';\nimport { Versions } from './versions.js';\n/**\n * STOMP Client Class.\n *\n * Part of `@stomp/stompjs`.\n *\n * This class provides a robust implementation for connecting to and interacting with a\n * STOMP-compliant messaging broker over WebSocket. It supports STOMP versions 1.2, 1.1, and 1.0.\n *\n * Features:\n * - Handles automatic reconnections.\n * - Supports heartbeat mechanisms to detect and report communication failures.\n * - Allows customization of connection and WebSocket behaviors through configurations.\n * - Compatible with both browser environments and Node.js with polyfill support for WebSocket.\n */\nexport class Client {\n /**\n * Provides access to the underlying WebSocket instance.\n * This property is **read-only**.\n *\n * Example:\n * ```javascript\n * const webSocket = client.webSocket;\n * if (webSocket) {\n * console.log('WebSocket is connected:', webSocket.readyState === WebSocket.OPEN);\n * }\n * ```\n *\n * **Caution:**\n * Directly interacting with the WebSocket instance (e.g., sending or receiving frames)\n * can interfere with the proper functioning of this library. Such actions may cause\n * unexpected behavior, disconnections, or invalid state in the library's internal mechanisms.\n *\n * Instead, use the library's provided methods to manage STOMP communication.\n *\n * @returns The WebSocket instance used by the STOMP handler, or `undefined` if not connected.\n */\n get webSocket() {\n return this._stompHandler?._webSocket;\n }\n /**\n * Allows customization of the disconnection headers.\n *\n * Any changes made during an active session will also be applied immediately.\n *\n * Example:\n * ```javascript\n * client.disconnectHeaders = {\n * receipt: 'custom-receipt-id'\n * };\n * ```\n */\n get disconnectHeaders() {\n return this._disconnectHeaders;\n }\n set disconnectHeaders(value) {\n this._disconnectHeaders = value;\n if (this._stompHandler) {\n this._stompHandler.disconnectHeaders = this._disconnectHeaders;\n }\n }\n /**\n * Indicates whether there is an active connection to the STOMP broker.\n *\n * Usage:\n * ```javascript\n * if (client.connected) {\n * console.log('Client is connected to the broker.');\n * } else {\n * console.log('No connection to the broker.');\n * }\n * ```\n *\n * @returns `true` if the client is currently connected, `false` otherwise.\n */\n get connected() {\n return !!this._stompHandler && this._stompHandler.connected;\n }\n /**\n * The version of the STOMP protocol negotiated with the server during connection.\n *\n * This is a **read-only** property and reflects the negotiated protocol version after\n * a successful connection.\n *\n * Example:\n * ```javascript\n * console.log('Connected STOMP version:', client.connectedVersion);\n * ```\n *\n * @returns The negotiated STOMP protocol version or `undefined` if not connected.\n */\n get connectedVersion() {\n return this._stompHandler ? this._stompHandler.connectedVersion : undefined;\n }\n /**\n * Indicates whether the client is currently active.\n *\n * A client is considered active if it is connected or actively attempting to reconnect.\n *\n * Example:\n * ```javascript\n * if (client.active) {\n * console.log('The client is active.');\n * } else {\n * console.log('The client is inactive.');\n * }\n * ```\n *\n * @returns `true` if the client is active, otherwise `false`.\n */\n get active() {\n return this.state === ActivationState.ACTIVE;\n }\n _changeState(state) {\n this.state = state;\n this.onChangeState(state);\n }\n /**\n * Constructs a new STOMP client instance.\n *\n * The constructor initializes default values and sets up no-op callbacks for all events.\n * Configuration can be passed during construction, or updated later using `configure`.\n *\n * Example:\n * ```javascript\n * const client = new Client({\n * brokerURL: 'wss://broker.example.com',\n * reconnectDelay: 5000\n * });\n * ```\n *\n * @param conf Optional configuration object to initialize the client with.\n */\n constructor(conf = {}) {\n /**\n * STOMP protocol versions to use during the handshake. By default, the client will attempt\n * versions `1.2`, `1.1`, and `1.0` in descending order of preference.\n *\n * Example:\n * ```javascript\n * // Configure the client to only use versions 1.1 and 1.0\n * client.stompVersions = new Versions(['1.1', '1.0']);\n * ```\n */\n this.stompVersions = Versions.default;\n /**\n * Timeout for establishing STOMP connection, in milliseconds.\n *\n * If the connection is not established within this period, the attempt will fail.\n * The default is `0`, meaning no timeout is set for connection attempts.\n *\n * Example:\n * ```javascript\n * client.connectionTimeout = 5000; // Fail connection if not established in 5 seconds\n * ```\n */\n this.connectionTimeout = 0;\n /**\n * Delay (in milliseconds) between reconnection attempts if the connection drops.\n *\n * Set to `0` to disable automatic reconnections. The default value is `5000` ms (5 seconds).\n *\n * Example:\n * ```javascript\n * client.reconnectDelay = 3000; // Attempt reconnection every 3 seconds\n * client.reconnectDelay = 0; // Disable automatic reconnection\n * ```\n */\n this.reconnectDelay = 5000;\n /**\n * The next reconnection delay, used internally.\n * Initialized to the value of [Client#reconnectDelay]{@link Client#reconnectDelay}, and it may\n * dynamically change based on [Client#reconnectTimeMode]{@link Client#reconnectTimeMode}.\n */\n this._nextReconnectDelay = 0;\n /**\n * Maximum delay (in milliseconds) between reconnection attempts when using exponential backoff.\n *\n * Default is 15 minutes (`15 * 60 * 1000` milliseconds). If `0`, there will be no upper limit.\n *\n * Example:\n * ```javascript\n * client.maxReconnectDelay = 10000; // Maximum wait time is 10 seconds\n * ```\n */\n this.maxReconnectDelay = 15 * 60 * 1000;\n /**\n * Mode for determining the time interval between reconnection attempts.\n *\n * Available modes:\n * - `ReconnectionTimeMode.LINEAR` (default): Fixed delays between reconnection attempts.\n * - `ReconnectionTimeMode.EXPONENTIAL`: Delay doubles after each attempt, capped by [maxReconnectDelay]{@link Client#maxReconnectDelay}.\n *\n * Example:\n * ```javascript\n * client.reconnectTimeMode = ReconnectionTimeMode.EXPONENTIAL;\n * client.reconnectDelay = 200; // Initial delay of 200 ms, doubles with each attempt\n * client.maxReconnectDelay = 2 * 60 * 1000; // Cap delay at 10 minutes\n * ```\n */\n this.reconnectTimeMode = ReconnectionTimeMode.LINEAR;\n /**\n * Interval (in milliseconds) for receiving heartbeat signals from the server.\n *\n * Specifies the expected frequency of heartbeats sent by the server. Set to `0` to disable.\n *\n * Example:\n * ```javascript\n * client.heartbeatIncoming = 10000; // Expect a heartbeat every 10 seconds\n * ```\n */\n this.heartbeatIncoming = 10000;\n /**\n * Multiplier for adjusting tolerance when processing heartbeat signals.\n *\n * Tolerance level is calculated using the multiplier:\n * `tolerance = heartbeatIncoming * heartbeatToleranceMultiplier`.\n * This helps account for delays in network communication or variations in timings.\n *\n * Default value is `2`.\n *\n * Example:\n * ```javascript\n * client.heartbeatToleranceMultiplier = 2.5; // Tolerates longer delays\n * ```\n */\n this.heartbeatToleranceMultiplier = 2;\n /**\n * Interval (in milliseconds) for sending heartbeat signals to the server.\n *\n * Specifies how frequently heartbeats should be sent to the server. Set to `0` to disable.\n *\n * Example:\n * ```javascript\n * client.heartbeatOutgoing = 5000; // Send a heartbeat every 5 seconds\n * ```\n */\n this.heartbeatOutgoing = 10000;\n /**\n * Strategy for sending outgoing heartbeats.\n *\n * Options:\n * - `TickerStrategy.Worker`: Uses Web Workers for sending heartbeats (recommended for long-running or background sessions).\n * - `TickerStrategy.Interval`: Uses standard JavaScript `setInterval` (default).\n *\n * Note:\n * - If Web Workers are unavailable (e.g., in Node.js), the `Interval` strategy is used automatically.\n * - Web Workers are preferable in browsers for reducing disconnects when tabs are in the background.\n *\n * Example:\n * ```javascript\n * client.heartbeatStrategy = TickerStrategy.Worker;\n * ```\n */\n this.heartbeatStrategy = TickerStrategy.Interval;\n /**\n * Enables splitting of large text WebSocket frames into smaller chunks.\n *\n * This setting is enabled for brokers that support only chunked messages (e.g., Java Spring-based brokers).\n * Default is `false`.\n *\n * Warning:\n * - Should not be used with WebSocket-compliant brokers, as chunking may cause large message failures.\n * - Binary WebSocket frames are never split.\n *\n * Example:\n * ```javascript\n * client.splitLargeFrames = true;\n * client.maxWebSocketChunkSize = 4096; // Allow chunks of 4 KB\n * ```\n */\n this.splitLargeFrames = false;\n /**\n * Maximum size (in bytes) for individual WebSocket chunks if [splitLargeFrames]{@link Client#splitLargeFrames} is enabled.\n *\n * Default is 8 KB (`8 * 1024` bytes). This value has no effect if [splitLargeFrames]{@link Client#splitLargeFrames} is `false`.\n */\n this.maxWebSocketChunkSize = 8 * 1024;\n /**\n * Forces all WebSocket frames to use binary transport, irrespective of payload type.\n *\n * Default behavior determines frame type based on payload (e.g., binary data for ArrayBuffers).\n *\n * Example:\n * ```javascript\n * client.forceBinaryWSFrames = true;\n * ```\n */\n this.forceBinaryWSFrames = false;\n /**\n * Workaround for a React Native WebSocket bug, where messages containing `NULL` are chopped.\n *\n * Enabling this appends a `NULL` character to incoming frames to ensure they remain valid STOMP packets.\n *\n * Warning:\n * - For brokers that split large messages, this may cause data loss or connection termination.\n *\n * Example:\n * ```javascript\n * client.appendMissingNULLonIncoming = true;\n * ```\n */\n this.appendMissingNULLonIncoming = false;\n /**\n * Instruct the library to immediately terminate the socket on communication failures, even\n * before the WebSocket is completely closed.\n *\n * This is particularly useful in browser environments where WebSocket closure may get delayed,\n * causing prolonged reconnection intervals under certain failure conditions.\n *\n *\n * Example:\n * ```javascript\n * client.discardWebsocketOnCommFailure = true; // Enable aggressive closing of WebSocket\n * ```\n *\n * Default value: `false`.\n */\n this.discardWebsocketOnCommFailure = false;\n /**\n * Current activation state of the client.\n *\n * Possible states:\n * - `ActivationState.ACTIVE`: Client is connected or actively attempting to connect.\n * - `ActivationState.INACTIVE`: Client is disconnected and not attempting to reconnect.\n * - `ActivationState.DEACTIVATING`: Client is in the process of disconnecting.\n *\n * Note: The client may transition directly from `ACTIVE` to `INACTIVE` without entering\n * the `DEACTIVATING` state.\n */\n this.state = ActivationState.INACTIVE;\n // No op callbacks\n const noOp = () => { };\n this.debug = noOp;\n this.beforeConnect = noOp;\n this.onConnect = noOp;\n this.onDisconnect = noOp;\n this.onUnhandledMessage = noOp;\n this.onUnhandledReceipt = noOp;\n this.onUnhandledFrame = noOp;\n this.onHeartbeatReceived = noOp;\n this.onHeartbeatLost = noOp;\n this.onStompError = noOp;\n this.onWebSocketClose = noOp;\n this.onWebSocketError = noOp;\n this.logRawCommunication = false;\n this.onChangeState = noOp;\n // These parameters would typically get proper values before connect is called\n this.connectHeaders = {};\n this._disconnectHeaders = {};\n // Apply configuration\n this.configure(conf);\n }\n /**\n * Updates the client's configuration.\n *\n * All properties in the provided configuration object will override the current settings.\n *\n * Additionally, a warning is logged if `maxReconnectDelay` is configured to a\n * value lower than `reconnectDelay`, and `maxReconnectDelay` is adjusted to match `reconnectDelay`.\n *\n * Example:\n * ```javascript\n * client.configure({\n * reconnectDelay: 3000,\n * maxReconnectDelay: 10000\n * });\n * ```\n *\n * @param conf Configuration object containing the new settings.\n */\n configure(conf) {\n // bulk assign all properties to this\n Object.assign(this, conf);\n // Warn on incorrect maxReconnectDelay settings\n if (this.maxReconnectDelay > 0 &&\n this.maxReconnectDelay < this.reconnectDelay) {\n this.debug(`Warning: maxReconnectDelay (${this.maxReconnectDelay}ms) is less than reconnectDelay (${this.reconnectDelay}ms). Using reconnectDelay as the maxReconnectDelay delay.`);\n this.maxReconnectDelay = this.reconnectDelay;\n }\n }\n /**\n * Activates the client, initiating a connection to the STOMP broker.\n *\n * On activation, the client attempts to connect and sets its state to `ACTIVE`. If the connection\n * is lost, it will automatically retry based on `reconnectDelay` or `maxReconnectDelay`. If\n * `reconnectTimeMode` is set to `EXPONENTIAL`, the reconnect delay increases exponentially.\n *\n * To stop reconnection attempts and disconnect, call [Client#deactivate]{@link Client#deactivate}.\n *\n * Example:\n * ```javascript\n * client.activate(); // Connect to the broker\n * ```\n *\n * If the client is currently `DEACTIVATING`, connection is delayed until the deactivation process completes.\n */\n activate() {\n const _activate = () => {\n if (this.active) {\n this.debug('Already ACTIVE, ignoring request to activate');\n return;\n }\n this._changeState(ActivationState.ACTIVE);\n this._nextReconnectDelay = this.reconnectDelay;\n this._connect();\n };\n // if it is deactivating, wait for it to complete before activating.\n if (this.state === ActivationState.DEACTIVATING) {\n this.debug('Waiting for deactivation to finish before activating');\n this.deactivate().then(() => {\n _activate();\n });\n }\n else {\n _activate();\n }\n }\n async _connect() {\n await this.beforeConnect(this);\n if (this._stompHandler) {\n this.debug('There is already a stompHandler, skipping the call to connect');\n return;\n }\n if (!this.active) {\n this.debug('Client has been marked inactive, will not attempt to connect');\n return;\n }\n // setup connection watcher\n if (this.connectionTimeout > 0) {\n // clear first\n if (this._connectionWatcher) {\n clearTimeout(this._connectionWatcher);\n }\n this._connectionWatcher = setTimeout(() => {\n if (this.connected) {\n return;\n }\n // Connection not established, close the underlying socket\n // a reconnection will be attempted\n this.debug(`Connection not established in ${this.connectionTimeout}ms, closing socket`);\n this.forceDisconnect();\n }, this.connectionTimeout);\n }\n this.debug('Opening Web Socket...');\n // Get the actual WebSocket (or a similar object)\n const webSocket = this._createWebSocket();\n this._stompHandler = new StompHandler(this, webSocket, {\n debug: this.debug,\n stompVersions: this.stompVersions,\n connectHeaders: this.connectHeaders,\n disconnectHeaders: this._disconnectHeaders,\n heartbeatIncoming: this.heartbeatIncoming,\n heartbeatGracePeriods: this.heartbeatToleranceMultiplier,\n heartbeatOutgoing: this.heartbeatOutgoing,\n heartbeatStrategy: this.heartbeatStrategy,\n splitLargeFrames: this.splitLargeFrames,\n maxWebSocketChunkSize: this.maxWebSocketChunkSize,\n forceBinaryWSFrames: this.forceBinaryWSFrames,\n logRawCommunication: this.logRawCommunication,\n appendMissingNULLonIncoming: this.appendMissingNULLonIncoming,\n discardWebsocketOnCommFailure: this.discardWebsocketOnCommFailure,\n onConnect: frame => {\n // Successfully connected, stop the connection watcher\n if (this._connectionWatcher) {\n clearTimeout(this._connectionWatcher);\n this._connectionWatcher = undefined;\n }\n // Reset reconnect delay after successful connection\n this._nextReconnectDelay = this.reconnectDelay;\n if (!this.active) {\n this.debug('STOMP got connected while deactivate was issued, will disconnect now');\n this._disposeStompHandler();\n return;\n }\n this.onConnect(frame);\n },\n onDisconnect: frame => {\n this.onDisconnect(frame);\n },\n onStompError: frame => {\n this.onStompError(frame);\n },\n onWebSocketClose: evt => {\n this._stompHandler = undefined; // a new one will be created in case of a reconnect\n if (this.state === ActivationState.DEACTIVATING) {\n // Mark deactivation complete\n this._changeState(ActivationState.INACTIVE);\n }\n // The callback is called before attempting to reconnect, this would allow the client\n // to be `deactivated` in the callback.\n this.onWebSocketClose(evt);\n if (this.active) {\n this._schedule_reconnect();\n }\n },\n onWebSocketError: evt => {\n this.onWebSocketError(evt);\n },\n onUnhandledMessage: message => {\n this.onUnhandledMessage(message);\n },\n onUnhandledReceipt: frame => {\n this.onUnhandledReceipt(frame);\n },\n onUnhandledFrame: frame => {\n this.onUnhandledFrame(frame);\n },\n onHeartbeatReceived: () => {\n this.onHeartbeatReceived();\n },\n onHeartbeatLost: () => {\n this.onHeartbeatLost();\n },\n });\n this._stompHandler.start();\n }\n _createWebSocket() {\n let webSocket;\n if (this.webSocketFactory) {\n webSocket = this.webSocketFactory();\n }\n else if (this.brokerURL) {\n webSocket = new WebSocket(this.brokerURL, this.stompVersions.protocolVersions());\n }\n else {\n throw new Error('Either brokerURL or webSocketFactory must be provided');\n }\n webSocket.binaryType = 'arraybuffer';\n return webSocket;\n }\n _schedule_reconnect() {\n if (this._nextReconnectDelay > 0) {\n this.debug(`STOMP: scheduling reconnection in ${this._nextReconnectDelay}ms`);\n this._reconnector = setTimeout(() => {\n if (this.reconnectTimeMode === ReconnectionTimeMode.EXPONENTIAL) {\n this._nextReconnectDelay = this._nextReconnectDelay * 2;\n // Truncated exponential backoff with a set limit unless disabled\n if (this.maxReconnectDelay !== 0) {\n this._nextReconnectDelay = Math.min(this._nextReconnectDelay, this.maxReconnectDelay);\n }\n }\n this._connect();\n }, this._nextReconnectDelay);\n }\n }\n /**\n * Disconnects the client and stops the automatic reconnection loop.\n *\n * If there is an active STOMP connection at the time of invocation, the appropriate callbacks\n * will be triggered during the shutdown sequence. Once deactivated, the client will enter the\n * `INACTIVE` state, and no further reconnection attempts will be made.\n *\n * **Behavior**:\n * - If there is no active WebSocket connection, this method resolves immediately.\n * - If there is an active connection, the method waits for the underlying WebSocket\n * to properly close before resolving.\n * - Multiple calls to this method are safe. Each invocation resolves upon completion.\n * - To reactivate, call [Client#activate]{@link Client#activate}.\n *\n * **Experimental Option:**\n * - By specifying the `force: true` option, the WebSocket connection is discarded immediately,\n * bypassing both the STOMP and WebSocket shutdown sequences.\n * - **Caution:** Using `force: true` may leave the WebSocket in an inconsistent state,\n * and brokers may not immediately detect the termination.\n *\n * Example:\n * ```javascript\n * // Graceful disconnect\n * await client.deactivate();\n *\n * // Forced disconnect to speed up shutdown when the connection is stale\n * await client.deactivate({ force: true });\n * ```\n *\n * @param options Configuration options for deactivation. Use `force: true` for immediate shutdown.\n * @returns A Promise that resolves when the deactivation process completes.\n */\n async deactivate(options = {}) {\n const force = options.force || false;\n const needToDispose = this.active;\n let retPromise;\n if (this.state === ActivationState.INACTIVE) {\n this.debug(`Already INACTIVE, nothing more to do`);\n return Promise.resolve();\n }\n this._changeState(ActivationState.DEACTIVATING);\n // Clear reconnection timer just to be safe\n this._nextReconnectDelay = 0;\n // Clear if a reconnection was scheduled\n if (this._reconnector) {\n clearTimeout(this._reconnector);\n this._reconnector = undefined;\n }\n if (this._stompHandler &&\n // @ts-ignore - if there is a _stompHandler, there is the webSocket\n this.webSocket.readyState !== StompSocketState.CLOSED) {\n const origOnWebSocketClose = this._stompHandler.onWebSocketClose;\n // we need to wait for the underlying websocket to close\n retPromise = new Promise((resolve, reject) => {\n // @ts-ignore - there is a _stompHandler\n this._stompHandler.onWebSocketClose = evt => {\n origOnWebSocketClose(evt);\n resolve();\n };\n });\n }\n else {\n // indicate that auto reconnect loop should terminate\n this._changeState(ActivationState.INACTIVE);\n return Promise.resolve();\n }\n if (force) {\n this._stompHandler?.discardWebsocket();\n }\n else if (needToDispose) {\n this._disposeStompHandler();\n }\n return retPromise;\n }\n /**\n * Forces a disconnect by directly closing the WebSocket.\n *\n * Unlike a normal disconnect, this does not send a DISCONNECT sequence to the broker but\n * instead closes the WebSocket connection directly. After forcing a disconnect, the client\n * will automatically attempt to reconnect based on its `reconnectDelay` configuration.\n *\n * **Note:** To prevent further reconnect attempts, call [Client#deactivate]{@link Client#deactivate}.\n *\n * Example:\n * ```javascript\n * client.forceDisconnect();\n * ```\n */\n forceDisconnect() {\n if (this._stompHandler) {\n this._stompHandler.forceDisconnect();\n }\n }\n _disposeStompHandler() {\n // Dispose STOMP Handler\n if (this._stompHandler) {\n this._stompHandler.dispose();\n }\n }\n /**\n * Sends a message to the specified destination on the STOMP broker.\n *\n * The `body` must be a `string`. For non-string payloads (e.g., JSON), encode it as a string before sending.\n * If sending binary data, use the `binaryBody` parameter as a [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array).\n *\n * **Content-Length Behavior**:\n * - For non-binary messages, the `content-length` header is added by default.\n * - The `content-length` header can be skipped for text frames by setting `skipContentLengthHeader: true` in the parameters.\n * - For binary messages, the `content-length` header is always included.\n *\n * **Notes**:\n * - Ensure that brokers support binary frames before using `binaryBody`.\n * - Sending messages with NULL octets and missing `content-length` headers can cause brokers to disconnect and throw errors.\n *\n * Example:\n * ```javascript\n * // Basic text message\n * client.publish({ destination: \"/queue/test\", body: \"Hello, STOMP\" });\n *\n * // Text message with additional headers\n * client.publish({ destination: \"/queue/test\", headers: { priority: 9 }, body: \"Hello, STOMP\" });\n *\n * // Skip content-length header\n * client.publish({ destination: \"/queue/test\", body: \"Hello, STOMP\", skipContentLengthHeader: true });\n *\n * // Binary message\n * const binaryData = new Uint8Array([1, 2, 3, 4]);\n * client.publish({\n * destination: '/topic/special',\n * binaryBody: binaryData,\n * headers: { 'content-type': 'application/octet-stream' }\n * });\n * ```\n */\n publish(params) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.publish(params);\n }\n _checkConnection() {\n if (!this.connected) {\n throw new TypeError('There is no underlying STOMP connection');\n }\n }\n /**\n * Monitors for a receipt acknowledgment from the broker for specific operations.\n *\n * Add a `receipt` header to the operation (like subscribe or publish), and use this method with\n * the same receipt ID to detect when the broker has acknowledged the operation's completion.\n *\n * The callback is invoked with the corresponding {@link IFrame} when the receipt is received.\n *\n * Example:\n * ```javascript\n * const receiptId = \"unique-receipt-id\";\n *\n * client.watchForReceipt(receiptId, (frame) => {\n * console.log(\"Operation acknowledged by the broker:\", frame);\n * });\n *\n * // Attach the receipt header to an operation\n * client.publish({ destination: \"/queue/test\", headers: { receipt: receiptId }, body: \"Hello\" });\n * ```\n *\n * @param receiptId Unique identifier for the receipt.\n * @param callback Callback function invoked on receiving the RECEIPT frame.\n */\n watchForReceipt(receiptId, callback) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.watchForReceipt(receiptId, callback);\n }\n /**\n * Subscribes to a destination on the STOMP broker.\n *\n * The callback is triggered for each message received from the subscribed destination. The message\n * is passed as an {@link IMessage} instance.\n *\n * **Subscription ID**:\n * - If no `id` is provided in `headers`, the library generates a unique subscription ID automatically.\n * - Provide an explicit `id` in `headers` if you wish to manage the subscription ID manually.\n *\n * Example:\n * ```javascript\n * const callback = (message) => {\n * console.log(\"Received message:\", message.body);\n * };\n *\n * // Auto-generated subscription ID\n * const subscription = client.subscribe(\"/queue/test\", callback);\n *\n * // Explicit subscription ID\n * const mySubId = \"my-subscription-id\";\n * const subscription = client.subscribe(\"/queue/test\", callback, { id: mySubId });\n * ```\n *\n * @param destination Destination to subscribe to.\n * @param callback Function invoked for each received message.\n * @param headers Optional headers for subscription, such as `id`.\n * @returns A {@link StompSubscription} which can be used to manage the subscription.\n */\n subscribe(destination, callback, headers = {}) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n return this._stompHandler.subscribe(destination, callback, headers);\n }\n /**\n * Unsubscribes from a subscription on the STOMP broker.\n *\n * Prefer using the `unsubscribe` method directly on the {@link StompSubscription} returned from `subscribe` for cleaner management:\n * ```javascript\n * const subscription = client.subscribe(\"/queue/test\", callback);\n * // Unsubscribe using the subscription object\n * subscription.unsubscribe();\n * ```\n *\n * This method can also be used directly with the subscription ID.\n *\n * Example:\n * ```javascript\n * client.unsubscribe(\"my-subscription-id\");\n * ```\n *\n * @param id Subscription ID to unsubscribe.\n * @param headers Optional headers to pass for the UNSUBSCRIBE frame.\n */\n unsubscribe(id, headers = {}) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.unsubscribe(id, headers);\n }\n /**\n * Starts a new transaction. The returned {@link ITransaction} object provides\n * methods for [commit]{@link ITransaction#commit} and [abort]{@link ITransaction#abort}.\n *\n * If `transactionId` is not provided, the library generates a unique ID internally.\n *\n * Example:\n * ```javascript\n * const tx = client.begin(); // Auto-generated ID\n *\n * // Or explicitly specify a transaction ID\n * const tx = client.begin(\"my-transaction-id\");\n * ```\n *\n * @param transactionId Optional transaction ID.\n * @returns An instance of {@link ITransaction}.\n */\n begin(transactionId) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n return this._stompHandler.begin(transactionId);\n }\n /**\n * Commits a transaction.\n *\n * It is strongly recommended to call [commit]{@link ITransaction#commit} on\n * the transaction object returned by [client#begin]{@link Client#begin}.\n *\n * Example:\n * ```javascript\n * const tx = client.begin();\n * // Perform operations under this transaction\n * tx.commit();\n * ```\n *\n * @param transactionId The ID of the transaction to commit.\n */\n commit(transactionId) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.commit(transactionId);\n }\n /**\n * Aborts a transaction.\n *\n * It is strongly recommended to call [abort]{@link ITransaction#abort} directly\n * on the transaction object returned by [client#begin]{@link Client#begin}.\n *\n * Example:\n * ```javascript\n * const tx = client.begin();\n * // Perform operations under this transaction\n * tx.abort(); // Abort the transaction\n * ```\n *\n * @param transactionId The ID of the transaction to abort.\n */\n abort(transactionId) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.abort(transactionId);\n }\n /**\n * Acknowledges receipt of a message. Typically, this should be done by calling\n * [ack]{@link IMessage#ack} directly on the {@link IMessage} instance passed\n * to the subscription callback.\n *\n * Example:\n * ```javascript\n * const callback = (message) => {\n * // Process the message\n * message.ack(); // Acknowledge the message\n * };\n *\n * client.subscribe(\"/queue/example\", callback, { ack: \"client\" });\n * ```\n *\n * @param messageId The ID of the message to acknowledge.\n * @param subscriptionId The ID of the subscription.\n * @param headers Optional headers for the acknowledgment frame.\n */\n ack(messageId, subscriptionId, headers = {}) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.ack(messageId, subscriptionId, headers);\n }\n /**\n * Rejects a message (negative acknowledgment). Like acknowledgments, this should\n * typically be done by calling [nack]{@link IMessage#nack} directly on the {@link IMessage}\n * instance passed to the subscription callback.\n *\n * Example:\n * ```javascript\n * const callback = (message) => {\n * // Process the message\n * if (isError(message)) {\n * message.nack(); // Reject the message\n * }\n * };\n *\n * client.subscribe(\"/queue/example\", callback, { ack: \"client\" });\n * ```\n *\n * @param messageId The ID of the message to negatively acknowledge.\n * @param subscriptionId The ID of the subscription.\n * @param headers Optional headers for the NACK frame.\n */\n nack(messageId, subscriptionId, headers = {}) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.nack(messageId, subscriptionId, headers);\n }\n}\n//# sourceMappingURL=client.js.map","'use strict';\n\nvar crypto = require('crypto');\n\n// This string has length 32, a power of 2, so the modulus doesn't introduce a\n// bias.\nvar _randomStringChars = 'abcdefghijklmnopqrstuvwxyz012345';\nmodule.exports = {\n string: function(length) {\n var max = _randomStringChars.length;\n var bytes = crypto.randomBytes(length);\n var ret = [];\n for (var i = 0; i < length; i++) {\n ret.push(_randomStringChars.substr(bytes[i] % max, 1));\n }\n return ret.join('');\n }\n\n, number: function(max) {\n return Math.floor(Math.random() * max);\n }\n\n, numberString: function(max) {\n var t = ('' + (max - 1)).length;\n var p = new Array(t + 1).join('0');\n return (p + this.number(max)).slice(-t);\n }\n};\n","'use strict';\n\nif (global.crypto && global.crypto.getRandomValues) {\n module.exports.randomBytes = function(length) {\n var bytes = new Uint8Array(length);\n global.crypto.getRandomValues(bytes);\n return bytes;\n };\n} else {\n module.exports.randomBytes = function(length) {\n var bytes = new Array(length);\n for (var i = 0; i < length; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n return bytes;\n };\n}\n","'use strict';\n\nvar random = require('./random');\n\nvar onUnload = {}\n , afterUnload = false\n // detect google chrome packaged apps because they don't allow the 'unload' event\n , isChromePackagedApp = global.chrome && global.chrome.app && global.chrome.app.runtime\n ;\n\nmodule.exports = {\n attachEvent: function(event, listener) {\n if (typeof global.addEventListener !== 'undefined') {\n global.addEventListener(event, listener, false);\n } else if (global.document && global.attachEvent) {\n // IE quirks.\n // According to: http://stevesouders.com/misc/test-postmessage.php\n // the message gets delivered only to 'document', not 'window'.\n global.document.attachEvent('on' + event, listener);\n // I get 'window' for ie8.\n global.attachEvent('on' + event, listener);\n }\n }\n\n, detachEvent: function(event, listener) {\n if (typeof global.addEventListener !== 'undefined') {\n global.removeEventListener(event, listener, false);\n } else if (global.document && global.detachEvent) {\n global.document.detachEvent('on' + event, listener);\n global.detachEvent('on' + event, listener);\n }\n }\n\n, unloadAdd: function(listener) {\n if (isChromePackagedApp) {\n return null;\n }\n\n var ref = random.string(8);\n onUnload[ref] = listener;\n if (afterUnload) {\n setTimeout(this.triggerUnloadCallbacks, 0);\n }\n return ref;\n }\n\n, unloadDel: function(ref) {\n if (ref in onUnload) {\n delete onUnload[ref];\n }\n }\n\n, triggerUnloadCallbacks: function() {\n for (var ref in onUnload) {\n onUnload[ref]();\n delete onUnload[ref];\n }\n }\n};\n\nvar unloadTriggered = function() {\n if (afterUnload) {\n return;\n }\n afterUnload = true;\n module.exports.triggerUnloadCallbacks();\n};\n\n// 'unload' alone is not reliable in opera within an iframe, but we\n// can't use `beforeunload` as IE fires it on javascript: links.\nif (!isChromePackagedApp) {\n module.exports.attachEvent('unload', unloadTriggered);\n}\n","'use strict';\n\nvar required = require('requires-port')\n , qs = require('querystringify')\n , controlOrWhitespace = /^[\\x00-\\x20\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]+/\n , CRHTLF = /[\\n\\r\\t]/g\n , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\\/\\//\n , port = /:\\d+$/\n , protocolre = /^([a-z][a-z0-9.+-]*:)?(\\/\\/)?([\\\\/]+)?([\\S\\s]*)/i\n , windowsDriveLetter = /^[a-zA-Z]:/;\n\n/**\n * Remove control characters and whitespace from the beginning of a string.\n *\n * @param {Object|String} str String to trim.\n * @returns {String} A new string representing `str` stripped of control\n * characters and whitespace from its beginning.\n * @public\n */\nfunction trimLeft(str) {\n return (str ? str : '').toString().replace(controlOrWhitespace, '');\n}\n\n/**\n * These are the parse rules for the URL parser, it informs the parser\n * about:\n *\n * 0. The char it Needs to parse, if it's a string it should be done using\n * indexOf, RegExp using exec and NaN means set as current value.\n * 1. The property we should set when parsing this value.\n * 2. Indication if it's backwards or forward parsing, when set as number it's\n * the value of extra chars that should be split off.\n * 3. Inherit from location if non existing in the parser.\n * 4. `toLowerCase` the resulting value.\n */\nvar rules = [\n ['#', 'hash'], // Extract from the back.\n ['?', 'query'], // Extract from the back.\n function sanitize(address, url) { // Sanitize what is left of the address\n return isSpecial(url.protocol) ? address.replace(/\\\\/g, '/') : address;\n },\n ['/', 'pathname'], // Extract from the back.\n ['@', 'auth', 1], // Extract from the front.\n [NaN, 'host', undefined, 1, 1], // Set left over value.\n [/:(\\d*)$/, 'port', undefined, 1], // RegExp the back.\n [NaN, 'hostname', undefined, 1, 1] // Set left over.\n];\n\n/**\n * These properties should not be copied or inherited from. This is only needed\n * for all non blob URL's as a blob URL does not include a hash, only the\n * origin.\n *\n * @type {Object}\n * @private\n */\nvar ignore = { hash: 1, query: 1 };\n\n/**\n * The location object differs when your code is loaded through a normal page,\n * Worker or through a worker using a blob. And with the blobble begins the\n * trouble as the location object will contain the URL of the blob, not the\n * location of the page where our code is loaded in. The actual origin is\n * encoded in the `pathname` so we can thankfully generate a good \"default\"\n * location from it so we can generate proper relative URL's again.\n *\n * @param {Object|String} loc Optional default location object.\n * @returns {Object} lolcation object.\n * @public\n */\nfunction lolcation(loc) {\n var globalVar;\n\n if (typeof window !== 'undefined') globalVar = window;\n else if (typeof global !== 'undefined') globalVar = global;\n else if (typeof self !== 'undefined') globalVar = self;\n else globalVar = {};\n\n var location = globalVar.location || {};\n loc = loc || location;\n\n var finaldestination = {}\n , type = typeof loc\n , key;\n\n if ('blob:' === loc.protocol) {\n finaldestination = new Url(unescape(loc.pathname), {});\n } else if ('string' === type) {\n finaldestination = new Url(loc, {});\n for (key in ignore) delete finaldestination[key];\n } else if ('object' === type) {\n for (key in loc) {\n if (key in ignore) continue;\n finaldestination[key] = loc[key];\n }\n\n if (finaldestination.slashes === undefined) {\n finaldestination.slashes = slashes.test(loc.href);\n }\n }\n\n return finaldestination;\n}\n\n/**\n * Check whether a protocol scheme is special.\n *\n * @param {String} The protocol scheme of the URL\n * @return {Boolean} `true` if the protocol scheme is special, else `false`\n * @private\n */\nfunction isSpecial(scheme) {\n return (\n scheme === 'file:' ||\n scheme === 'ftp:' ||\n scheme === 'http:' ||\n scheme === 'https:' ||\n scheme === 'ws:' ||\n scheme === 'wss:'\n );\n}\n\n/**\n * @typedef ProtocolExtract\n * @type Object\n * @property {String} protocol Protocol matched in the URL, in lowercase.\n * @property {Boolean} slashes `true` if protocol is followed by \"//\", else `false`.\n * @property {String} rest Rest of the URL that is not part of the protocol.\n */\n\n/**\n * Extract protocol information from a URL with/without double slash (\"//\").\n *\n * @param {String} address URL we want to extract from.\n * @param {Object} location\n * @return {ProtocolExtract} Extracted information.\n * @private\n */\nfunction extractProtocol(address, location) {\n address = trimLeft(address);\n address = address.replace(CRHTLF, '');\n location = location || {};\n\n var match = protocolre.exec(address);\n var protocol = match[1] ? match[1].toLowerCase() : '';\n var forwardSlashes = !!match[2];\n var otherSlashes = !!match[3];\n var slashesCount = 0;\n var rest;\n\n if (forwardSlashes) {\n if (otherSlashes) {\n rest = match[2] + match[3] + match[4];\n slashesCount = match[2].length + match[3].length;\n } else {\n rest = match[2] + match[4];\n slashesCount = match[2].length;\n }\n } else {\n if (otherSlashes) {\n rest = match[3] + match[4];\n slashesCount = match[3].length;\n } else {\n rest = match[4]\n }\n }\n\n if (protocol === 'file:') {\n if (slashesCount >= 2) {\n rest = rest.slice(2);\n }\n } else if (isSpecial(protocol)) {\n rest = match[4];\n } else if (protocol) {\n if (forwardSlashes) {\n rest = rest.slice(2);\n }\n } else if (slashesCount >= 2 && isSpecial(location.protocol)) {\n rest = match[4];\n }\n\n return {\n protocol: protocol,\n slashes: forwardSlashes || isSpecial(protocol),\n slashesCount: slashesCount,\n rest: rest\n };\n}\n\n/**\n * Resolve a relative URL pathname against a base URL pathname.\n *\n * @param {String} relative Pathname of the relative URL.\n * @param {String} base Pathname of the base URL.\n * @return {String} Resolved pathname.\n * @private\n */\nfunction resolve(relative, base) {\n if (relative === '') return base;\n\n var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/'))\n , i = path.length\n , last = path[i - 1]\n , unshift = false\n , up = 0;\n\n while (i--) {\n if (path[i] === '.') {\n path.splice(i, 1);\n } else if (path[i] === '..') {\n path.splice(i, 1);\n up++;\n } else if (up) {\n if (i === 0) unshift = true;\n path.splice(i, 1);\n up--;\n }\n }\n\n if (unshift) path.unshift('');\n if (last === '.' || last === '..') path.push('');\n\n return path.join('/');\n}\n\n/**\n * The actual URL instance. Instead of returning an object we've opted-in to\n * create an actual constructor as it's much more memory efficient and\n * faster and it pleases my OCD.\n *\n * It is worth noting that we should not use `URL` as class name to prevent\n * clashes with the global URL instance that got introduced in browsers.\n *\n * @constructor\n * @param {String} address URL we want to parse.\n * @param {Object|String} [location] Location defaults for relative paths.\n * @param {Boolean|Function} [parser] Parser for the query string.\n * @private\n */\nfunction Url(address, location, parser) {\n address = trimLeft(address);\n address = address.replace(CRHTLF, '');\n\n if (!(this instanceof Url)) {\n return new Url(address, location, parser);\n }\n\n var relative, extracted, parse, instruction, index, key\n , instructions = rules.slice()\n , type = typeof location\n , url = this\n , i = 0;\n\n //\n // The following if statements allows this module two have compatibility with\n // 2 different API:\n //\n // 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments\n // where the boolean indicates that the query string should also be parsed.\n //\n // 2. The `URL` interface of the browser which accepts a URL, object as\n // arguments. The supplied object will be used as default values / fall-back\n // for relative paths.\n //\n if ('object' !== type && 'string' !== type) {\n parser = location;\n location = null;\n }\n\n if (parser && 'function' !== typeof parser) parser = qs.parse;\n\n location = lolcation(location);\n\n //\n // Extract protocol information before running the instructions.\n //\n extracted = extractProtocol(address || '', location);\n relative = !extracted.protocol && !extracted.slashes;\n url.slashes = extracted.slashes || relative && location.slashes;\n url.protocol = extracted.protocol || location.protocol || '';\n address = extracted.rest;\n\n //\n // When the authority component is absent the URL starts with a path\n // component.\n //\n if (\n extracted.protocol === 'file:' && (\n extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) ||\n (!extracted.slashes &&\n (extracted.protocol ||\n extracted.slashesCount < 2 ||\n !isSpecial(url.protocol)))\n ) {\n instructions[3] = [/(.*)/, 'pathname'];\n }\n\n for (; i < instructions.length; i++) {\n instruction = instructions[i];\n\n if (typeof instruction === 'function') {\n address = instruction(address, url);\n continue;\n }\n\n parse = instruction[0];\n key = instruction[1];\n\n if (parse !== parse) {\n url[key] = address;\n } else if ('string' === typeof parse) {\n index = parse === '@'\n ? address.lastIndexOf(parse)\n : address.indexOf(parse);\n\n if (~index) {\n if ('number' === typeof instruction[2]) {\n url[key] = address.slice(0, index);\n address = address.slice(index + instruction[2]);\n } else {\n url[key] = address.slice(index);\n address = address.slice(0, index);\n }\n }\n } else if ((index = parse.exec(address))) {\n url[key] = index[1];\n address = address.slice(0, index.index);\n }\n\n url[key] = url[key] || (\n relative && instruction[3] ? location[key] || '' : ''\n );\n\n //\n // Hostname, host and protocol should be lowercased so they can be used to\n // create a proper `origin`.\n //\n if (instruction[4]) url[key] = url[key].toLowerCase();\n }\n\n //\n // Also parse the supplied query string in to an object. If we're supplied\n // with a custom parser as function use that instead of the default build-in\n // parser.\n //\n if (parser) url.query = parser(url.query);\n\n //\n // If the URL is relative, resolve the pathname against the base URL.\n //\n if (\n relative\n && location.slashes\n && url.pathname.charAt(0) !== '/'\n && (url.pathname !== '' || location.pathname !== '')\n ) {\n url.pathname = resolve(url.pathname, location.pathname);\n }\n\n //\n // Default to a / for pathname if none exists. This normalizes the URL\n // to always have a /\n //\n if (url.pathname.charAt(0) !== '/' && isSpecial(url.protocol)) {\n url.pathname = '/' + url.pathname;\n }\n\n //\n // We should not add port numbers if they are already the default port number\n // for a given protocol. As the host also contains the port number we're going\n // override it with the hostname which contains no port number.\n //\n if (!required(url.port, url.protocol)) {\n url.host = url.hostname;\n url.port = '';\n }\n\n //\n // Parse down the `auth` for the username and password.\n //\n url.username = url.password = '';\n\n if (url.auth) {\n index = url.auth.indexOf(':');\n\n if (~index) {\n url.username = url.auth.slice(0, index);\n url.username = encodeURIComponent(decodeURIComponent(url.username));\n\n url.password = url.auth.slice(index + 1);\n url.password = encodeURIComponent(decodeURIComponent(url.password))\n } else {\n url.username = encodeURIComponent(decodeURIComponent(url.auth));\n }\n\n url.auth = url.password ? url.username +':'+ url.password : url.username;\n }\n\n url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host\n ? url.protocol +'//'+ url.host\n : 'null';\n\n //\n // The href is just the compiled result.\n //\n url.href = url.toString();\n}\n\n/**\n * This is convenience method for changing properties in the URL instance to\n * insure that they all propagate correctly.\n *\n * @param {String} part Property we need to adjust.\n * @param {Mixed} value The newly assigned value.\n * @param {Boolean|Function} fn When setting the query, it will be the function\n * used to parse the query.\n * When setting the protocol, double slash will be\n * removed from the final url if it is true.\n * @returns {URL} URL instance for chaining.\n * @public\n */\nfunction set(part, value, fn) {\n var url = this;\n\n switch (part) {\n case 'query':\n if ('string' === typeof value && value.length) {\n value = (fn || qs.parse)(value);\n }\n\n url[part] = value;\n break;\n\n case 'port':\n url[part] = value;\n\n if (!required(value, url.protocol)) {\n url.host = url.hostname;\n url[part] = '';\n } else if (value) {\n url.host = url.hostname +':'+ value;\n }\n\n break;\n\n case 'hostname':\n url[part] = value;\n\n if (url.port) value += ':'+ url.port;\n url.host = value;\n break;\n\n case 'host':\n url[part] = value;\n\n if (port.test(value)) {\n value = value.split(':');\n url.port = value.pop();\n url.hostname = value.join(':');\n } else {\n url.hostname = value;\n url.port = '';\n }\n\n break;\n\n case 'protocol':\n url.protocol = value.toLowerCase();\n url.slashes = !fn;\n break;\n\n case 'pathname':\n case 'hash':\n if (value) {\n var char = part === 'pathname' ? '/' : '#';\n url[part] = value.charAt(0) !== char ? char + value : value;\n } else {\n url[part] = value;\n }\n break;\n\n case 'username':\n case 'password':\n url[part] = encodeURIComponent(value);\n break;\n\n case 'auth':\n var index = value.indexOf(':');\n\n if (~index) {\n url.username = value.slice(0, index);\n url.username = encodeURIComponent(decodeURIComponent(url.username));\n\n url.password = value.slice(index + 1);\n url.password = encodeURIComponent(decodeURIComponent(url.password));\n } else {\n url.username = encodeURIComponent(decodeURIComponent(value));\n }\n }\n\n for (var i = 0; i < rules.length; i++) {\n var ins = rules[i];\n\n if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase();\n }\n\n url.auth = url.password ? url.username +':'+ url.password : url.username;\n\n url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host\n ? url.protocol +'//'+ url.host\n : 'null';\n\n url.href = url.toString();\n\n return url;\n}\n\n/**\n * Transform the properties back in to a valid and full URL string.\n *\n * @param {Function} stringify Optional query stringify function.\n * @returns {String} Compiled version of the URL.\n * @public\n */\nfunction toString(stringify) {\n if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify;\n\n var query\n , url = this\n , host = url.host\n , protocol = url.protocol;\n\n if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':';\n\n var result =\n protocol +\n ((url.protocol && url.slashes) || isSpecial(url.protocol) ? '//' : '');\n\n if (url.username) {\n result += url.username;\n if (url.password) result += ':'+ url.password;\n result += '@';\n } else if (url.password) {\n result += ':'+ url.password;\n result += '@';\n } else if (\n url.protocol !== 'file:' &&\n isSpecial(url.protocol) &&\n !host &&\n url.pathname !== '/'\n ) {\n //\n // Add back the empty userinfo, otherwise the original invalid URL\n // might be transformed into a valid one with `url.pathname` as host.\n //\n result += '@';\n }\n\n //\n // Trailing colon is removed from `url.host` when it is parsed. If it still\n // ends with a colon, then add back the trailing colon that was removed. This\n // prevents an invalid URL from being transformed into a valid one.\n //\n if (host[host.length - 1] === ':' || (port.test(url.hostname) && !url.port)) {\n host += ':';\n }\n\n result += host + url.pathname;\n\n query = 'object' === typeof url.query ? stringify(url.query) : url.query;\n if (query) result += '?' !== query.charAt(0) ? '?'+ query : query;\n\n if (url.hash) result += url.hash;\n\n return result;\n}\n\nUrl.prototype = { set: set, toString: toString };\n\n//\n// Expose the URL parser and some additional properties that might be useful for\n// others or testing.\n//\nUrl.extractProtocol = extractProtocol;\nUrl.location = lolcation;\nUrl.trimLeft = trimLeft;\nUrl.qs = qs;\n\nmodule.exports = Url;\n","'use strict';\n\n/**\n * Check if we're required to add a port number.\n *\n * @see https://url.spec.whatwg.org/#default-port\n * @param {Number|String} port Port number we need to check\n * @param {String} protocol Protocol we need to check against.\n * @returns {Boolean} Is it a default port for the given protocol\n * @api private\n */\nmodule.exports = function required(port, protocol) {\n protocol = protocol.split(':')[0];\n port = +port;\n\n if (!port) return false;\n\n switch (protocol) {\n case 'http':\n case 'ws':\n return port !== 80;\n\n case 'https':\n case 'wss':\n return port !== 443;\n\n case 'ftp':\n return port !== 21;\n\n case 'gopher':\n return port !== 70;\n\n case 'file':\n return false;\n }\n\n return port !== 0;\n};\n","'use strict';\n\nvar has = Object.prototype.hasOwnProperty\n , undef;\n\n/**\n * Decode a URI encoded string.\n *\n * @param {String} input The URI encoded string.\n * @returns {String|Null} The decoded string.\n * @api private\n */\nfunction decode(input) {\n try {\n return decodeURIComponent(input.replace(/\\+/g, ' '));\n } catch (e) {\n return null;\n }\n}\n\n/**\n * Attempts to encode a given input.\n *\n * @param {String} input The string that needs to be encoded.\n * @returns {String|Null} The encoded string.\n * @api private\n */\nfunction encode(input) {\n try {\n return encodeURIComponent(input);\n } catch (e) {\n return null;\n }\n}\n\n/**\n * Simple query string parser.\n *\n * @param {String} query The query string that needs to be parsed.\n * @returns {Object}\n * @api public\n */\nfunction querystring(query) {\n var parser = /([^=?#&]+)=?([^&]*)/g\n , result = {}\n , part;\n\n while (part = parser.exec(query)) {\n var key = decode(part[1])\n , value = decode(part[2]);\n\n //\n // Prevent overriding of existing properties. This ensures that build-in\n // methods like `toString` or __proto__ are not overriden by malicious\n // querystrings.\n //\n // In the case if failed decoding, we want to omit the key/value pairs\n // from the result.\n //\n if (key === null || value === null || key in result) continue;\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Transform a query string to an object.\n *\n * @param {Object} obj Object that should be transformed.\n * @param {String} prefix Optional prefix.\n * @returns {String}\n * @api public\n */\nfunction querystringify(obj, prefix) {\n prefix = prefix || '';\n\n var pairs = []\n , value\n , key;\n\n //\n // Optionally prefix with a '?' if needed\n //\n if ('string' !== typeof prefix) prefix = '?';\n\n for (key in obj) {\n if (has.call(obj, key)) {\n value = obj[key];\n\n //\n // Edge cases where we actually want to encode the value to an empty\n // string instead of the stringified value.\n //\n if (!value && (value === null || value === undef || isNaN(value))) {\n value = '';\n }\n\n key = encode(key);\n value = encode(value);\n\n //\n // If we failed to encode the strings, we should bail out as we don't\n // want to add invalid strings to the query.\n //\n if (key === null || value === null) continue;\n pairs.push(key +'='+ value);\n }\n }\n\n return pairs.length ? prefix + pairs.join('&') : '';\n}\n\n//\n// Expose the module.\n//\nexports.stringify = querystringify;\nexports.parse = querystring;\n","'use strict';\n\nvar URL = require('url-parse');\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:utils:url');\n}\n\nmodule.exports = {\n getOrigin: function(url) {\n if (!url) {\n return null;\n }\n\n var p = new URL(url);\n if (p.protocol === 'file:') {\n return null;\n }\n\n var port = p.port;\n if (!port) {\n port = (p.protocol === 'https:') ? '443' : '80';\n }\n\n return p.protocol + '//' + p.hostname + ':' + port;\n }\n\n, isOriginEqual: function(a, b) {\n var res = this.getOrigin(a) === this.getOrigin(b);\n debug('same', a, b, res);\n return res;\n }\n\n, isSchemeEqual: function(a, b) {\n return (a.split(':')[0] === b.split(':')[0]);\n }\n\n, addPath: function (url, path) {\n var qs = url.split('?');\n return qs[0] + path + (qs[1] ? '?' + qs[1] : '');\n }\n\n, addQuery: function (url, q) {\n return url + (url.indexOf('?') === -1 ? ('?' + q) : ('&' + q));\n }\n\n, isLoopbackAddr: function (addr) {\n return /^127\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$/i.test(addr) || /^\\[::1\\]$/.test(addr);\n }\n};\n","if (typeof Object.create === 'function') {\n // implementation from standard node.js 'util' module\n module.exports = function inherits(ctor, superCtor) {\n if (superCtor) {\n ctor.super_ = superCtor\n ctor.prototype = Object.create(superCtor.prototype, {\n constructor: {\n value: ctor,\n enumerable: false,\n writable: true,\n configurable: true\n }\n })\n }\n };\n} else {\n // old school shim for old browsers\n module.exports = function inherits(ctor, superCtor) {\n if (superCtor) {\n ctor.super_ = superCtor\n var TempCtor = function () {}\n TempCtor.prototype = superCtor.prototype\n ctor.prototype = new TempCtor()\n ctor.prototype.constructor = ctor\n }\n }\n}\n","'use strict';\n\n/* Simplified implementation of DOM2 EventTarget.\n * http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget\n */\n\nfunction EventTarget() {\n this._listeners = {};\n}\n\nEventTarget.prototype.addEventListener = function(eventType, listener) {\n if (!(eventType in this._listeners)) {\n this._listeners[eventType] = [];\n }\n var arr = this._listeners[eventType];\n // #4\n if (arr.indexOf(listener) === -1) {\n // Make a copy so as not to interfere with a current dispatchEvent.\n arr = arr.concat([listener]);\n }\n this._listeners[eventType] = arr;\n};\n\nEventTarget.prototype.removeEventListener = function(eventType, listener) {\n var arr = this._listeners[eventType];\n if (!arr) {\n return;\n }\n var idx = arr.indexOf(listener);\n if (idx !== -1) {\n if (arr.length > 1) {\n // Make a copy so as not to interfere with a current dispatchEvent.\n this._listeners[eventType] = arr.slice(0, idx).concat(arr.slice(idx + 1));\n } else {\n delete this._listeners[eventType];\n }\n return;\n }\n};\n\nEventTarget.prototype.dispatchEvent = function() {\n var event = arguments[0];\n var t = event.type;\n // equivalent of Array.prototype.slice.call(arguments, 0);\n var args = arguments.length === 1 ? [event] : Array.apply(null, arguments);\n // TODO: This doesn't match the real behavior; per spec, onfoo get\n // their place in line from the /first/ time they're set from\n // non-null. Although WebKit bumps it to the end every time it's\n // set.\n if (this['on' + t]) {\n this['on' + t].apply(this, args);\n }\n if (t in this._listeners) {\n // Grab a reference to the listeners list. removeEventListener may alter the list.\n var listeners = this._listeners[t];\n for (var i = 0; i < listeners.length; i++) {\n listeners[i].apply(this, args);\n }\n }\n};\n\nmodule.exports = EventTarget;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventTarget = require('./eventtarget')\n ;\n\nfunction EventEmitter() {\n EventTarget.call(this);\n}\n\ninherits(EventEmitter, EventTarget);\n\nEventEmitter.prototype.removeAllListeners = function(type) {\n if (type) {\n delete this._listeners[type];\n } else {\n this._listeners = {};\n }\n};\n\nEventEmitter.prototype.once = function(type, listener) {\n var self = this\n , fired = false;\n\n function g() {\n self.removeListener(type, g);\n\n if (!fired) {\n fired = true;\n listener.apply(this, arguments);\n }\n }\n\n this.on(type, g);\n};\n\nEventEmitter.prototype.emit = function() {\n var type = arguments[0];\n var listeners = this._listeners[type];\n if (!listeners) {\n return;\n }\n // equivalent of Array.prototype.slice.call(arguments, 1);\n var l = arguments.length;\n var args = new Array(l - 1);\n for (var ai = 1; ai < l; ai++) {\n args[ai - 1] = arguments[ai];\n }\n for (var i = 0; i < listeners.length; i++) {\n listeners[i].apply(this, args);\n }\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener = EventTarget.prototype.addEventListener;\nEventEmitter.prototype.removeListener = EventTarget.prototype.removeEventListener;\n\nmodule.exports.EventEmitter = EventEmitter;\n","'use strict';\n\nvar utils = require('../utils/event')\n , urlUtils = require('../utils/url')\n , inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n , WebsocketDriver = require('./driver/websocket')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:websocket');\n}\n\nfunction WebSocketTransport(transUrl, ignore, options) {\n if (!WebSocketTransport.enabled()) {\n throw new Error('Transport created when disabled');\n }\n\n EventEmitter.call(this);\n debug('constructor', transUrl);\n\n var self = this;\n var url = urlUtils.addPath(transUrl, '/websocket');\n if (url.slice(0, 5) === 'https') {\n url = 'wss' + url.slice(5);\n } else {\n url = 'ws' + url.slice(4);\n }\n this.url = url;\n\n this.ws = new WebsocketDriver(this.url, [], options);\n this.ws.onmessage = function(e) {\n debug('message event', e.data);\n self.emit('message', e.data);\n };\n // Firefox has an interesting bug. If a websocket connection is\n // created after onunload, it stays alive even when user\n // navigates away from the page. In such situation let's lie -\n // let's not open the ws connection at all. See:\n // https://github.com/sockjs/sockjs-client/issues/28\n // https://bugzilla.mozilla.org/show_bug.cgi?id=696085\n this.unloadRef = utils.unloadAdd(function() {\n debug('unload');\n self.ws.close();\n });\n this.ws.onclose = function(e) {\n debug('close event', e.code, e.reason);\n self.emit('close', e.code, e.reason);\n self._cleanup();\n };\n this.ws.onerror = function(e) {\n debug('error event', e);\n self.emit('close', 1006, 'WebSocket connection broken');\n self._cleanup();\n };\n}\n\ninherits(WebSocketTransport, EventEmitter);\n\nWebSocketTransport.prototype.send = function(data) {\n var msg = '[' + data + ']';\n debug('send', msg);\n this.ws.send(msg);\n};\n\nWebSocketTransport.prototype.close = function() {\n debug('close');\n var ws = this.ws;\n this._cleanup();\n if (ws) {\n ws.close();\n }\n};\n\nWebSocketTransport.prototype._cleanup = function() {\n debug('_cleanup');\n var ws = this.ws;\n if (ws) {\n ws.onmessage = ws.onclose = ws.onerror = null;\n }\n utils.unloadDel(this.unloadRef);\n this.unloadRef = this.ws = null;\n this.removeAllListeners();\n};\n\nWebSocketTransport.enabled = function() {\n debug('enabled');\n return !!WebsocketDriver;\n};\nWebSocketTransport.transportName = 'websocket';\n\n// In theory, ws should require 1 round trip. But in chrome, this is\n// not very stable over SSL. Most likely a ws connection requires a\n// separate SSL connection, in which case 2 round trips are an\n// absolute minumum.\nWebSocketTransport.roundTrips = 2;\n\nmodule.exports = WebSocketTransport;\n","'use strict';\n\nvar Driver = global.WebSocket || global.MozWebSocket;\nif (Driver) {\n\tmodule.exports = function WebSocketBrowserDriver(url) {\n\t\treturn new Driver(url);\n\t};\n} else {\n\tmodule.exports = undefined;\n}\n","'use strict';\n\nvar inherits = require('inherits')\n , urlUtils = require('../../utils/url')\n , BufferedSender = require('./buffered-sender')\n , Polling = require('./polling')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:sender-receiver');\n}\n\nfunction SenderReceiver(transUrl, urlSuffix, senderFunc, Receiver, AjaxObject) {\n var pollUrl = urlUtils.addPath(transUrl, urlSuffix);\n debug(pollUrl);\n var self = this;\n BufferedSender.call(this, transUrl, senderFunc);\n\n this.poll = new Polling(Receiver, pollUrl, AjaxObject);\n this.poll.on('message', function(msg) {\n debug('poll message', msg);\n self.emit('message', msg);\n });\n this.poll.once('close', function(code, reason) {\n debug('poll close', code, reason);\n self.poll = null;\n self.emit('close', code, reason);\n self.close();\n });\n}\n\ninherits(SenderReceiver, BufferedSender);\n\nSenderReceiver.prototype.close = function() {\n BufferedSender.prototype.close.call(this);\n debug('close');\n this.removeAllListeners();\n if (this.poll) {\n this.poll.abort();\n this.poll = null;\n }\n};\n\nmodule.exports = SenderReceiver;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:buffered-sender');\n}\n\nfunction BufferedSender(url, sender) {\n debug(url);\n EventEmitter.call(this);\n this.sendBuffer = [];\n this.sender = sender;\n this.url = url;\n}\n\ninherits(BufferedSender, EventEmitter);\n\nBufferedSender.prototype.send = function(message) {\n debug('send', message);\n this.sendBuffer.push(message);\n if (!this.sendStop) {\n this.sendSchedule();\n }\n};\n\n// For polling transports in a situation when in the message callback,\n// new message is being send. If the sending connection was started\n// before receiving one, it is possible to saturate the network and\n// timeout due to the lack of receiving socket. To avoid that we delay\n// sending messages by some small time, in order to let receiving\n// connection be started beforehand. This is only a halfmeasure and\n// does not fix the big problem, but it does make the tests go more\n// stable on slow networks.\nBufferedSender.prototype.sendScheduleWait = function() {\n debug('sendScheduleWait');\n var self = this;\n var tref;\n this.sendStop = function() {\n debug('sendStop');\n self.sendStop = null;\n clearTimeout(tref);\n };\n tref = setTimeout(function() {\n debug('timeout');\n self.sendStop = null;\n self.sendSchedule();\n }, 25);\n};\n\nBufferedSender.prototype.sendSchedule = function() {\n debug('sendSchedule', this.sendBuffer.length);\n var self = this;\n if (this.sendBuffer.length > 0) {\n var payload = '[' + this.sendBuffer.join(',') + ']';\n this.sendStop = this.sender(this.url, payload, function(err) {\n self.sendStop = null;\n if (err) {\n debug('error', err);\n self.emit('close', err.code || 1006, 'Sending error: ' + err);\n self.close();\n } else {\n self.sendScheduleWait();\n }\n });\n this.sendBuffer = [];\n }\n};\n\nBufferedSender.prototype._cleanup = function() {\n debug('_cleanup');\n this.removeAllListeners();\n};\n\nBufferedSender.prototype.close = function() {\n debug('close');\n this._cleanup();\n if (this.sendStop) {\n this.sendStop();\n this.sendStop = null;\n }\n};\n\nmodule.exports = BufferedSender;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:polling');\n}\n\nfunction Polling(Receiver, receiveUrl, AjaxObject) {\n debug(receiveUrl);\n EventEmitter.call(this);\n this.Receiver = Receiver;\n this.receiveUrl = receiveUrl;\n this.AjaxObject = AjaxObject;\n this._scheduleReceiver();\n}\n\ninherits(Polling, EventEmitter);\n\nPolling.prototype._scheduleReceiver = function() {\n debug('_scheduleReceiver');\n var self = this;\n var poll = this.poll = new this.Receiver(this.receiveUrl, this.AjaxObject);\n\n poll.on('message', function(msg) {\n debug('message', msg);\n self.emit('message', msg);\n });\n\n poll.once('close', function(code, reason) {\n debug('close', code, reason, self.pollIsClosing);\n self.poll = poll = null;\n\n if (!self.pollIsClosing) {\n if (reason === 'network') {\n self._scheduleReceiver();\n } else {\n self.emit('close', code || 1006, reason);\n self.removeAllListeners();\n }\n }\n });\n};\n\nPolling.prototype.abort = function() {\n debug('abort');\n this.removeAllListeners();\n this.pollIsClosing = true;\n if (this.poll) {\n this.poll.abort();\n }\n};\n\nmodule.exports = Polling;\n","'use strict';\n\nvar inherits = require('inherits')\n , urlUtils = require('../../utils/url')\n , SenderReceiver = require('./sender-receiver')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:ajax-based');\n}\n\nfunction createAjaxSender(AjaxObject) {\n return function(url, payload, callback) {\n debug('create ajax sender', url, payload);\n var opt = {};\n if (typeof payload === 'string') {\n opt.headers = {'Content-type': 'text/plain'};\n }\n var ajaxUrl = urlUtils.addPath(url, '/xhr_send');\n var xo = new AjaxObject('POST', ajaxUrl, payload, opt);\n xo.once('finish', function(status) {\n debug('finish', status);\n xo = null;\n\n if (status !== 200 && status !== 204) {\n return callback(new Error('http status ' + status));\n }\n callback();\n });\n return function() {\n debug('abort');\n xo.close();\n xo = null;\n\n var err = new Error('Aborted');\n err.code = 1000;\n callback(err);\n };\n };\n}\n\nfunction AjaxBasedTransport(transUrl, urlSuffix, Receiver, AjaxObject) {\n SenderReceiver.call(this, transUrl, urlSuffix, createAjaxSender(AjaxObject), Receiver, AjaxObject);\n}\n\ninherits(AjaxBasedTransport, SenderReceiver);\n\nmodule.exports = AjaxBasedTransport;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:receiver:xhr');\n}\n\nfunction XhrReceiver(url, AjaxObject) {\n debug(url);\n EventEmitter.call(this);\n var self = this;\n\n this.bufferPosition = 0;\n\n this.xo = new AjaxObject('POST', url, null);\n this.xo.on('chunk', this._chunkHandler.bind(this));\n this.xo.once('finish', function(status, text) {\n debug('finish', status, text);\n self._chunkHandler(status, text);\n self.xo = null;\n var reason = status === 200 ? 'network' : 'permanent';\n debug('close', reason);\n self.emit('close', null, reason);\n self._cleanup();\n });\n}\n\ninherits(XhrReceiver, EventEmitter);\n\nXhrReceiver.prototype._chunkHandler = function(status, text) {\n debug('_chunkHandler', status);\n if (status !== 200 || !text) {\n return;\n }\n\n for (var idx = -1; ; this.bufferPosition += idx + 1) {\n var buf = text.slice(this.bufferPosition);\n idx = buf.indexOf('\\n');\n if (idx === -1) {\n break;\n }\n var msg = buf.slice(0, idx);\n if (msg) {\n debug('message', msg);\n this.emit('message', msg);\n }\n }\n};\n\nXhrReceiver.prototype._cleanup = function() {\n debug('_cleanup');\n this.removeAllListeners();\n};\n\nXhrReceiver.prototype.abort = function() {\n debug('abort');\n if (this.xo) {\n this.xo.close();\n debug('close');\n this.emit('close', null, 'user');\n this.xo = null;\n }\n this._cleanup();\n};\n\nmodule.exports = XhrReceiver;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , utils = require('../../utils/event')\n , urlUtils = require('../../utils/url')\n , XHR = global.XMLHttpRequest\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:browser:xhr');\n}\n\nfunction AbstractXHRObject(method, url, payload, opts) {\n debug(method, url);\n var self = this;\n EventEmitter.call(this);\n\n setTimeout(function () {\n self._start(method, url, payload, opts);\n }, 0);\n}\n\ninherits(AbstractXHRObject, EventEmitter);\n\nAbstractXHRObject.prototype._start = function(method, url, payload, opts) {\n var self = this;\n\n try {\n this.xhr = new XHR();\n } catch (x) {\n // intentionally empty\n }\n\n if (!this.xhr) {\n debug('no xhr');\n this.emit('finish', 0, 'no xhr support');\n this._cleanup();\n return;\n }\n\n // several browsers cache POSTs\n url = urlUtils.addQuery(url, 't=' + (+new Date()));\n\n // Explorer tends to keep connection open, even after the\n // tab gets closed: http://bugs.jquery.com/ticket/5280\n this.unloadRef = utils.unloadAdd(function() {\n debug('unload cleanup');\n self._cleanup(true);\n });\n try {\n this.xhr.open(method, url, true);\n if (this.timeout && 'timeout' in this.xhr) {\n this.xhr.timeout = this.timeout;\n this.xhr.ontimeout = function() {\n debug('xhr timeout');\n self.emit('finish', 0, '');\n self._cleanup(false);\n };\n }\n } catch (e) {\n debug('exception', e);\n // IE raises an exception on wrong port.\n this.emit('finish', 0, '');\n this._cleanup(false);\n return;\n }\n\n if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) {\n debug('withCredentials');\n // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest :\n // \"This never affects same-site requests.\"\n\n this.xhr.withCredentials = true;\n }\n if (opts && opts.headers) {\n for (var key in opts.headers) {\n this.xhr.setRequestHeader(key, opts.headers[key]);\n }\n }\n\n this.xhr.onreadystatechange = function() {\n if (self.xhr) {\n var x = self.xhr;\n var text, status;\n debug('readyState', x.readyState);\n switch (x.readyState) {\n case 3:\n // IE doesn't like peeking into responseText or status\n // on Microsoft.XMLHTTP and readystate=3\n try {\n status = x.status;\n text = x.responseText;\n } catch (e) {\n // intentionally empty\n }\n debug('status', status);\n // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450\n if (status === 1223) {\n status = 204;\n }\n\n // IE does return readystate == 3 for 404 answers.\n if (status === 200 && text && text.length > 0) {\n debug('chunk');\n self.emit('chunk', status, text);\n }\n break;\n case 4:\n status = x.status;\n debug('status', status);\n // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450\n if (status === 1223) {\n status = 204;\n }\n // IE returns this for a bad port\n // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383770(v=vs.85).aspx\n if (status === 12005 || status === 12029) {\n status = 0;\n }\n\n debug('finish', status, x.responseText);\n self.emit('finish', status, x.responseText);\n self._cleanup(false);\n break;\n }\n }\n };\n\n try {\n self.xhr.send(payload);\n } catch (e) {\n self.emit('finish', 0, '');\n self._cleanup(false);\n }\n};\n\nAbstractXHRObject.prototype._cleanup = function(abort) {\n debug('cleanup');\n if (!this.xhr) {\n return;\n }\n this.removeAllListeners();\n utils.unloadDel(this.unloadRef);\n\n // IE needs this field to be a function\n this.xhr.onreadystatechange = function() {};\n if (this.xhr.ontimeout) {\n this.xhr.ontimeout = null;\n }\n\n if (abort) {\n try {\n this.xhr.abort();\n } catch (x) {\n // intentionally empty\n }\n }\n this.unloadRef = this.xhr = null;\n};\n\nAbstractXHRObject.prototype.close = function() {\n debug('close');\n this._cleanup(true);\n};\n\nAbstractXHRObject.enabled = !!XHR;\n// override XMLHttpRequest for IE6/7\n// obfuscate to avoid firewalls\nvar axo = ['Active'].concat('Object').join('X');\nif (!AbstractXHRObject.enabled && (axo in global)) {\n debug('overriding xmlhttprequest');\n XHR = function() {\n try {\n return new global[axo]('Microsoft.XMLHTTP');\n } catch (e) {\n return null;\n }\n };\n AbstractXHRObject.enabled = !!new XHR();\n}\n\nvar cors = false;\ntry {\n cors = 'withCredentials' in new XHR();\n} catch (ignored) {\n // intentionally empty\n}\n\nAbstractXHRObject.supportsCORS = cors;\n\nmodule.exports = AbstractXHRObject;\n","'use strict';\n\nvar inherits = require('inherits')\n , XhrDriver = require('../driver/xhr')\n ;\n\nfunction XHRCorsObject(method, url, payload, opts) {\n XhrDriver.call(this, method, url, payload, opts);\n}\n\ninherits(XHRCorsObject, XhrDriver);\n\nXHRCorsObject.enabled = XhrDriver.enabled && XhrDriver.supportsCORS;\n\nmodule.exports = XHRCorsObject;\n","'use strict';\n\nvar inherits = require('inherits')\n , XhrDriver = require('../driver/xhr')\n ;\n\nfunction XHRLocalObject(method, url, payload /*, opts */) {\n XhrDriver.call(this, method, url, payload, {\n noCredentials: true\n });\n}\n\ninherits(XHRLocalObject, XhrDriver);\n\nXHRLocalObject.enabled = XhrDriver.enabled;\n\nmodule.exports = XHRLocalObject;\n","'use strict';\n\nmodule.exports = {\n isOpera: function() {\n return global.navigator &&\n /opera/i.test(global.navigator.userAgent);\n }\n\n, isKonqueror: function() {\n return global.navigator &&\n /konqueror/i.test(global.navigator.userAgent);\n }\n\n // #187 wrap document.domain in try/catch because of WP8 from file:///\n, hasDomain: function () {\n // non-browser client always has a domain\n if (!global.document) {\n return true;\n }\n\n try {\n return !!global.document.domain;\n } catch (e) {\n return false;\n }\n }\n};\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , XhrReceiver = require('./receiver/xhr')\n , XHRCorsObject = require('./sender/xhr-cors')\n , XHRLocalObject = require('./sender/xhr-local')\n , browser = require('../utils/browser')\n ;\n\nfunction XhrStreamingTransport(transUrl) {\n if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XHRCorsObject);\n}\n\ninherits(XhrStreamingTransport, AjaxBasedTransport);\n\nXhrStreamingTransport.enabled = function(info) {\n if (info.nullOrigin) {\n return false;\n }\n // Opera doesn't support xhr-streaming #60\n // But it might be able to #92\n if (browser.isOpera()) {\n return false;\n }\n\n return XHRCorsObject.enabled;\n};\n\nXhrStreamingTransport.transportName = 'xhr-streaming';\nXhrStreamingTransport.roundTrips = 2; // preflight, ajax\n\n// Safari gets confused when a streaming ajax request is started\n// before onload. This causes the load indicator to spin indefinetely.\n// Only require body when used in a browser\nXhrStreamingTransport.needBody = !!global.document;\n\nmodule.exports = XhrStreamingTransport;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , eventUtils = require('../../utils/event')\n , browser = require('../../utils/browser')\n , urlUtils = require('../../utils/url')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:sender:xdr');\n}\n\n// References:\n// http://ajaxian.com/archives/100-line-ajax-wrapper\n// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx\n\nfunction XDRObject(method, url, payload) {\n debug(method, url);\n var self = this;\n EventEmitter.call(this);\n\n setTimeout(function() {\n self._start(method, url, payload);\n }, 0);\n}\n\ninherits(XDRObject, EventEmitter);\n\nXDRObject.prototype._start = function(method, url, payload) {\n debug('_start');\n var self = this;\n var xdr = new global.XDomainRequest();\n // IE caches even POSTs\n url = urlUtils.addQuery(url, 't=' + (+new Date()));\n\n xdr.onerror = function() {\n debug('onerror');\n self._error();\n };\n xdr.ontimeout = function() {\n debug('ontimeout');\n self._error();\n };\n xdr.onprogress = function() {\n debug('progress', xdr.responseText);\n self.emit('chunk', 200, xdr.responseText);\n };\n xdr.onload = function() {\n debug('load');\n self.emit('finish', 200, xdr.responseText);\n self._cleanup(false);\n };\n this.xdr = xdr;\n this.unloadRef = eventUtils.unloadAdd(function() {\n self._cleanup(true);\n });\n try {\n // Fails with AccessDenied if port number is bogus\n this.xdr.open(method, url);\n if (this.timeout) {\n this.xdr.timeout = this.timeout;\n }\n this.xdr.send(payload);\n } catch (x) {\n this._error();\n }\n};\n\nXDRObject.prototype._error = function() {\n this.emit('finish', 0, '');\n this._cleanup(false);\n};\n\nXDRObject.prototype._cleanup = function(abort) {\n debug('cleanup', abort);\n if (!this.xdr) {\n return;\n }\n this.removeAllListeners();\n eventUtils.unloadDel(this.unloadRef);\n\n this.xdr.ontimeout = this.xdr.onerror = this.xdr.onprogress = this.xdr.onload = null;\n if (abort) {\n try {\n this.xdr.abort();\n } catch (x) {\n // intentionally empty\n }\n }\n this.unloadRef = this.xdr = null;\n};\n\nXDRObject.prototype.close = function() {\n debug('close');\n this._cleanup(true);\n};\n\n// IE 8/9 if the request target uses the same scheme - #79\nXDRObject.enabled = !!(global.XDomainRequest && browser.hasDomain());\n\nmodule.exports = XDRObject;\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , XhrReceiver = require('./receiver/xhr')\n , XDRObject = require('./sender/xdr')\n ;\n\n// According to:\n// http://stackoverflow.com/questions/1641507/detect-browser-support-for-cross-domain-xmlhttprequests\n// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/\n\nfunction XdrStreamingTransport(transUrl) {\n if (!XDRObject.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XDRObject);\n}\n\ninherits(XdrStreamingTransport, AjaxBasedTransport);\n\nXdrStreamingTransport.enabled = function(info) {\n if (info.cookie_needed || info.nullOrigin) {\n return false;\n }\n return XDRObject.enabled && info.sameScheme;\n};\n\nXdrStreamingTransport.transportName = 'xdr-streaming';\nXdrStreamingTransport.roundTrips = 2; // preflight, ajax\n\nmodule.exports = XdrStreamingTransport;\n","module.exports = global.EventSource;\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , EventSourceReceiver = require('./receiver/eventsource')\n , XHRCorsObject = require('./sender/xhr-cors')\n , EventSourceDriver = require('eventsource')\n ;\n\nfunction EventSourceTransport(transUrl) {\n if (!EventSourceTransport.enabled()) {\n throw new Error('Transport created when disabled');\n }\n\n AjaxBasedTransport.call(this, transUrl, '/eventsource', EventSourceReceiver, XHRCorsObject);\n}\n\ninherits(EventSourceTransport, AjaxBasedTransport);\n\nEventSourceTransport.enabled = function() {\n return !!EventSourceDriver;\n};\n\nEventSourceTransport.transportName = 'eventsource';\nEventSourceTransport.roundTrips = 2;\n\nmodule.exports = EventSourceTransport;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n , EventSourceDriver = require('eventsource')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:receiver:eventsource');\n}\n\nfunction EventSourceReceiver(url) {\n debug(url);\n EventEmitter.call(this);\n\n var self = this;\n var es = this.es = new EventSourceDriver(url);\n es.onmessage = function(e) {\n debug('message', e.data);\n self.emit('message', decodeURI(e.data));\n };\n es.onerror = function(e) {\n debug('error', es.readyState, e);\n // ES on reconnection has readyState = 0 or 1.\n // on network error it's CLOSED = 2\n var reason = (es.readyState !== 2 ? 'network' : 'permanent');\n self._cleanup();\n self._close(reason);\n };\n}\n\ninherits(EventSourceReceiver, EventEmitter);\n\nEventSourceReceiver.prototype.abort = function() {\n debug('abort');\n this._cleanup();\n this._close('user');\n};\n\nEventSourceReceiver.prototype._cleanup = function() {\n debug('cleanup');\n var es = this.es;\n if (es) {\n es.onmessage = es.onerror = null;\n es.close();\n this.es = null;\n }\n};\n\nEventSourceReceiver.prototype._close = function(reason) {\n debug('close', reason);\n var self = this;\n // Safari and chrome < 15 crash if we close window before\n // waiting for ES cleanup. See:\n // https://code.google.com/p/chromium/issues/detail?id=89155\n setTimeout(function() {\n self.emit('close', null, reason);\n self.removeAllListeners();\n }, 200);\n};\n\nmodule.exports = EventSourceReceiver;\n","module.exports = '1.6.1';\n","'use strict';\n\nvar eventUtils = require('./event')\n , browser = require('./browser')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:utils:iframe');\n}\n\nmodule.exports = {\n WPrefix: '_jp'\n, currentWindowId: null\n\n, polluteGlobalNamespace: function() {\n if (!(module.exports.WPrefix in global)) {\n global[module.exports.WPrefix] = {};\n }\n }\n\n, postMessage: function(type, data) {\n if (global.parent !== global) {\n global.parent.postMessage(JSON.stringify({\n windowId: module.exports.currentWindowId\n , type: type\n , data: data || ''\n }), '*');\n } else {\n debug('Cannot postMessage, no parent window.', type, data);\n }\n }\n\n, createIframe: function(iframeUrl, errorCallback) {\n var iframe = global.document.createElement('iframe');\n var tref, unloadRef;\n var unattach = function() {\n debug('unattach');\n clearTimeout(tref);\n // Explorer had problems with that.\n try {\n iframe.onload = null;\n } catch (x) {\n // intentionally empty\n }\n iframe.onerror = null;\n };\n var cleanup = function() {\n debug('cleanup');\n if (iframe) {\n unattach();\n // This timeout makes chrome fire onbeforeunload event\n // within iframe. Without the timeout it goes straight to\n // onunload.\n setTimeout(function() {\n if (iframe) {\n iframe.parentNode.removeChild(iframe);\n }\n iframe = null;\n }, 0);\n eventUtils.unloadDel(unloadRef);\n }\n };\n var onerror = function(err) {\n debug('onerror', err);\n if (iframe) {\n cleanup();\n errorCallback(err);\n }\n };\n var post = function(msg, origin) {\n debug('post', msg, origin);\n setTimeout(function() {\n try {\n // When the iframe is not loaded, IE raises an exception\n // on 'contentWindow'.\n if (iframe && iframe.contentWindow) {\n iframe.contentWindow.postMessage(msg, origin);\n }\n } catch (x) {\n // intentionally empty\n }\n }, 0);\n };\n\n iframe.src = iframeUrl;\n iframe.style.display = 'none';\n iframe.style.position = 'absolute';\n iframe.onerror = function() {\n onerror('onerror');\n };\n iframe.onload = function() {\n debug('onload');\n // `onload` is triggered before scripts on the iframe are\n // executed. Give it few seconds to actually load stuff.\n clearTimeout(tref);\n tref = setTimeout(function() {\n onerror('onload timeout');\n }, 2000);\n };\n global.document.body.appendChild(iframe);\n tref = setTimeout(function() {\n onerror('timeout');\n }, 15000);\n unloadRef = eventUtils.unloadAdd(cleanup);\n return {\n post: post\n , cleanup: cleanup\n , loaded: unattach\n };\n }\n\n/* eslint no-undef: \"off\", new-cap: \"off\" */\n, createHtmlfile: function(iframeUrl, errorCallback) {\n var axo = ['Active'].concat('Object').join('X');\n var doc = new global[axo]('htmlfile');\n var tref, unloadRef;\n var iframe;\n var unattach = function() {\n clearTimeout(tref);\n iframe.onerror = null;\n };\n var cleanup = function() {\n if (doc) {\n unattach();\n eventUtils.unloadDel(unloadRef);\n iframe.parentNode.removeChild(iframe);\n iframe = doc = null;\n CollectGarbage();\n }\n };\n var onerror = function(r) {\n debug('onerror', r);\n if (doc) {\n cleanup();\n errorCallback(r);\n }\n };\n var post = function(msg, origin) {\n try {\n // When the iframe is not loaded, IE raises an exception\n // on 'contentWindow'.\n setTimeout(function() {\n if (iframe && iframe.contentWindow) {\n iframe.contentWindow.postMessage(msg, origin);\n }\n }, 0);\n } catch (x) {\n // intentionally empty\n }\n };\n\n doc.open();\n doc.write('<html><s' + 'cript>' +\n 'document.domain=\"' + global.document.domain + '\";' +\n '</s' + 'cript></html>');\n doc.close();\n doc.parentWindow[module.exports.WPrefix] = global[module.exports.WPrefix];\n var c = doc.createElement('div');\n doc.body.appendChild(c);\n iframe = doc.createElement('iframe');\n c.appendChild(iframe);\n iframe.src = iframeUrl;\n iframe.onerror = function() {\n onerror('onerror');\n };\n tref = setTimeout(function() {\n onerror('timeout');\n }, 15000);\n unloadRef = eventUtils.unloadAdd(cleanup);\n return {\n post: post\n , cleanup: cleanup\n , loaded: unattach\n };\n }\n};\n\nmodule.exports.iframeEnabled = false;\nif (global.document) {\n // postMessage misbehaves in konqueror 4.6.5 - the messages are delivered with\n // huge delay, or not at all.\n module.exports.iframeEnabled = (typeof global.postMessage === 'function' ||\n typeof global.postMessage === 'object') && (!browser.isKonqueror());\n}\n","'use strict';\n\n// Few cool transports do work only for same-origin. In order to make\n// them work cross-domain we shall use iframe, served from the\n// remote domain. New browsers have capabilities to communicate with\n// cross domain iframe using postMessage(). In IE it was implemented\n// from IE 8+, but of course, IE got some details wrong:\n// http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx\n// http://stevesouders.com/misc/test-postmessage.php\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n , version = require('../version')\n , urlUtils = require('../utils/url')\n , iframeUtils = require('../utils/iframe')\n , eventUtils = require('../utils/event')\n , random = require('../utils/random')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:transport:iframe');\n}\n\nfunction IframeTransport(transport, transUrl, baseUrl) {\n if (!IframeTransport.enabled()) {\n throw new Error('Transport created when disabled');\n }\n EventEmitter.call(this);\n\n var self = this;\n this.origin = urlUtils.getOrigin(baseUrl);\n this.baseUrl = baseUrl;\n this.transUrl = transUrl;\n this.transport = transport;\n this.windowId = random.string(8);\n\n var iframeUrl = urlUtils.addPath(baseUrl, '/iframe.html') + '#' + this.windowId;\n debug(transport, transUrl, iframeUrl);\n\n this.iframeObj = iframeUtils.createIframe(iframeUrl, function(r) {\n debug('err callback');\n self.emit('close', 1006, 'Unable to load an iframe (' + r + ')');\n self.close();\n });\n\n this.onmessageCallback = this._message.bind(this);\n eventUtils.attachEvent('message', this.onmessageCallback);\n}\n\ninherits(IframeTransport, EventEmitter);\n\nIframeTransport.prototype.close = function() {\n debug('close');\n this.removeAllListeners();\n if (this.iframeObj) {\n eventUtils.detachEvent('message', this.onmessageCallback);\n try {\n // When the iframe is not loaded, IE raises an exception\n // on 'contentWindow'.\n this.postMessage('c');\n } catch (x) {\n // intentionally empty\n }\n this.iframeObj.cleanup();\n this.iframeObj = null;\n this.onmessageCallback = this.iframeObj = null;\n }\n};\n\nIframeTransport.prototype._message = function(e) {\n debug('message', e.data);\n if (!urlUtils.isOriginEqual(e.origin, this.origin)) {\n debug('not same origin', e.origin, this.origin);\n return;\n }\n\n var iframeMessage;\n try {\n iframeMessage = JSON.parse(e.data);\n } catch (ignored) {\n debug('bad json', e.data);\n return;\n }\n\n if (iframeMessage.windowId !== this.windowId) {\n debug('mismatched window id', iframeMessage.windowId, this.windowId);\n return;\n }\n\n switch (iframeMessage.type) {\n case 's':\n this.iframeObj.loaded();\n // window global dependency\n this.postMessage('s', JSON.stringify([\n version\n , this.transport\n , this.transUrl\n , this.baseUrl\n ]));\n break;\n case 't':\n this.emit('message', iframeMessage.data);\n break;\n case 'c':\n var cdata;\n try {\n cdata = JSON.parse(iframeMessage.data);\n } catch (ignored) {\n debug('bad json', iframeMessage.data);\n return;\n }\n this.emit('close', cdata[0], cdata[1]);\n this.close();\n break;\n }\n};\n\nIframeTransport.prototype.postMessage = function(type, data) {\n debug('postMessage', type, data);\n this.iframeObj.post(JSON.stringify({\n windowId: this.windowId\n , type: type\n , data: data || ''\n }), this.origin);\n};\n\nIframeTransport.prototype.send = function(message) {\n debug('send', message);\n this.postMessage('m', message);\n};\n\nIframeTransport.enabled = function() {\n return iframeUtils.iframeEnabled;\n};\n\nIframeTransport.transportName = 'iframe';\nIframeTransport.roundTrips = 2;\n\nmodule.exports = IframeTransport;\n","'use strict';\n\nmodule.exports = {\n isObject: function(obj) {\n var type = typeof obj;\n return type === 'function' || type === 'object' && !!obj;\n }\n\n, extend: function(obj) {\n if (!this.isObject(obj)) {\n return obj;\n }\n var source, prop;\n for (var i = 1, length = arguments.length; i < length; i++) {\n source = arguments[i];\n for (prop in source) {\n if (Object.prototype.hasOwnProperty.call(source, prop)) {\n obj[prop] = source[prop];\n }\n }\n }\n return obj;\n }\n};\n","'use strict';\n\nvar inherits = require('inherits')\n , IframeTransport = require('../iframe')\n , objectUtils = require('../../utils/object')\n ;\n\nmodule.exports = function(transport) {\n\n function IframeWrapTransport(transUrl, baseUrl) {\n IframeTransport.call(this, transport.transportName, transUrl, baseUrl);\n }\n\n inherits(IframeWrapTransport, IframeTransport);\n\n IframeWrapTransport.enabled = function(url, info) {\n if (!global.document) {\n return false;\n }\n\n var iframeInfo = objectUtils.extend({}, info);\n iframeInfo.sameOrigin = true;\n return transport.enabled(iframeInfo) && IframeTransport.enabled();\n };\n\n IframeWrapTransport.transportName = 'iframe-' + transport.transportName;\n IframeWrapTransport.needBody = true;\n IframeWrapTransport.roundTrips = IframeTransport.roundTrips + transport.roundTrips - 1; // html, javascript (2) + transport - no CORS (1)\n\n IframeWrapTransport.facadeTransport = transport;\n\n return IframeWrapTransport;\n};\n","'use strict';\n\nvar inherits = require('inherits')\n , HtmlfileReceiver = require('./receiver/htmlfile')\n , XHRLocalObject = require('./sender/xhr-local')\n , AjaxBasedTransport = require('./lib/ajax-based')\n ;\n\nfunction HtmlFileTransport(transUrl) {\n if (!HtmlfileReceiver.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/htmlfile', HtmlfileReceiver, XHRLocalObject);\n}\n\ninherits(HtmlFileTransport, AjaxBasedTransport);\n\nHtmlFileTransport.enabled = function(info) {\n return HtmlfileReceiver.enabled && info.sameOrigin;\n};\n\nHtmlFileTransport.transportName = 'htmlfile';\nHtmlFileTransport.roundTrips = 2;\n\nmodule.exports = HtmlFileTransport;\n","'use strict';\n\nvar inherits = require('inherits')\n , iframeUtils = require('../../utils/iframe')\n , urlUtils = require('../../utils/url')\n , EventEmitter = require('events').EventEmitter\n , random = require('../../utils/random')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:receiver:htmlfile');\n}\n\nfunction HtmlfileReceiver(url) {\n debug(url);\n EventEmitter.call(this);\n var self = this;\n iframeUtils.polluteGlobalNamespace();\n\n this.id = 'a' + random.string(6);\n url = urlUtils.addQuery(url, 'c=' + decodeURIComponent(iframeUtils.WPrefix + '.' + this.id));\n\n debug('using htmlfile', HtmlfileReceiver.htmlfileEnabled);\n var constructFunc = HtmlfileReceiver.htmlfileEnabled ?\n iframeUtils.createHtmlfile : iframeUtils.createIframe;\n\n global[iframeUtils.WPrefix][this.id] = {\n start: function() {\n debug('start');\n self.iframeObj.loaded();\n }\n , message: function(data) {\n debug('message', data);\n self.emit('message', data);\n }\n , stop: function() {\n debug('stop');\n self._cleanup();\n self._close('network');\n }\n };\n this.iframeObj = constructFunc(url, function() {\n debug('callback');\n self._cleanup();\n self._close('permanent');\n });\n}\n\ninherits(HtmlfileReceiver, EventEmitter);\n\nHtmlfileReceiver.prototype.abort = function() {\n debug('abort');\n this._cleanup();\n this._close('user');\n};\n\nHtmlfileReceiver.prototype._cleanup = function() {\n debug('_cleanup');\n if (this.iframeObj) {\n this.iframeObj.cleanup();\n this.iframeObj = null;\n }\n delete global[iframeUtils.WPrefix][this.id];\n};\n\nHtmlfileReceiver.prototype._close = function(reason) {\n debug('_close', reason);\n this.emit('close', null, reason);\n this.removeAllListeners();\n};\n\nHtmlfileReceiver.htmlfileEnabled = false;\n\n// obfuscate to avoid firewalls\nvar axo = ['Active'].concat('Object').join('X');\nif (axo in global) {\n try {\n HtmlfileReceiver.htmlfileEnabled = !!new global[axo]('htmlfile');\n } catch (x) {\n // intentionally empty\n }\n}\n\nHtmlfileReceiver.enabled = HtmlfileReceiver.htmlfileEnabled || iframeUtils.iframeEnabled;\n\nmodule.exports = HtmlfileReceiver;\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , XhrReceiver = require('./receiver/xhr')\n , XHRCorsObject = require('./sender/xhr-cors')\n , XHRLocalObject = require('./sender/xhr-local')\n ;\n\nfunction XhrPollingTransport(transUrl) {\n if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XHRCorsObject);\n}\n\ninherits(XhrPollingTransport, AjaxBasedTransport);\n\nXhrPollingTransport.enabled = function(info) {\n if (info.nullOrigin) {\n return false;\n }\n\n if (XHRLocalObject.enabled && info.sameOrigin) {\n return true;\n }\n return XHRCorsObject.enabled;\n};\n\nXhrPollingTransport.transportName = 'xhr-polling';\nXhrPollingTransport.roundTrips = 2; // preflight, ajax\n\nmodule.exports = XhrPollingTransport;\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , XdrStreamingTransport = require('./xdr-streaming')\n , XhrReceiver = require('./receiver/xhr')\n , XDRObject = require('./sender/xdr')\n ;\n\nfunction XdrPollingTransport(transUrl) {\n if (!XDRObject.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XDRObject);\n}\n\ninherits(XdrPollingTransport, AjaxBasedTransport);\n\nXdrPollingTransport.enabled = XdrStreamingTransport.enabled;\nXdrPollingTransport.transportName = 'xdr-polling';\nXdrPollingTransport.roundTrips = 2; // preflight, ajax\n\nmodule.exports = XdrPollingTransport;\n","'use strict';\n\nvar random = require('../../utils/random')\n , urlUtils = require('../../utils/url')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:sender:jsonp');\n}\n\nvar form, area;\n\nfunction createIframe(id) {\n debug('createIframe', id);\n try {\n // ie6 dynamic iframes with target=\"\" support (thanks Chris Lambacher)\n return global.document.createElement('<iframe name=\"' + id + '\">');\n } catch (x) {\n var iframe = global.document.createElement('iframe');\n iframe.name = id;\n return iframe;\n }\n}\n\nfunction createForm() {\n debug('createForm');\n form = global.document.createElement('form');\n form.style.display = 'none';\n form.style.position = 'absolute';\n form.method = 'POST';\n form.enctype = 'application/x-www-form-urlencoded';\n form.acceptCharset = 'UTF-8';\n\n area = global.document.createElement('textarea');\n area.name = 'd';\n form.appendChild(area);\n\n global.document.body.appendChild(form);\n}\n\nmodule.exports = function(url, payload, callback) {\n debug(url, payload);\n if (!form) {\n createForm();\n }\n var id = 'a' + random.string(8);\n form.target = id;\n form.action = urlUtils.addQuery(urlUtils.addPath(url, '/jsonp_send'), 'i=' + id);\n\n var iframe = createIframe(id);\n iframe.id = id;\n iframe.style.display = 'none';\n form.appendChild(iframe);\n\n try {\n area.value = payload;\n } catch (e) {\n // seriously broken browsers get here\n }\n form.submit();\n\n var completed = function(err) {\n debug('completed', id, err);\n if (!iframe.onerror) {\n return;\n }\n iframe.onreadystatechange = iframe.onerror = iframe.onload = null;\n // Opera mini doesn't like if we GC iframe\n // immediately, thus this timeout.\n setTimeout(function() {\n debug('cleaning up', id);\n iframe.parentNode.removeChild(iframe);\n iframe = null;\n }, 500);\n area.value = '';\n // It is not possible to detect if the iframe succeeded or\n // failed to submit our form.\n callback(err);\n };\n iframe.onerror = function() {\n debug('onerror', id);\n completed();\n };\n iframe.onload = function() {\n debug('onload', id);\n completed();\n };\n iframe.onreadystatechange = function(e) {\n debug('onreadystatechange', id, iframe.readyState, e);\n if (iframe.readyState === 'complete') {\n completed();\n }\n };\n return function() {\n debug('aborted', id);\n completed(new Error('Aborted'));\n };\n};\n","'use strict';\n\n// The simplest and most robust transport, using the well-know cross\n// domain hack - JSONP. This transport is quite inefficient - one\n// message could use up to one http request. But at least it works almost\n// everywhere.\n// Known limitations:\n// o you will get a spinning cursor\n// o for Konqueror a dumb timer is needed to detect errors\n\nvar inherits = require('inherits')\n , SenderReceiver = require('./lib/sender-receiver')\n , JsonpReceiver = require('./receiver/jsonp')\n , jsonpSender = require('./sender/jsonp')\n ;\n\nfunction JsonPTransport(transUrl) {\n if (!JsonPTransport.enabled()) {\n throw new Error('Transport created when disabled');\n }\n SenderReceiver.call(this, transUrl, '/jsonp', jsonpSender, JsonpReceiver);\n}\n\ninherits(JsonPTransport, SenderReceiver);\n\nJsonPTransport.enabled = function() {\n return !!global.document;\n};\n\nJsonPTransport.transportName = 'jsonp-polling';\nJsonPTransport.roundTrips = 1;\nJsonPTransport.needBody = true;\n\nmodule.exports = JsonPTransport;\n","'use strict';\n\nvar utils = require('../../utils/iframe')\n , random = require('../../utils/random')\n , browser = require('../../utils/browser')\n , urlUtils = require('../../utils/url')\n , inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:receiver:jsonp');\n}\n\nfunction JsonpReceiver(url) {\n debug(url);\n var self = this;\n EventEmitter.call(this);\n\n utils.polluteGlobalNamespace();\n\n this.id = 'a' + random.string(6);\n var urlWithId = urlUtils.addQuery(url, 'c=' + encodeURIComponent(utils.WPrefix + '.' + this.id));\n\n global[utils.WPrefix][this.id] = this._callback.bind(this);\n this._createScript(urlWithId);\n\n // Fallback mostly for Konqueror - stupid timer, 35 seconds shall be plenty.\n this.timeoutId = setTimeout(function() {\n debug('timeout');\n self._abort(new Error('JSONP script loaded abnormally (timeout)'));\n }, JsonpReceiver.timeout);\n}\n\ninherits(JsonpReceiver, EventEmitter);\n\nJsonpReceiver.prototype.abort = function() {\n debug('abort');\n if (global[utils.WPrefix][this.id]) {\n var err = new Error('JSONP user aborted read');\n err.code = 1000;\n this._abort(err);\n }\n};\n\nJsonpReceiver.timeout = 35000;\nJsonpReceiver.scriptErrorTimeout = 1000;\n\nJsonpReceiver.prototype._callback = function(data) {\n debug('_callback', data);\n this._cleanup();\n\n if (this.aborting) {\n return;\n }\n\n if (data) {\n debug('message', data);\n this.emit('message', data);\n }\n this.emit('close', null, 'network');\n this.removeAllListeners();\n};\n\nJsonpReceiver.prototype._abort = function(err) {\n debug('_abort', err);\n this._cleanup();\n this.aborting = true;\n this.emit('close', err.code, err.message);\n this.removeAllListeners();\n};\n\nJsonpReceiver.prototype._cleanup = function() {\n debug('_cleanup');\n clearTimeout(this.timeoutId);\n if (this.script2) {\n this.script2.parentNode.removeChild(this.script2);\n this.script2 = null;\n }\n if (this.script) {\n var script = this.script;\n // Unfortunately, you can't really abort script loading of\n // the script.\n script.parentNode.removeChild(script);\n script.onreadystatechange = script.onerror =\n script.onload = script.onclick = null;\n this.script = null;\n }\n delete global[utils.WPrefix][this.id];\n};\n\nJsonpReceiver.prototype._scriptError = function() {\n debug('_scriptError');\n var self = this;\n if (this.errorTimer) {\n return;\n }\n\n this.errorTimer = setTimeout(function() {\n if (!self.loadedOkay) {\n self._abort(new Error('JSONP script loaded abnormally (onerror)'));\n }\n }, JsonpReceiver.scriptErrorTimeout);\n};\n\nJsonpReceiver.prototype._createScript = function(url) {\n debug('_createScript', url);\n var self = this;\n var script = this.script = global.document.createElement('script');\n var script2; // Opera synchronous load trick.\n\n script.id = 'a' + random.string(8);\n script.src = url;\n script.type = 'text/javascript';\n script.charset = 'UTF-8';\n script.onerror = this._scriptError.bind(this);\n script.onload = function() {\n debug('onload');\n self._abort(new Error('JSONP script loaded abnormally (onload)'));\n };\n\n // IE9 fires 'error' event after onreadystatechange or before, in random order.\n // Use loadedOkay to determine if actually errored\n script.onreadystatechange = function() {\n debug('onreadystatechange', script.readyState);\n if (/loaded|closed/.test(script.readyState)) {\n if (script && script.htmlFor && script.onclick) {\n self.loadedOkay = true;\n try {\n // In IE, actually execute the script.\n script.onclick();\n } catch (x) {\n // intentionally empty\n }\n }\n if (script) {\n self._abort(new Error('JSONP script loaded abnormally (onreadystatechange)'));\n }\n }\n };\n // IE: event/htmlFor/onclick trick.\n // One can't rely on proper order for onreadystatechange. In order to\n // make sure, set a 'htmlFor' and 'event' properties, so that\n // script code will be installed as 'onclick' handler for the\n // script object. Later, onreadystatechange, manually execute this\n // code. FF and Chrome doesn't work with 'event' and 'htmlFor'\n // set. For reference see:\n // http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html\n // Also, read on that about script ordering:\n // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order\n if (typeof script.async === 'undefined' && global.document.attachEvent) {\n // According to mozilla docs, in recent browsers script.async defaults\n // to 'true', so we may use it to detect a good browser:\n // https://developer.mozilla.org/en/HTML/Element/script\n if (!browser.isOpera()) {\n // Naively assume we're in IE\n try {\n script.htmlFor = script.id;\n script.event = 'onclick';\n } catch (x) {\n // intentionally empty\n }\n script.async = true;\n } else {\n // Opera, second sync script hack\n script2 = this.script2 = global.document.createElement('script');\n script2.text = \"try{var a = document.getElementById('\" + script.id + \"'); if(a)a.onerror();}catch(x){};\";\n script.async = script2.async = false;\n }\n }\n if (typeof script.async !== 'undefined') {\n script.async = true;\n }\n\n var head = global.document.getElementsByTagName('head')[0];\n head.insertBefore(script, head.firstChild);\n if (script2) {\n head.insertBefore(script2, head.firstChild);\n }\n};\n\nmodule.exports = JsonpReceiver;\n","'use strict';\n\nmodule.exports = [\n // streaming transports\n require('./transport/websocket')\n, require('./transport/xhr-streaming')\n, require('./transport/xdr-streaming')\n, require('./transport/eventsource')\n, require('./transport/lib/iframe-wrap')(require('./transport/eventsource'))\n\n // polling transports\n, require('./transport/htmlfile')\n, require('./transport/lib/iframe-wrap')(require('./transport/htmlfile'))\n, require('./transport/xhr-polling')\n, require('./transport/xdr-polling')\n, require('./transport/lib/iframe-wrap')(require('./transport/xhr-polling'))\n, require('./transport/jsonp-polling')\n];\n","/* eslint-disable */\n/* jscs: disable */\n'use strict';\n\n// pulled specific shims from https://github.com/es-shims/es5-shim\n\nvar ArrayPrototype = Array.prototype;\nvar ObjectPrototype = Object.prototype;\nvar FunctionPrototype = Function.prototype;\nvar StringPrototype = String.prototype;\nvar array_slice = ArrayPrototype.slice;\n\nvar _toString = ObjectPrototype.toString;\nvar isFunction = function (val) {\n return ObjectPrototype.toString.call(val) === '[object Function]';\n};\nvar isArray = function isArray(obj) {\n return _toString.call(obj) === '[object Array]';\n};\nvar isString = function isString(obj) {\n return _toString.call(obj) === '[object String]';\n};\n\nvar supportsDescriptors = Object.defineProperty && (function () {\n try {\n Object.defineProperty({}, 'x', {});\n return true;\n } catch (e) { /* this is ES3 */\n return false;\n }\n}());\n\n// Define configurable, writable and non-enumerable props\n// if they don't exist.\nvar defineProperty;\nif (supportsDescriptors) {\n defineProperty = function (object, name, method, forceAssign) {\n if (!forceAssign && (name in object)) { return; }\n Object.defineProperty(object, name, {\n configurable: true,\n enumerable: false,\n writable: true,\n value: method\n });\n };\n} else {\n defineProperty = function (object, name, method, forceAssign) {\n if (!forceAssign && (name in object)) { return; }\n object[name] = method;\n };\n}\nvar defineProperties = function (object, map, forceAssign) {\n for (var name in map) {\n if (ObjectPrototype.hasOwnProperty.call(map, name)) {\n defineProperty(object, name, map[name], forceAssign);\n }\n }\n};\n\nvar toObject = function (o) {\n if (o == null) { // this matches both null and undefined\n throw new TypeError(\"can't convert \" + o + ' to object');\n }\n return Object(o);\n};\n\n//\n// Util\n// ======\n//\n\n// ES5 9.4\n// http://es5.github.com/#x9.4\n// http://jsperf.com/to-integer\n\nfunction toInteger(num) {\n var n = +num;\n if (n !== n) { // isNaN\n n = 0;\n } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {\n n = (n > 0 || -1) * Math.floor(Math.abs(n));\n }\n return n;\n}\n\nfunction ToUint32(x) {\n return x >>> 0;\n}\n\n//\n// Function\n// ========\n//\n\n// ES-5 15.3.4.5\n// http://es5.github.com/#x15.3.4.5\n\nfunction Empty() {}\n\ndefineProperties(FunctionPrototype, {\n bind: function bind(that) { // .length is 1\n // 1. Let Target be the this value.\n var target = this;\n // 2. If IsCallable(Target) is false, throw a TypeError exception.\n if (!isFunction(target)) {\n throw new TypeError('Function.prototype.bind called on incompatible ' + target);\n }\n // 3. Let A be a new (possibly empty) internal list of all of the\n // argument values provided after thisArg (arg1, arg2 etc), in order.\n // XXX slicedArgs will stand in for \"A\" if used\n var args = array_slice.call(arguments, 1); // for normal call\n // 4. Let F be a new native ECMAScript object.\n // 11. Set the [[Prototype]] internal property of F to the standard\n // built-in Function prototype object as specified in 15.3.3.1.\n // 12. Set the [[Call]] internal property of F as described in\n // 15.3.4.5.1.\n // 13. Set the [[Construct]] internal property of F as described in\n // 15.3.4.5.2.\n // 14. Set the [[HasInstance]] internal property of F as described in\n // 15.3.4.5.3.\n var binder = function () {\n\n if (this instanceof bound) {\n // 15.3.4.5.2 [[Construct]]\n // When the [[Construct]] internal method of a function object,\n // F that was created using the bind function is called with a\n // list of arguments ExtraArgs, the following steps are taken:\n // 1. Let target be the value of F's [[TargetFunction]]\n // internal property.\n // 2. If target has no [[Construct]] internal method, a\n // TypeError exception is thrown.\n // 3. Let boundArgs be the value of F's [[BoundArgs]] internal\n // property.\n // 4. Let args be a new list containing the same values as the\n // list boundArgs in the same order followed by the same\n // values as the list ExtraArgs in the same order.\n // 5. Return the result of calling the [[Construct]] internal\n // method of target providing args as the arguments.\n\n var result = target.apply(\n this,\n args.concat(array_slice.call(arguments))\n );\n if (Object(result) === result) {\n return result;\n }\n return this;\n\n } else {\n // 15.3.4.5.1 [[Call]]\n // When the [[Call]] internal method of a function object, F,\n // which was created using the bind function is called with a\n // this value and a list of arguments ExtraArgs, the following\n // steps are taken:\n // 1. Let boundArgs be the value of F's [[BoundArgs]] internal\n // property.\n // 2. Let boundThis be the value of F's [[BoundThis]] internal\n // property.\n // 3. Let target be the value of F's [[TargetFunction]] internal\n // property.\n // 4. Let args be a new list containing the same values as the\n // list boundArgs in the same order followed by the same\n // values as the list ExtraArgs in the same order.\n // 5. Return the result of calling the [[Call]] internal method\n // of target providing boundThis as the this value and\n // providing args as the arguments.\n\n // equiv: target.call(this, ...boundArgs, ...args)\n return target.apply(\n that,\n args.concat(array_slice.call(arguments))\n );\n\n }\n\n };\n\n // 15. If the [[Class]] internal property of Target is \"Function\", then\n // a. Let L be the length property of Target minus the length of A.\n // b. Set the length own property of F to either 0 or L, whichever is\n // larger.\n // 16. Else set the length own property of F to 0.\n\n var boundLength = Math.max(0, target.length - args.length);\n\n // 17. Set the attributes of the length own property of F to the values\n // specified in 15.3.5.1.\n var boundArgs = [];\n for (var i = 0; i < boundLength; i++) {\n boundArgs.push('$' + i);\n }\n\n // XXX Build a dynamic function with desired amount of arguments is the only\n // way to set the length property of a function.\n // In environments where Content Security Policies enabled (Chrome extensions,\n // for ex.) all use of eval or Function costructor throws an exception.\n // However in all of these environments Function.prototype.bind exists\n // and so this code will never be executed.\n var bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);\n\n if (target.prototype) {\n Empty.prototype = target.prototype;\n bound.prototype = new Empty();\n // Clean up dangling references.\n Empty.prototype = null;\n }\n\n // TODO\n // 18. Set the [[Extensible]] internal property of F to true.\n\n // TODO\n // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).\n // 20. Call the [[DefineOwnProperty]] internal method of F with\n // arguments \"caller\", PropertyDescriptor {[[Get]]: thrower, [[Set]]:\n // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and\n // false.\n // 21. Call the [[DefineOwnProperty]] internal method of F with\n // arguments \"arguments\", PropertyDescriptor {[[Get]]: thrower,\n // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},\n // and false.\n\n // TODO\n // NOTE Function objects created using Function.prototype.bind do not\n // have a prototype property or the [[Code]], [[FormalParameters]], and\n // [[Scope]] internal properties.\n // XXX can't delete prototype in pure-js.\n\n // 22. Return F.\n return bound;\n }\n});\n\n//\n// Array\n// =====\n//\n\n// ES5 15.4.3.2\n// http://es5.github.com/#x15.4.3.2\n// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray\ndefineProperties(Array, { isArray: isArray });\n\n\nvar boxedString = Object('a');\nvar splitString = boxedString[0] !== 'a' || !(0 in boxedString);\n\nvar properlyBoxesContext = function properlyBoxed(method) {\n // Check node 0.6.21 bug where third parameter is not boxed\n var properlyBoxesNonStrict = true;\n var properlyBoxesStrict = true;\n if (method) {\n method.call('foo', function (_, __, context) {\n if (typeof context !== 'object') { properlyBoxesNonStrict = false; }\n });\n\n method.call([1], function () {\n 'use strict';\n properlyBoxesStrict = typeof this === 'string';\n }, 'x');\n }\n return !!method && properlyBoxesNonStrict && properlyBoxesStrict;\n};\n\ndefineProperties(ArrayPrototype, {\n forEach: function forEach(fun /*, thisp*/) {\n var object = toObject(this),\n self = splitString && isString(this) ? this.split('') : object,\n thisp = arguments[1],\n i = -1,\n length = self.length >>> 0;\n\n // If no callback function or if callback is not a callable function\n if (!isFunction(fun)) {\n throw new TypeError(); // TODO message\n }\n\n while (++i < length) {\n if (i in self) {\n // Invoke the callback function with call, passing arguments:\n // context, property value, property key, thisArg object\n // context\n fun.call(thisp, self[i], i, object);\n }\n }\n }\n}, !properlyBoxesContext(ArrayPrototype.forEach));\n\n// ES5 15.4.4.14\n// http://es5.github.com/#x15.4.4.14\n// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf\nvar hasFirefox2IndexOfBug = Array.prototype.indexOf && [0, 1].indexOf(1, 2) !== -1;\ndefineProperties(ArrayPrototype, {\n indexOf: function indexOf(sought /*, fromIndex */ ) {\n var self = splitString && isString(this) ? this.split('') : toObject(this),\n length = self.length >>> 0;\n\n if (!length) {\n return -1;\n }\n\n var i = 0;\n if (arguments.length > 1) {\n i = toInteger(arguments[1]);\n }\n\n // handle negative indices\n i = i >= 0 ? i : Math.max(0, length + i);\n for (; i < length; i++) {\n if (i in self && self[i] === sought) {\n return i;\n }\n }\n return -1;\n }\n}, hasFirefox2IndexOfBug);\n\n//\n// String\n// ======\n//\n\n// ES5 15.5.4.14\n// http://es5.github.com/#x15.5.4.14\n\n// [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]\n// Many browsers do not split properly with regular expressions or they\n// do not perform the split correctly under obscure conditions.\n// See http://blog.stevenlevithan.com/archives/cross-browser-split\n// I've tested in many browsers and this seems to cover the deviant ones:\n// 'ab'.split(/(?:ab)*/) should be [\"\", \"\"], not [\"\"]\n// '.'.split(/(.?)(.?)/) should be [\"\", \".\", \"\", \"\"], not [\"\", \"\"]\n// 'tesst'.split(/(s)*/) should be [\"t\", undefined, \"e\", \"s\", \"t\"], not\n// [undefined, \"t\", undefined, \"e\", ...]\n// ''.split(/.?/) should be [], not [\"\"]\n// '.'.split(/()()/) should be [\".\"], not [\"\", \"\", \".\"]\n\nvar string_split = StringPrototype.split;\nif (\n 'ab'.split(/(?:ab)*/).length !== 2 ||\n '.'.split(/(.?)(.?)/).length !== 4 ||\n 'tesst'.split(/(s)*/)[1] === 't' ||\n 'test'.split(/(?:)/, -1).length !== 4 ||\n ''.split(/.?/).length ||\n '.'.split(/()()/).length > 1\n) {\n (function () {\n var compliantExecNpcg = /()??/.exec('')[1] === void 0; // NPCG: nonparticipating capturing group\n\n StringPrototype.split = function (separator, limit) {\n var string = this;\n if (separator === void 0 && limit === 0) {\n return [];\n }\n\n // If `separator` is not a regex, use native split\n if (_toString.call(separator) !== '[object RegExp]') {\n return string_split.call(this, separator, limit);\n }\n\n var output = [],\n flags = (separator.ignoreCase ? 'i' : '') +\n (separator.multiline ? 'm' : '') +\n (separator.extended ? 'x' : '') + // Proposed for ES6\n (separator.sticky ? 'y' : ''), // Firefox 3+\n lastLastIndex = 0,\n // Make `global` and avoid `lastIndex` issues by working with a copy\n separator2, match, lastIndex, lastLength;\n separator = new RegExp(separator.source, flags + 'g');\n string += ''; // Type-convert\n if (!compliantExecNpcg) {\n // Doesn't need flags gy, but they don't hurt\n separator2 = new RegExp('^' + separator.source + '$(?!\\\\s)', flags);\n }\n /* Values for `limit`, per the spec:\n * If undefined: 4294967295 // Math.pow(2, 32) - 1\n * If 0, Infinity, or NaN: 0\n * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;\n * If negative number: 4294967296 - Math.floor(Math.abs(limit))\n * If other: Type-convert, then use the above rules\n */\n limit = limit === void 0 ?\n -1 >>> 0 : // Math.pow(2, 32) - 1\n ToUint32(limit);\n while (match = separator.exec(string)) {\n // `separator.lastIndex` is not reliable cross-browser\n lastIndex = match.index + match[0].length;\n if (lastIndex > lastLastIndex) {\n output.push(string.slice(lastLastIndex, match.index));\n // Fix browsers whose `exec` methods don't consistently return `undefined` for\n // nonparticipating capturing groups\n if (!compliantExecNpcg && match.length > 1) {\n match[0].replace(separator2, function () {\n for (var i = 1; i < arguments.length - 2; i++) {\n if (arguments[i] === void 0) {\n match[i] = void 0;\n }\n }\n });\n }\n if (match.length > 1 && match.index < string.length) {\n ArrayPrototype.push.apply(output, match.slice(1));\n }\n lastLength = match[0].length;\n lastLastIndex = lastIndex;\n if (output.length >= limit) {\n break;\n }\n }\n if (separator.lastIndex === match.index) {\n separator.lastIndex++; // Avoid an infinite loop\n }\n }\n if (lastLastIndex === string.length) {\n if (lastLength || !separator.test('')) {\n output.push('');\n }\n } else {\n output.push(string.slice(lastLastIndex));\n }\n return output.length > limit ? output.slice(0, limit) : output;\n };\n }());\n\n// [bugfix, chrome]\n// If separator is undefined, then the result array contains just one String,\n// which is the this value (converted to a String). If limit is not undefined,\n// then the output array is truncated so that it contains no more than limit\n// elements.\n// \"0\".split(undefined, 0) -> []\n} else if ('0'.split(void 0, 0).length) {\n StringPrototype.split = function split(separator, limit) {\n if (separator === void 0 && limit === 0) { return []; }\n return string_split.call(this, separator, limit);\n };\n}\n\n// ECMA-262, 3rd B.2.3\n// Not an ECMAScript standard, although ECMAScript 3rd Edition has a\n// non-normative section suggesting uniform semantics and it should be\n// normalized across all browsers\n// [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE\nvar string_substr = StringPrototype.substr;\nvar hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';\ndefineProperties(StringPrototype, {\n substr: function substr(start, length) {\n return string_substr.call(\n this,\n start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start,\n length\n );\n }\n}, hasNegativeSubstrBug);\n","'use strict';\n\n// Some extra characters that Chrome gets wrong, and substitutes with\n// something else on the wire.\n// eslint-disable-next-line no-control-regex, no-misleading-character-class\nvar extraEscapable = /[\\x00-\\x1f\\ud800-\\udfff\\ufffe\\uffff\\u0300-\\u0333\\u033d-\\u0346\\u034a-\\u034c\\u0350-\\u0352\\u0357-\\u0358\\u035c-\\u0362\\u0374\\u037e\\u0387\\u0591-\\u05af\\u05c4\\u0610-\\u0617\\u0653-\\u0654\\u0657-\\u065b\\u065d-\\u065e\\u06df-\\u06e2\\u06eb-\\u06ec\\u0730\\u0732-\\u0733\\u0735-\\u0736\\u073a\\u073d\\u073f-\\u0741\\u0743\\u0745\\u0747\\u07eb-\\u07f1\\u0951\\u0958-\\u095f\\u09dc-\\u09dd\\u09df\\u0a33\\u0a36\\u0a59-\\u0a5b\\u0a5e\\u0b5c-\\u0b5d\\u0e38-\\u0e39\\u0f43\\u0f4d\\u0f52\\u0f57\\u0f5c\\u0f69\\u0f72-\\u0f76\\u0f78\\u0f80-\\u0f83\\u0f93\\u0f9d\\u0fa2\\u0fa7\\u0fac\\u0fb9\\u1939-\\u193a\\u1a17\\u1b6b\\u1cda-\\u1cdb\\u1dc0-\\u1dcf\\u1dfc\\u1dfe\\u1f71\\u1f73\\u1f75\\u1f77\\u1f79\\u1f7b\\u1f7d\\u1fbb\\u1fbe\\u1fc9\\u1fcb\\u1fd3\\u1fdb\\u1fe3\\u1feb\\u1fee-\\u1fef\\u1ff9\\u1ffb\\u1ffd\\u2000-\\u2001\\u20d0-\\u20d1\\u20d4-\\u20d7\\u20e7-\\u20e9\\u2126\\u212a-\\u212b\\u2329-\\u232a\\u2adc\\u302b-\\u302c\\uaab2-\\uaab3\\uf900-\\ufa0d\\ufa10\\ufa12\\ufa15-\\ufa1e\\ufa20\\ufa22\\ufa25-\\ufa26\\ufa2a-\\ufa2d\\ufa30-\\ufa6d\\ufa70-\\ufad9\\ufb1d\\ufb1f\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40-\\ufb41\\ufb43-\\ufb44\\ufb46-\\ufb4e\\ufff0-\\uffff]/g\n , extraLookup;\n\n// This may be quite slow, so let's delay until user actually uses bad\n// characters.\nvar unrollLookup = function(escapable) {\n var i;\n var unrolled = {};\n var c = [];\n for (i = 0; i < 65536; i++) {\n c.push( String.fromCharCode(i) );\n }\n escapable.lastIndex = 0;\n c.join('').replace(escapable, function(a) {\n unrolled[ a ] = '\\\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\n return '';\n });\n escapable.lastIndex = 0;\n return unrolled;\n};\n\n// Quote string, also taking care of unicode characters that browsers\n// often break. Especially, take care of unicode surrogates:\n// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates\nmodule.exports = {\n quote: function(string) {\n var quoted = JSON.stringify(string);\n\n // In most cases this should be very fast and good enough.\n extraEscapable.lastIndex = 0;\n if (!extraEscapable.test(quoted)) {\n return quoted;\n }\n\n if (!extraLookup) {\n extraLookup = unrollLookup(extraEscapable);\n }\n\n return quoted.replace(extraEscapable, function(a) {\n return extraLookup[a];\n });\n }\n};\n","'use strict';\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:utils:transport');\n}\n\nmodule.exports = function(availableTransports) {\n return {\n filterToEnabled: function(transportsWhitelist, info) {\n var transports = {\n main: []\n , facade: []\n };\n if (!transportsWhitelist) {\n transportsWhitelist = [];\n } else if (typeof transportsWhitelist === 'string') {\n transportsWhitelist = [transportsWhitelist];\n }\n\n availableTransports.forEach(function(trans) {\n if (!trans) {\n return;\n }\n\n if (trans.transportName === 'websocket' && info.websocket === false) {\n debug('disabled from server', 'websocket');\n return;\n }\n\n if (transportsWhitelist.length &&\n transportsWhitelist.indexOf(trans.transportName) === -1) {\n debug('not in whitelist', trans.transportName);\n return;\n }\n\n if (trans.enabled(info)) {\n debug('enabled', trans.transportName);\n transports.main.push(trans);\n if (trans.facadeTransport) {\n transports.facade.push(trans.facadeTransport);\n }\n } else {\n debug('disabled', trans.transportName);\n }\n });\n return transports;\n }\n };\n};\n","'use strict';\n\nvar logObject = {};\n['log', 'debug', 'warn'].forEach(function (level) {\n var levelExists;\n\n try {\n levelExists = global.console && global.console[level] && global.console[level].apply;\n } catch(e) {\n // do nothing\n }\n\n logObject[level] = levelExists ? function () {\n return global.console[level].apply(global.console, arguments);\n } : (level === 'log' ? function () {} : logObject.log);\n});\n\nmodule.exports = logObject;\n","'use strict';\n\nfunction Event(eventType) {\n this.type = eventType;\n}\n\nEvent.prototype.initEvent = function(eventType, canBubble, cancelable) {\n this.type = eventType;\n this.bubbles = canBubble;\n this.cancelable = cancelable;\n this.timeStamp = +new Date();\n return this;\n};\n\nEvent.prototype.stopPropagation = function() {};\nEvent.prototype.preventDefault = function() {};\n\nEvent.CAPTURING_PHASE = 1;\nEvent.AT_TARGET = 2;\nEvent.BUBBLING_PHASE = 3;\n\nmodule.exports = Event;\n","'use strict';\n\nmodule.exports = global.location || {\n origin: 'http://localhost:80'\n, protocol: 'http:'\n, host: 'localhost'\n, port: 80\n, href: 'http://localhost/'\n, hash: ''\n};\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , objectUtils = require('./utils/object')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:info-ajax');\n}\n\nfunction InfoAjax(url, AjaxObject) {\n EventEmitter.call(this);\n\n var self = this;\n var t0 = +new Date();\n this.xo = new AjaxObject('GET', url);\n\n this.xo.once('finish', function(status, text) {\n var info, rtt;\n if (status === 200) {\n rtt = (+new Date()) - t0;\n if (text) {\n try {\n info = JSON.parse(text);\n } catch (e) {\n debug('bad json', text);\n }\n }\n\n if (!objectUtils.isObject(info)) {\n info = {};\n }\n }\n self.emit('finish', info, rtt);\n self.removeAllListeners();\n });\n}\n\ninherits(InfoAjax, EventEmitter);\n\nInfoAjax.prototype.close = function() {\n this.removeAllListeners();\n this.xo.close();\n};\n\nmodule.exports = InfoAjax;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n , XHRLocalObject = require('./transport/sender/xhr-local')\n , InfoAjax = require('./info-ajax')\n ;\n\nfunction InfoReceiverIframe(transUrl) {\n var self = this;\n EventEmitter.call(this);\n\n this.ir = new InfoAjax(transUrl, XHRLocalObject);\n this.ir.once('finish', function(info, rtt) {\n self.ir = null;\n self.emit('message', JSON.stringify([info, rtt]));\n });\n}\n\ninherits(InfoReceiverIframe, EventEmitter);\n\nInfoReceiverIframe.transportName = 'iframe-info-receiver';\n\nInfoReceiverIframe.prototype.close = function() {\n if (this.ir) {\n this.ir.close();\n this.ir = null;\n }\n this.removeAllListeners();\n};\n\nmodule.exports = InfoReceiverIframe;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , urlUtils = require('./utils/url')\n , XDR = require('./transport/sender/xdr')\n , XHRCors = require('./transport/sender/xhr-cors')\n , XHRLocal = require('./transport/sender/xhr-local')\n , XHRFake = require('./transport/sender/xhr-fake')\n , InfoIframe = require('./info-iframe')\n , InfoAjax = require('./info-ajax')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:info-receiver');\n}\n\nfunction InfoReceiver(baseUrl, urlInfo) {\n debug(baseUrl);\n var self = this;\n EventEmitter.call(this);\n\n setTimeout(function() {\n self.doXhr(baseUrl, urlInfo);\n }, 0);\n}\n\ninherits(InfoReceiver, EventEmitter);\n\n// TODO this is currently ignoring the list of available transports and the whitelist\n\nInfoReceiver._getReceiver = function(baseUrl, url, urlInfo) {\n // determine method of CORS support (if needed)\n if (urlInfo.sameOrigin) {\n return new InfoAjax(url, XHRLocal);\n }\n if (XHRCors.enabled) {\n return new InfoAjax(url, XHRCors);\n }\n if (XDR.enabled && urlInfo.sameScheme) {\n return new InfoAjax(url, XDR);\n }\n if (InfoIframe.enabled()) {\n return new InfoIframe(baseUrl, url);\n }\n return new InfoAjax(url, XHRFake);\n};\n\nInfoReceiver.prototype.doXhr = function(baseUrl, urlInfo) {\n var self = this\n , url = urlUtils.addPath(baseUrl, '/info')\n ;\n debug('doXhr', url);\n\n this.xo = InfoReceiver._getReceiver(baseUrl, url, urlInfo);\n\n this.timeoutRef = setTimeout(function() {\n debug('timeout');\n self._cleanup(false);\n self.emit('finish');\n }, InfoReceiver.timeout);\n\n this.xo.once('finish', function(info, rtt) {\n debug('finish', info, rtt);\n self._cleanup(true);\n self.emit('finish', info, rtt);\n });\n};\n\nInfoReceiver.prototype._cleanup = function(wasClean) {\n debug('_cleanup');\n clearTimeout(this.timeoutRef);\n this.timeoutRef = null;\n if (!wasClean && this.xo) {\n this.xo.close();\n }\n this.xo = null;\n};\n\nInfoReceiver.prototype.close = function() {\n debug('close');\n this.removeAllListeners();\n this._cleanup(false);\n};\n\nInfoReceiver.timeout = 8000;\n\nmodule.exports = InfoReceiver;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n ;\n\nfunction XHRFake(/* method, url, payload, opts */) {\n var self = this;\n EventEmitter.call(this);\n\n this.to = setTimeout(function() {\n self.emit('finish', 200, '{}');\n }, XHRFake.timeout);\n}\n\ninherits(XHRFake, EventEmitter);\n\nXHRFake.prototype.close = function() {\n clearTimeout(this.to);\n};\n\nXHRFake.timeout = 2000;\n\nmodule.exports = XHRFake;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , utils = require('./utils/event')\n , IframeTransport = require('./transport/iframe')\n , InfoReceiverIframe = require('./info-iframe-receiver')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:info-iframe');\n}\n\nfunction InfoIframe(baseUrl, url) {\n var self = this;\n EventEmitter.call(this);\n\n var go = function() {\n var ifr = self.ifr = new IframeTransport(InfoReceiverIframe.transportName, url, baseUrl);\n\n ifr.once('message', function(msg) {\n if (msg) {\n var d;\n try {\n d = JSON.parse(msg);\n } catch (e) {\n debug('bad json', msg);\n self.emit('finish');\n self.close();\n return;\n }\n\n var info = d[0], rtt = d[1];\n self.emit('finish', info, rtt);\n }\n self.close();\n });\n\n ifr.once('close', function() {\n self.emit('finish');\n self.close();\n });\n };\n\n // TODO this seems the same as the 'needBody' from transports\n if (!global.document.body) {\n utils.attachEvent('load', go);\n } else {\n go();\n }\n}\n\ninherits(InfoIframe, EventEmitter);\n\nInfoIframe.enabled = function() {\n return IframeTransport.enabled();\n};\n\nInfoIframe.prototype.close = function() {\n if (this.ifr) {\n this.ifr.close();\n }\n this.removeAllListeners();\n this.ifr = null;\n};\n\nmodule.exports = InfoIframe;\n","'use strict';\n\nvar urlUtils = require('./utils/url')\n , eventUtils = require('./utils/event')\n , FacadeJS = require('./facade')\n , InfoIframeReceiver = require('./info-iframe-receiver')\n , iframeUtils = require('./utils/iframe')\n , loc = require('./location')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:iframe-bootstrap');\n}\n\nmodule.exports = function(SockJS, availableTransports) {\n var transportMap = {};\n availableTransports.forEach(function(at) {\n if (at.facadeTransport) {\n transportMap[at.facadeTransport.transportName] = at.facadeTransport;\n }\n });\n\n // hard-coded for the info iframe\n // TODO see if we can make this more dynamic\n transportMap[InfoIframeReceiver.transportName] = InfoIframeReceiver;\n var parentOrigin;\n\n /* eslint-disable camelcase */\n SockJS.bootstrap_iframe = function() {\n /* eslint-enable camelcase */\n var facade;\n iframeUtils.currentWindowId = loc.hash.slice(1);\n var onMessage = function(e) {\n if (e.source !== parent) {\n return;\n }\n if (typeof parentOrigin === 'undefined') {\n parentOrigin = e.origin;\n }\n if (e.origin !== parentOrigin) {\n return;\n }\n\n var iframeMessage;\n try {\n iframeMessage = JSON.parse(e.data);\n } catch (ignored) {\n debug('bad json', e.data);\n return;\n }\n\n if (iframeMessage.windowId !== iframeUtils.currentWindowId) {\n return;\n }\n switch (iframeMessage.type) {\n case 's':\n var p;\n try {\n p = JSON.parse(iframeMessage.data);\n } catch (ignored) {\n debug('bad json', iframeMessage.data);\n break;\n }\n var version = p[0];\n var transport = p[1];\n var transUrl = p[2];\n var baseUrl = p[3];\n debug(version, transport, transUrl, baseUrl);\n // change this to semver logic\n if (version !== SockJS.version) {\n throw new Error('Incompatible SockJS! Main site uses:' +\n ' \"' + version + '\", the iframe:' +\n ' \"' + SockJS.version + '\".');\n }\n\n if (!urlUtils.isOriginEqual(transUrl, loc.href) ||\n !urlUtils.isOriginEqual(baseUrl, loc.href)) {\n throw new Error('Can\\'t connect to different domain from within an ' +\n 'iframe. (' + loc.href + ', ' + transUrl + ', ' + baseUrl + ')');\n }\n facade = new FacadeJS(new transportMap[transport](transUrl, baseUrl));\n break;\n case 'm':\n facade._send(iframeMessage.data);\n break;\n case 'c':\n if (facade) {\n facade._close();\n }\n facade = null;\n break;\n }\n };\n\n eventUtils.attachEvent('message', onMessage);\n\n // Start\n iframeUtils.postMessage('s');\n };\n};\n","'use strict';\n\nvar iframeUtils = require('./utils/iframe')\n ;\n\nfunction FacadeJS(transport) {\n this._transport = transport;\n transport.on('message', this._transportMessage.bind(this));\n transport.on('close', this._transportClose.bind(this));\n}\n\nFacadeJS.prototype._transportClose = function(code, reason) {\n iframeUtils.postMessage('c', JSON.stringify([code, reason]));\n};\nFacadeJS.prototype._transportMessage = function(frame) {\n iframeUtils.postMessage('t', frame);\n};\nFacadeJS.prototype._send = function(data) {\n this._transport.send(data);\n};\nFacadeJS.prototype._close = function() {\n this._transport.close();\n this._transport.removeAllListeners();\n};\n\nmodule.exports = FacadeJS;\n","'use strict';\n\nrequire('./shims');\n\nvar URL = require('url-parse')\n , inherits = require('inherits')\n , random = require('./utils/random')\n , escape = require('./utils/escape')\n , urlUtils = require('./utils/url')\n , eventUtils = require('./utils/event')\n , transport = require('./utils/transport')\n , objectUtils = require('./utils/object')\n , browser = require('./utils/browser')\n , log = require('./utils/log')\n , Event = require('./event/event')\n , EventTarget = require('./event/eventtarget')\n , loc = require('./location')\n , CloseEvent = require('./event/close')\n , TransportMessageEvent = require('./event/trans-message')\n , InfoReceiver = require('./info-receiver')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:main');\n}\n\nvar transports;\n\n// follow constructor steps defined at http://dev.w3.org/html5/websockets/#the-websocket-interface\nfunction SockJS(url, protocols, options) {\n if (!(this instanceof SockJS)) {\n return new SockJS(url, protocols, options);\n }\n if (arguments.length < 1) {\n throw new TypeError(\"Failed to construct 'SockJS: 1 argument required, but only 0 present\");\n }\n EventTarget.call(this);\n\n this.readyState = SockJS.CONNECTING;\n this.extensions = '';\n this.protocol = '';\n\n // non-standard extension\n options = options || {};\n if (options.protocols_whitelist) {\n log.warn(\"'protocols_whitelist' is DEPRECATED. Use 'transports' instead.\");\n }\n this._transportsWhitelist = options.transports;\n this._transportOptions = options.transportOptions || {};\n this._timeout = options.timeout || 0;\n\n var sessionId = options.sessionId || 8;\n if (typeof sessionId === 'function') {\n this._generateSessionId = sessionId;\n } else if (typeof sessionId === 'number') {\n this._generateSessionId = function() {\n return random.string(sessionId);\n };\n } else {\n throw new TypeError('If sessionId is used in the options, it needs to be a number or a function.');\n }\n\n this._server = options.server || random.numberString(1000);\n\n // Step 1 of WS spec - parse and validate the url. Issue #8\n var parsedUrl = new URL(url);\n if (!parsedUrl.host || !parsedUrl.protocol) {\n throw new SyntaxError(\"The URL '\" + url + \"' is invalid\");\n } else if (parsedUrl.hash) {\n throw new SyntaxError('The URL must not contain a fragment');\n } else if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {\n throw new SyntaxError(\"The URL's scheme must be either 'http:' or 'https:'. '\" + parsedUrl.protocol + \"' is not allowed.\");\n }\n\n var secure = parsedUrl.protocol === 'https:';\n // Step 2 - don't allow secure origin with an insecure protocol\n if (loc.protocol === 'https:' && !secure) {\n // exception is 127.0.0.0/8 and ::1 urls\n if (!urlUtils.isLoopbackAddr(parsedUrl.hostname)) {\n throw new Error('SecurityError: An insecure SockJS connection may not be initiated from a page loaded over HTTPS');\n }\n }\n\n // Step 3 - check port access - no need here\n // Step 4 - parse protocols argument\n if (!protocols) {\n protocols = [];\n } else if (!Array.isArray(protocols)) {\n protocols = [protocols];\n }\n\n // Step 5 - check protocols argument\n var sortedProtocols = protocols.sort();\n sortedProtocols.forEach(function(proto, i) {\n if (!proto) {\n throw new SyntaxError(\"The protocols entry '\" + proto + \"' is invalid.\");\n }\n if (i < (sortedProtocols.length - 1) && proto === sortedProtocols[i + 1]) {\n throw new SyntaxError(\"The protocols entry '\" + proto + \"' is duplicated.\");\n }\n });\n\n // Step 6 - convert origin\n var o = urlUtils.getOrigin(loc.href);\n this._origin = o ? o.toLowerCase() : null;\n\n // remove the trailing slash\n parsedUrl.set('pathname', parsedUrl.pathname.replace(/\\/+$/, ''));\n\n // store the sanitized url\n this.url = parsedUrl.href;\n debug('using url', this.url);\n\n // Step 7 - start connection in background\n // obtain server info\n // http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-26\n this._urlInfo = {\n nullOrigin: !browser.hasDomain()\n , sameOrigin: urlUtils.isOriginEqual(this.url, loc.href)\n , sameScheme: urlUtils.isSchemeEqual(this.url, loc.href)\n };\n\n this._ir = new InfoReceiver(this.url, this._urlInfo);\n this._ir.once('finish', this._receiveInfo.bind(this));\n}\n\ninherits(SockJS, EventTarget);\n\nfunction userSetCode(code) {\n return code === 1000 || (code >= 3000 && code <= 4999);\n}\n\nSockJS.prototype.close = function(code, reason) {\n // Step 1\n if (code && !userSetCode(code)) {\n throw new Error('InvalidAccessError: Invalid code');\n }\n // Step 2.4 states the max is 123 bytes, but we are just checking length\n if (reason && reason.length > 123) {\n throw new SyntaxError('reason argument has an invalid length');\n }\n\n // Step 3.1\n if (this.readyState === SockJS.CLOSING || this.readyState === SockJS.CLOSED) {\n return;\n }\n\n // TODO look at docs to determine how to set this\n var wasClean = true;\n this._close(code || 1000, reason || 'Normal closure', wasClean);\n};\n\nSockJS.prototype.send = function(data) {\n // #13 - convert anything non-string to string\n // TODO this currently turns objects into [object Object]\n if (typeof data !== 'string') {\n data = '' + data;\n }\n if (this.readyState === SockJS.CONNECTING) {\n throw new Error('InvalidStateError: The connection has not been established yet');\n }\n if (this.readyState !== SockJS.OPEN) {\n return;\n }\n this._transport.send(escape.quote(data));\n};\n\nSockJS.version = require('./version');\n\nSockJS.CONNECTING = 0;\nSockJS.OPEN = 1;\nSockJS.CLOSING = 2;\nSockJS.CLOSED = 3;\n\nSockJS.prototype._receiveInfo = function(info, rtt) {\n debug('_receiveInfo', rtt);\n this._ir = null;\n if (!info) {\n this._close(1002, 'Cannot connect to server');\n return;\n }\n\n // establish a round-trip timeout (RTO) based on the\n // round-trip time (RTT)\n this._rto = this.countRTO(rtt);\n // allow server to override url used for the actual transport\n this._transUrl = info.base_url ? info.base_url : this.url;\n info = objectUtils.extend(info, this._urlInfo);\n debug('info', info);\n // determine list of desired and supported transports\n var enabledTransports = transports.filterToEnabled(this._transportsWhitelist, info);\n this._transports = enabledTransports.main;\n debug(this._transports.length + ' enabled transports');\n\n this._connect();\n};\n\nSockJS.prototype._connect = function() {\n for (var Transport = this._transports.shift(); Transport; Transport = this._transports.shift()) {\n debug('attempt', Transport.transportName);\n if (Transport.needBody) {\n if (!global.document.body ||\n (typeof global.document.readyState !== 'undefined' &&\n global.document.readyState !== 'complete' &&\n global.document.readyState !== 'interactive')) {\n debug('waiting for body');\n this._transports.unshift(Transport);\n eventUtils.attachEvent('load', this._connect.bind(this));\n return;\n }\n }\n\n // calculate timeout based on RTO and round trips. Default to 5s\n var timeoutMs = Math.max(this._timeout, (this._rto * Transport.roundTrips) || 5000);\n this._transportTimeoutId = setTimeout(this._transportTimeout.bind(this), timeoutMs);\n debug('using timeout', timeoutMs);\n\n var transportUrl = urlUtils.addPath(this._transUrl, '/' + this._server + '/' + this._generateSessionId());\n var options = this._transportOptions[Transport.transportName];\n debug('transport url', transportUrl);\n var transportObj = new Transport(transportUrl, this._transUrl, options);\n transportObj.on('message', this._transportMessage.bind(this));\n transportObj.once('close', this._transportClose.bind(this));\n transportObj.transportName = Transport.transportName;\n this._transport = transportObj;\n\n return;\n }\n this._close(2000, 'All transports failed', false);\n};\n\nSockJS.prototype._transportTimeout = function() {\n debug('_transportTimeout');\n if (this.readyState === SockJS.CONNECTING) {\n if (this._transport) {\n this._transport.close();\n }\n\n this._transportClose(2007, 'Transport timed out');\n }\n};\n\nSockJS.prototype._transportMessage = function(msg) {\n debug('_transportMessage', msg);\n var self = this\n , type = msg.slice(0, 1)\n , content = msg.slice(1)\n , payload\n ;\n\n // first check for messages that don't need a payload\n switch (type) {\n case 'o':\n this._open();\n return;\n case 'h':\n this.dispatchEvent(new Event('heartbeat'));\n debug('heartbeat', this.transport);\n return;\n }\n\n if (content) {\n try {\n payload = JSON.parse(content);\n } catch (e) {\n debug('bad json', content);\n }\n }\n\n if (typeof payload === 'undefined') {\n debug('empty payload', content);\n return;\n }\n\n switch (type) {\n case 'a':\n if (Array.isArray(payload)) {\n payload.forEach(function(p) {\n debug('message', self.transport, p);\n self.dispatchEvent(new TransportMessageEvent(p));\n });\n }\n break;\n case 'm':\n debug('message', this.transport, payload);\n this.dispatchEvent(new TransportMessageEvent(payload));\n break;\n case 'c':\n if (Array.isArray(payload) && payload.length === 2) {\n this._close(payload[0], payload[1], true);\n }\n break;\n }\n};\n\nSockJS.prototype._transportClose = function(code, reason) {\n debug('_transportClose', this.transport, code, reason);\n if (this._transport) {\n this._transport.removeAllListeners();\n this._transport = null;\n this.transport = null;\n }\n\n if (!userSetCode(code) && code !== 2000 && this.readyState === SockJS.CONNECTING) {\n this._connect();\n return;\n }\n\n this._close(code, reason);\n};\n\nSockJS.prototype._open = function() {\n debug('_open', this._transport && this._transport.transportName, this.readyState);\n if (this.readyState === SockJS.CONNECTING) {\n if (this._transportTimeoutId) {\n clearTimeout(this._transportTimeoutId);\n this._transportTimeoutId = null;\n }\n this.readyState = SockJS.OPEN;\n this.transport = this._transport.transportName;\n this.dispatchEvent(new Event('open'));\n debug('connected', this.transport);\n } else {\n // The server might have been restarted, and lost track of our\n // connection.\n this._close(1006, 'Server lost session');\n }\n};\n\nSockJS.prototype._close = function(code, reason, wasClean) {\n debug('_close', this.transport, code, reason, wasClean, this.readyState);\n var forceFail = false;\n\n if (this._ir) {\n forceFail = true;\n this._ir.close();\n this._ir = null;\n }\n if (this._transport) {\n this._transport.close();\n this._transport = null;\n this.transport = null;\n }\n\n if (this.readyState === SockJS.CLOSED) {\n throw new Error('InvalidStateError: SockJS has already been closed');\n }\n\n this.readyState = SockJS.CLOSING;\n setTimeout(function() {\n this.readyState = SockJS.CLOSED;\n\n if (forceFail) {\n this.dispatchEvent(new Event('error'));\n }\n\n var e = new CloseEvent('close');\n e.wasClean = wasClean || false;\n e.code = code || 1000;\n e.reason = reason;\n\n this.dispatchEvent(e);\n this.onmessage = this.onclose = this.onerror = null;\n debug('disconnected');\n }.bind(this), 0);\n};\n\n// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/\n// and RFC 2988.\nSockJS.prototype.countRTO = function(rtt) {\n // In a local environment, when using IE8/9 and the `jsonp-polling`\n // transport the time needed to establish a connection (the time that pass\n // from the opening of the transport to the call of `_dispatchOpen`) is\n // around 200msec (the lower bound used in the article above) and this\n // causes spurious timeouts. For this reason we calculate a value slightly\n // larger than that used in the article.\n if (rtt > 100) {\n return 4 * rtt; // rto > 400msec\n }\n return 300 + rtt; // 300msec < rto <= 400msec\n};\n\nmodule.exports = function(availableTransports) {\n transports = transport(availableTransports);\n require('./iframe-bootstrap')(SockJS, availableTransports);\n return SockJS;\n};\n","'use strict';\n\nvar inherits = require('inherits')\n , Event = require('./event')\n ;\n\nfunction CloseEvent() {\n Event.call(this);\n this.initEvent('close', false, false);\n this.wasClean = false;\n this.code = 0;\n this.reason = '';\n}\n\ninherits(CloseEvent, Event);\n\nmodule.exports = CloseEvent;\n","'use strict';\n\nvar inherits = require('inherits')\n , Event = require('./event')\n ;\n\nfunction TransportMessageEvent(data) {\n Event.call(this);\n this.initEvent('message', false, false);\n this.data = data;\n}\n\ninherits(TransportMessageEvent, Event);\n\nmodule.exports = TransportMessageEvent;\n","'use strict';\n\nvar transportList = require('./transport-list');\n\nmodule.exports = require('./main')(transportList);\n\n// TODO can't get rid of this until all servers do\nif ('_sockjs_onload' in global) {\n setTimeout(global._sockjs_onload, 1);\n}\n","/**\r\n * ConnectionManager\r\n * WebSocket/STOMP 연결 관리 (Chat, WebRTC 공유)\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport { ConnectionState, ErrorTypes, WebSocketPaths, DefaultConfig, LogLevel } from '../constants.js';\r\nimport { Client as StompClient } from '@stomp/stompjs';\r\nimport SockJS from 'sockjs-client';\r\n\r\n// STOMP Client 가져오기 (CDN 사용 시 전역 객체 우선)\r\nconst getStompClient = () => {\r\n // noinspection JSUnresolvedReference\r\n if (typeof window !== 'undefined' && window.StompJs && window.StompJs.Client) {\r\n return window.StompJs.Client;\r\n }\r\n return StompClient;\r\n};\r\n\r\n// SockJS 가져오기 (CDN 사용 시 전역 객체 우선)\r\nconst getSockJS = () => {\r\n // noinspection JSUnresolvedReference\r\n if (typeof window !== 'undefined' && window.SockJS) {\r\n return window.SockJS;\r\n }\r\n return SockJS;\r\n};\r\n\r\nclass ConnectionManager extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {string} options.serverUrl - 서버 URL\r\n * @param {string} options.jwtToken - JWT 토큰\r\n * @param {string} options.apiKey - API 키\r\n * @param {string} options.projectId - 프로젝트 ID\r\n * @param {boolean} [options.useSockJS=true] - SockJS 사용 여부\r\n * @param {number} [options.reconnectDelay] - 재연결 지연 시간\r\n * @param {number} [options.maxReconnectAttempts] - 최대 재연결 시도 횟수\r\n * @param {number} [options.heartbeatIncoming] - 수신 하트비트 간격\r\n * @param {number} [options.heartbeatOutgoing] - 발신 하트비트 간격\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n super();\r\n\r\n this.serverUrl = options.serverUrl.replace(/\\/$/, '');\r\n this.jwtToken = options.jwtToken;\r\n this.apiKey = options.apiKey;\r\n this.projectId = options.projectId;\r\n this.useSockJS = options.useSockJS !== false;\r\n\r\n this.reconnectDelay = options.reconnectDelay || DefaultConfig.reconnectDelay;\r\n this.maxReconnectAttempts = options.maxReconnectAttempts || DefaultConfig.maxReconnectAttempts;\r\n this.heartbeatIncoming = options.heartbeatIncoming || DefaultConfig.heartbeatIncoming;\r\n this.heartbeatOutgoing = options.heartbeatOutgoing || DefaultConfig.heartbeatOutgoing;\r\n\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'ConnectionManager');\r\n\r\n // 상태\r\n this.state = ConnectionState.DISCONNECTED;\r\n this.stompClient = null;\r\n this.reconnectAttempts = 0;\r\n this.subscriptions = new Map();\r\n this.pendingSubscriptions = [];\r\n this.userId = null;\r\n }\r\n\r\n /**\r\n * 연결 상태 변경\r\n * @private\r\n */\r\n _setState(state) {\r\n const prevState = this.state;\r\n this.state = state;\r\n this.emit('stateChange', { state, prevState });\r\n this.logger.info(`State changed: ${prevState} -> ${state}`);\r\n }\r\n\r\n /**\r\n * WebSocket URL 생성.\r\n *\r\n * <p>JWT 는 STOMP connectHeaders 의 Authorization 헤더로만 전달합니다.\r\n * URL query parameter 에 토큰을 넣지 않습니다 — 서버 로그, 프록시 로그,\r\n * 브라우저 히스토리에 JWT 가 평문 노출되는 것을 방지.</p>\r\n *\r\n * @private\r\n */\r\n _getWebSocketUrl() {\r\n const endpoint = this.useSockJS\r\n ? WebSocketPaths.SOCKJS_ENDPOINT\r\n : WebSocketPaths.NATIVE_ENDPOINT;\r\n\r\n return `${this.serverUrl}${endpoint}`;\r\n }\r\n\r\n /**\r\n * STOMP 클라이언트 생성\r\n * @private\r\n */\r\n _createStompClient() {\r\n const wsUrl = this._getWebSocketUrl();\r\n\r\n const config = {\r\n connectHeaders: {\r\n 'Authorization': this.jwtToken.startsWith('Bearer ')\r\n ? this.jwtToken\r\n : `Bearer ${this.jwtToken}`,\r\n 'X-API-KEY': this.apiKey,\r\n 'X-PROJECT-ID': this.projectId\r\n },\r\n heartbeatIncoming: this.heartbeatIncoming,\r\n heartbeatOutgoing: this.heartbeatOutgoing,\r\n reconnectDelay: this.reconnectDelay,\r\n debug: (str) => {\r\n if (this.logger.level <= LogLevel.DEBUG) {\r\n this.logger.debug('STOMP:', str);\r\n }\r\n },\r\n onConnect: (frame) => this._onConnect(frame),\r\n onDisconnect: (frame) => this._onDisconnect(frame),\r\n onStompError: (frame) => this._onStompError(frame),\r\n onWebSocketClose: (event) => this._onWebSocketClose(event),\r\n onWebSocketError: (event) => this._onWebSocketError(event)\r\n };\r\n\r\n if (this.useSockJS) {\r\n const SockJS = getSockJS();\r\n config.webSocketFactory = () => new SockJS(wsUrl);\r\n } else {\r\n // Native WebSocket — 토큰은 connectHeaders 로만 전달 (URL 노출 방지)\r\n const wsProtocol = this.serverUrl.startsWith('https') ? 'wss' : 'ws';\r\n const wsHost = this.serverUrl.replace(/^https?:\\/\\//, '');\r\n config.brokerURL = `${wsProtocol}://${wsHost}${WebSocketPaths.NATIVE_ENDPOINT}`;\r\n }\r\n\r\n const Client = getStompClient();\r\n return new Client(config);\r\n }\r\n\r\n /**\r\n * 연결\r\n * @returns {Promise<void>}\r\n */\r\n async connect() {\r\n if (this.state === ConnectionState.CONNECTED) {\r\n this.logger.warn('Already connected');\r\n return;\r\n }\r\n\r\n if (this.state === ConnectionState.CONNECTING) {\r\n this.logger.warn('Connection in progress');\r\n return;\r\n }\r\n\r\n this._setState(ConnectionState.CONNECTING);\r\n this.reconnectAttempts = 0;\r\n\r\n return new Promise((resolve, reject) => {\r\n this._connectResolve = resolve;\r\n this._connectReject = reject;\r\n\r\n try {\r\n this.stompClient = this._createStompClient();\r\n this.stompClient.activate();\r\n } catch (error) {\r\n this._setState(ConnectionState.ERROR);\r\n this._drainPendingSubscriptions('Connection activation failed');\r\n reject(error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * 연결 성공 콜백\r\n * @private\r\n */\r\n _onConnect(frame) {\r\n this._setState(ConnectionState.CONNECTED);\r\n this.reconnectAttempts = 0;\r\n this.logger.info('Connected to server', frame);\r\n\r\n // 기존 구독 재등록 (재연결 시)\r\n this._restoreSubscriptions();\r\n\r\n // 대기 중인 구독 처리\r\n this._processPendingSubscriptions();\r\n\r\n this.emit('connected', { frame });\r\n\r\n if (this._connectResolve) {\r\n this._connectResolve();\r\n this._connectResolve = null;\r\n this._connectReject = null;\r\n }\r\n }\r\n\r\n /**\r\n * 연결 해제 콜백\r\n * @private\r\n */\r\n _onDisconnect(frame) {\r\n this.logger.info('Disconnected from server', frame);\r\n this._setState(ConnectionState.DISCONNECTED);\r\n this.emit('disconnected', { frame });\r\n }\r\n\r\n /**\r\n * STOMP 에러 콜백\r\n * @private\r\n */\r\n _onStompError(frame) {\r\n this.logger.error('STOMP error:', frame);\r\n this._setState(ConnectionState.ERROR);\r\n\r\n const error = {\r\n type: ErrorTypes.CONNECTION_FAILED,\r\n message: frame.headers?.message || 'STOMP error',\r\n frame\r\n };\r\n\r\n // terminal 전이 — 대기 중 구독 Promise 가 영구 hang 되지 않도록 drain.\r\n this._drainPendingSubscriptions(`STOMP error: ${error.message}`);\r\n\r\n this.emit('error', error);\r\n\r\n if (this._connectReject) {\r\n this._connectReject(new Error(error.message));\r\n this._connectResolve = null;\r\n this._connectReject = null;\r\n }\r\n }\r\n\r\n /**\r\n * WebSocket 종료 콜백\r\n * @private\r\n */\r\n _onWebSocketClose(event) {\r\n this.logger.warn('WebSocket closed:', event);\r\n\r\n if (this.state !== ConnectionState.DISCONNECTED) {\r\n this._handleReconnect();\r\n }\r\n }\r\n\r\n /**\r\n * WebSocket 에러 콜백\r\n * @private\r\n */\r\n _onWebSocketError(event) {\r\n this.logger.error('WebSocket error:', event);\r\n\r\n // 초기 연결 실패 경로 — _connectReject 가 살아있다는 건 아직 한 번도\r\n // CONNECTED 된 적이 없다는 뜻. pending 구독도 함께 drain 해서 호출자 hang 방지.\r\n // 이미 CONNECTED 후 일시 끊김은 _onWebSocketClose → _handleReconnect 로 흘러\r\n // 재연결 성공 시 _processPendingSubscriptions 가 살린다.\r\n if (this._connectReject) {\r\n this._drainPendingSubscriptions('WebSocket connection failed');\r\n this._connectReject(new Error('WebSocket connection failed'));\r\n this._connectResolve = null;\r\n this._connectReject = null;\r\n }\r\n }\r\n\r\n /**\r\n * 재연결 처리.\r\n *\r\n * <p>max 초과 시 STOMP 내부 재연결 루프(reconnectDelay) 를 차단하기 위해\r\n * stompClient 를 반드시 deactivate 하고 참조를 해제한다. state 변경만으로는\r\n * STOMP 자체가 소유한 타이머를 멈출 수 없음.</p>\r\n *\r\n * @private\r\n */\r\n _handleReconnect() {\r\n this.reconnectAttempts++;\r\n\r\n if (this.reconnectAttempts > this.maxReconnectAttempts) {\r\n if (this.stompClient) {\r\n try {\r\n this.stompClient.deactivate();\r\n } catch (error) {\r\n this.logger.warn('Error deactivating stomp client on max reconnect:', error);\r\n }\r\n this.stompClient = null;\r\n }\r\n\r\n // terminal 전이 — 재연결 성공으로 살릴 기회가 사라졌으므로 pending 도 drain.\r\n this._drainPendingSubscriptions('Max reconnection attempts exceeded');\r\n\r\n this._setState(ConnectionState.ERROR);\r\n this.emit('error', {\r\n type: ErrorTypes.CONNECTION_LOST,\r\n message: 'Max reconnection attempts exceeded'\r\n });\r\n return;\r\n }\r\n\r\n this._setState(ConnectionState.RECONNECTING);\r\n this.emit('reconnecting', { attempt: this.reconnectAttempts });\r\n this.logger.info(`Reconnecting... attempt ${this.reconnectAttempts}`);\r\n }\r\n\r\n /**\r\n * 기존 구독 재등록 (재연결 시)\r\n * @private\r\n */\r\n _restoreSubscriptions() {\r\n if (this.subscriptions.size === 0) return;\r\n\r\n this.logger.info(`Restoring ${this.subscriptions.size} subscriptions after reconnect`);\r\n\r\n const previousSubscriptions = new Map(this.subscriptions);\r\n this.subscriptions.clear();\r\n\r\n previousSubscriptions.forEach(({ callback, headers }, destination) => {\r\n this._subscribeInternal(destination, callback, headers);\r\n this.logger.debug(`Restored subscription: ${destination}`);\r\n });\r\n }\r\n\r\n /**\r\n * 대기 중인 구독 처리\r\n * @private\r\n */\r\n _processPendingSubscriptions() {\r\n while (this.pendingSubscriptions.length > 0) {\r\n const { destination, callback, headers, resolve } = this.pendingSubscriptions.shift();\r\n const subscription = this._subscribeInternal(destination, callback, headers);\r\n if (resolve) {\r\n resolve(subscription);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 내부 구독 처리\r\n * @private\r\n */\r\n _subscribeInternal(destination, callback, headers = {}) {\r\n const subscription = this.stompClient.subscribe(destination, (message) => {\r\n try {\r\n const body = JSON.parse(message.body);\r\n callback(body, message);\r\n } catch (error) {\r\n this.logger.error('Failed to parse message:', error);\r\n callback(message.body, message);\r\n }\r\n }, headers);\r\n\r\n // 재연결 시 복구를 위해 callback, headers도 함께 저장\r\n this.subscriptions.set(destination, { subscription, callback, headers });\r\n this.logger.debug(`Subscribed to: ${destination}`);\r\n\r\n return subscription;\r\n }\r\n\r\n /**\r\n * 채널 구독\r\n * @param {string} destination - 구독 경로\r\n * @param {Function} callback - 메시지 콜백\r\n * @param {Object} [headers] - 추가 헤더\r\n * @returns {Promise<Object>} - 구독 객체\r\n */\r\n subscribe(destination, callback, headers = {}) {\r\n // 이미 구독 중이면 기존 구독 해제 후 재구독\r\n if (this.subscriptions.has(destination)) {\r\n this.unsubscribe(destination);\r\n }\r\n\r\n if (this.state !== ConnectionState.CONNECTED) {\r\n return new Promise((resolve, reject) => {\r\n this.pendingSubscriptions.push({ destination, callback, headers, resolve, reject });\r\n this.logger.debug(`Queued subscription for: ${destination}`);\r\n });\r\n }\r\n\r\n return Promise.resolve(this._subscribeInternal(destination, callback, headers));\r\n }\r\n\r\n /**\r\n * 구독 해제\r\n * @param {string} destination - 구독 경로\r\n */\r\n unsubscribe(destination) {\r\n const entry = this.subscriptions.get(destination);\r\n if (entry) {\r\n entry.subscription.unsubscribe();\r\n this.subscriptions.delete(destination);\r\n this.logger.debug(`Unsubscribed from: ${destination}`);\r\n }\r\n }\r\n\r\n /**\r\n * 모든 구독 해제\r\n */\r\n unsubscribeAll() {\r\n this.subscriptions.forEach(({ subscription }, destination) => {\r\n subscription.unsubscribe();\r\n this.logger.debug(`Unsubscribed from: ${destination}`);\r\n });\r\n this.subscriptions.clear();\r\n }\r\n\r\n /**\r\n * 메시지 전송\r\n * @param {string} destination - 전송 경로\r\n * @param {Object} body - 메시지 본문\r\n * @param {Object} [headers] - 추가 헤더\r\n */\r\n send(destination, body, headers = {}) {\r\n if (this.state !== ConnectionState.CONNECTED) {\r\n this.logger.warn('Cannot send message: not connected');\r\n return false;\r\n }\r\n\r\n this.stompClient.publish({\r\n destination,\r\n body: JSON.stringify(body),\r\n headers\r\n });\r\n\r\n this.logger.debug(`Sent to ${destination}:`, body);\r\n return true;\r\n }\r\n\r\n /**\r\n * JWT 토큰 업데이트\r\n * @param {string} token\r\n */\r\n updateToken(token) {\r\n this.jwtToken = token;\r\n\r\n if (this.stompClient) {\r\n this.stompClient.connectHeaders = {\r\n ...this.stompClient.connectHeaders,\r\n 'Authorization': token.startsWith('Bearer ') ? token : `Bearer ${token}`\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * 연결 해제.\r\n *\r\n * <p>대기 중 구독이 남아 있으면 각 Promise 를 reject 하고 큐를 비운다.\r\n * clear 만 하면 호출자 {@code await} 가 영원히 hang 된다.</p>\r\n */\r\n async disconnect() {\r\n this._drainPendingSubscriptions('Disconnected before subscription completed');\r\n\r\n if (this.stompClient) {\r\n this.unsubscribeAll();\r\n await this.stompClient.deactivate();\r\n this.stompClient = null;\r\n }\r\n\r\n this._setState(ConnectionState.DISCONNECTED);\r\n this.logger.info('Disconnected');\r\n }\r\n\r\n /**\r\n * 대기 중 구독 Promise 일괄 reject + 큐 비움.\r\n * @private\r\n */\r\n _drainPendingSubscriptions(reason) {\r\n if (this.pendingSubscriptions.length === 0) return;\r\n\r\n const pending = this.pendingSubscriptions;\r\n this.pendingSubscriptions = [];\r\n\r\n pending.forEach(({ destination, reject }) => {\r\n if (reject) {\r\n reject(new Error(`${reason}: ${destination}`));\r\n }\r\n });\r\n\r\n this.logger.debug(`Drained ${pending.length} pending subscriptions: ${reason}`);\r\n }\r\n\r\n /**\r\n * 연결 상태 확인\r\n * @returns {boolean}\r\n */\r\n isConnected() {\r\n return this.state === ConnectionState.CONNECTED;\r\n }\r\n\r\n /**\r\n * 현재 상태 반환\r\n * @returns {string}\r\n */\r\n getState() {\r\n return this.state;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리.\r\n *\r\n * <p>{@link #disconnect} 가 async 이므로 await 필수. 최상위 {@link TalkFlowClient#destroy}\r\n * 는 별도 {@code await this.disconnect()} 선행으로 이미 완충되지만,\r\n * ConnectionManager 를 직접 소유한 사용자가 destroy 만 호출하는 경로도 안전해야 함.</p>\r\n */\r\n async destroy() {\r\n await this.disconnect();\r\n this.removeAllListeners();\r\n this.logger.info('ConnectionManager destroyed');\r\n }\r\n}\r\n\r\nexport default ConnectionManager;\r\n","// noinspection JSValidateJSDoc\r\n\r\n/**\r\n * ChatClient\r\n * 채팅 기능 클라이언트\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport { WebSocketPaths, LogLevel, RoomListEventType, ChatRoomType } from '../constants.js';\r\n\r\nconst MAX_FILES_PER_MESSAGE = 20;\r\n\r\nclass ChatClient extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {Object} options.connectionManager - ConnectionManager 인스턴스\r\n * @param {Object} options.apiClient - ApiClient 인스턴스\r\n * @param {string} options.userId - 사용자 ID\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n super();\r\n\r\n this.connectionManager = options.connectionManager;\r\n this.apiClient = options.apiClient;\r\n this.userId = options.userId;\r\n\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'ChatClient');\r\n\r\n // 구독 중인 채팅방\r\n this.subscribedRooms = new Map();\r\n\r\n // 채팅방 리스트 구독 상태 (카톡 스타일 리스트 실시간 업데이트)\r\n this.roomListSubscribed = false;\r\n\r\n // 현재 보고 있는 채팅방 ID (이 방에서만 자동 읽음 처리)\r\n this._activeRoomId = null;\r\n\r\n // 타이핑 표시 debounce 타이머 (roomId → setTimeout ID)\r\n // 이 Map 은 outgoing 전용 — 사용자가 startTyping() 호출 시 N초 후 자동 stopTyping() 발신용.\r\n this._typingTimers = new Map();\r\n\r\n // 어시스턴트 응답 준비 중 typing=true 수신 후 client-side timeout (roomId → setTimeout ID).\r\n // 서버는 typing=false 를 발행하지 않으며 (멘션 N personas / 락 실패 / LLM 실패 / skip 등 종료 추적\r\n // 부담 회피), SDK 가 (1) assistant 메시지 도착 시 즉시 해제 (2) 본 timeout 만료 시 자동 해제 책임.\r\n // outgoing 타이머 (_typingTimers) 와 의미가 달라 별도 Map 유지.\r\n this._assistantTypingTimers = new Map();\r\n // 어시 typing 자동 해제 시한 (ms) — LLM 호출 최대 시간 (Sonnet 5-10초) + 부수 작업 + 여유.\r\n // 운영 환경 LLM 응답 지연 분포에 따라 조정 가능.\r\n this._assistantTypingTimeoutMs = 30000;\r\n // 어시 typing 의 generic sender 식별자 — 서버 ChatMessageSentAssistantListener 의\r\n // ASSISTANT_TYPING_USER_ID 와 정확히 일치해야 함. UI 가 roomId+userId 로 typing state 추적 시\r\n // typing=true / typing=false 가 같은 키로 매칭되도록 일관 유지 필수.\r\n this._assistantTypingUserId = '__assistant__';\r\n this._assistantTypingUserName = 'AI';\r\n\r\n // 메시지 dedup 보호 — 같은 messageId 의 중복 수신 차단.\r\n // 서버측 멱등 race 복구 / 네트워크 재전송 / WebSocket 재연결 직후 등 다양한 중복 시나리오 커버.\r\n //\r\n // chat WebSocket (MESSAGE_CREATED) 와 roomList (MESSAGE_RECEIVED) 는 같은 messageId 의 별개 이벤트라\r\n // 각각 다른 키 공간 사용. 정상 흐름에서 두 이벤트가 거의 동시에 도착하므로 키를 공유하면 둘 중\r\n // 하나가 차단되는 부작용 발생.\r\n //\r\n // 구조: roomId → insertion-order Map<messageId, true> (LRU). size 초과 시 가장 오래된 항목 제거.\r\n this._seenChatMessageIdsByRoom = new Map();\r\n this._seenRoomListMessageIdsByRoom = new Map();\r\n // 방 하나당 최근 N개의 messageId 만 dedup 대상 — 정상 흐름에서 충돌 가능성 없는 적정 크기.\r\n this._maxSeenPerRoom = 200;\r\n }\r\n\r\n /**\r\n * 방별 LRU dedup 체크 — 이미 본 messageId 면 {@code true} 반환 (중복).\r\n * 처음 보는 messageId 면 기록 후 {@code false} 반환.\r\n *\r\n * <p>Map 의 insertion-order 보존 특성을 이용한 가벼운 LRU. 외부 lib 없음.</p>\r\n *\r\n * @private\r\n * @param {Map<string, Map<string, true>>} bucket - 키 공간 (chat / roomList 별도)\r\n * @param {string} roomId\r\n * @param {string} messageId\r\n * @returns {boolean} 이미 본 메시지면 true (호출자가 무시)\r\n */\r\n _shouldDedupMessage(bucket, roomId, messageId) {\r\n if (!roomId || !messageId) return false;\r\n let seen = bucket.get(roomId);\r\n if (!seen) {\r\n seen = new Map();\r\n bucket.set(roomId, seen);\r\n }\r\n if (seen.has(messageId)) {\r\n return true;\r\n }\r\n seen.set(messageId, true);\r\n if (seen.size > this._maxSeenPerRoom) {\r\n // 가장 오래된 키 제거 (Map insertion-order 의 첫 항목).\r\n const oldestKey = seen.keys().next().value;\r\n seen.delete(oldestKey);\r\n }\r\n return false;\r\n }\r\n\r\n // ==================== REST API ====================\r\n\r\n /**\r\n * 내 채팅방 목록 조회\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기 (1-100)\r\n * @param {string} [params.lastId] - 마지막 채팅방 ID (커서)\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값 (epoch millis)\r\n * @param {string} [params.type] - 채팅방 타입 필터 ('DIRECT' | 'GROUP'). 생략 시 전체.\r\n * 'GROUP' 은 \"그룹 탭\" 시맨틱 — PRIVATE_GROUP/TEAM 을 포함한 그룹 전체를 반환\r\n * (PRIVATE_GROUP/TEAM 값을 보내도 동일하게 그룹 전체 — 타입별 정확 필터 아님).\r\n * 특정 타입만 필요하면 응답의 {@code roomType} 필드로 클라이언트에서 거른다.\r\n * @returns {Promise<Object>}\r\n */\r\n async getRooms(params = {}) {\r\n const { size = 50, lastId, lastSortValue, type } = params;\r\n return this.apiClient.get('/api/v1/rooms/my', { size, lastId, lastSortValue, type });\r\n }\r\n\r\n /**\r\n * 채팅방 입장 (상세 조회).\r\n *\r\n * <p>서버 {@code markAllAsRead} 를 트리거하므로 방 안의 누적 unread 가 전부 읽음 처리된다.\r\n * 실시간 읽음 이벤트 보정 수신이 필요하면 {@link #enterRoom} 사용을 권장 — subscribe 를\r\n * {@link #getRoom} 보다 먼저 수행해서 \"응답 후, 구독 전\" 구간의 read 이벤트 유실을 막는다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async getRoom(roomId) {\r\n return this.apiClient.get(`/api/v1/rooms/${roomId}`);\r\n }\r\n\r\n /**\r\n * 채팅방 진입 — <b>권장 진입 경로</b>.\r\n *\r\n * <p>순서를 고정해서 race-free 진입을 보장:</p>\r\n * <ol>\r\n * <li><b>subscribeRoom</b> — WebSocket 구독 먼저. 서버가 발행하는 read/message/typing 이벤트의\r\n * 도달을 이 시점부터 보장.</li>\r\n * <li><b>setActiveRoom</b> — 이후 수신되는 신규 메시지 자동 읽음 처리 활성화.</li>\r\n * <li><b>getRoom</b> — 서버 {@code markAllAsRead} + 초기 메시지 50개 fetch. 이 시점에 서버가\r\n * 발행하는 read 이벤트는 (1) 의 구독으로 모두 수신됨.</li>\r\n * </ol>\r\n *\r\n * <p>구독이 {@code getRoom} 보다 먼저 완료돼야 \"서버에서 발행된 read 이벤트가 아직 구독 안 된\r\n * 클라에 유실\" 되는 케이스가 제거된다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>} {@link #getRoom} 의 응답 (방 상세 + 초기 메시지)\r\n *\r\n * @example\r\n * // 방 클릭 시\r\n * const detail = await client.chat.enterRoom('room-id');\r\n * renderRoom(detail);\r\n */\r\n async enterRoom(roomId) {\r\n // \"이 호출이 새로 만든 구독인지\" 기억 — getRoom 실패 시 rollback 시에 이전 구독은 유지.\r\n const wasAlreadySubscribed = this.subscribedRooms.has(roomId);\r\n\r\n // 1) WebSocket 구독을 먼저 — 이후 서버 read 이벤트 유실 방지\r\n if (!wasAlreadySubscribed) {\r\n await this.subscribeRoom(roomId);\r\n }\r\n // 2) 자동 읽음 처리 활성 방 지정\r\n this.setActiveRoom(roomId);\r\n\r\n try {\r\n // 3) 서버 입장 API — markAllAsRead + 초기 메시지 조회\r\n return await this.getRoom(roomId);\r\n } catch (e) {\r\n // getRoom 실패 시 half-open 상태 방지 — 이 호출이 켠 리소스만 되돌림.\r\n if (this.getActiveRoom() === roomId) {\r\n this.clearActiveRoom();\r\n }\r\n if (!wasAlreadySubscribed) {\r\n this.unsubscribeRoom(roomId);\r\n }\r\n throw e;\r\n }\r\n }\r\n\r\n /**\r\n * 채팅방 정보 조회 (입장하지 않고 정보만)\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async getRoomInfo(roomId) {\r\n return this.apiClient.get(`/api/v1/rooms/${roomId}/info`);\r\n }\r\n\r\n /**\r\n * 내 방 언어 설정 (다국어). 이 방에서 내가 볼 언어를 지정한다.\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string|null} language - BCP-47 코드(예 'ko','en','ja') / 'OFF'(원문 보기) /\r\n * null·''(오버라이드 해제 → 사용자 기본 언어로 복귀)\r\n * @returns {Promise<Object>} 갱신된 방 상세\r\n */\r\n async setMyRoomLanguage(roomId, language) {\r\n return this.apiClient.put(`/api/v1/rooms/${roomId}/my-language`, { language });\r\n }\r\n\r\n /**\r\n * 1:1 채팅방 생성/조회\r\n * @param {string} friendId - 상대방 사용자 ID (projectUserId)\r\n * @returns {Promise<Object>}\r\n */\r\n async createOneToOneRoom(friendId) {\r\n return this.apiClient.post(`/api/v1/rooms/direct/${friendId}`);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방 생성.\r\n *\r\n * <p>방 타입 {@code GROUP} (공개) / {@code PRIVATE_GROUP} (비밀) / {@code TEAM} (팀) 지정 가능.\r\n * 비밀방인 경우 {@code password} 필수 (서버가 bcrypt 해시로 저장). 팀방은 비밀번호 금지 —\r\n * 초대 전용이라 비밀번호 개념이 없고, 멤버는 입장 시점과 무관하게 방 전체 히스토리를 본다.</p>\r\n *\r\n * <p>{@code invitedAssistantPersonaIds} 로 AI 어시스턴트 페르소나도 함께 추가 가능.\r\n * 페르소나 ID 는 {@link #getAssistants} 로 사전 조회. 서버가 해당 프로젝트에서 사용 가능한\r\n * 페르소나인지 검증 (글로벌 또는 자체/오버라이드). 활성 페르소나만 허용.</p>\r\n *\r\n * @param {Object} data\r\n * @param {string} data.roomName - 채팅방 이름 (필수, max 100자)\r\n * @param {string} [data.description] - 채팅방 설명 (max 500자)\r\n * @param {string[]} [data.invitedUserIds] - 초대할 사용자 ID 배열 (max 50명)\r\n * @param {string[]} [data.invitedAssistantPersonaIds] - 추가할 어시스턴트 ID 배열 (max 10개).\r\n * {@link #getAssistants} 로 조회한 어시스턴트의 {@code id} 를 전달.\r\n * @param {string} [data.roomType='GROUP'] - 방 타입 ('GROUP' | 'PRIVATE_GROUP' | 'TEAM'). 기본값 'GROUP'.\r\n * 'TEAM' = 초대 전용(직접 입장 불가) + 멤버 전체 히스토리 열람 + 비참여자에게 목록 비노출.\r\n * @param {string} [data.password] - 비밀번호 (roomType='PRIVATE_GROUP' 일 때 필수, 4~50자.\r\n * 'GROUP'/'TEAM' 에는 금지)\r\n * @param {number} [data.messageRetentionHours] - 메시지 보관 기간 (시간, 1~8760). 미설정 시 무제한.\r\n * 설정 시 메시지를 삭제하지 않고 조회 시 기간 필터링만 적용. 변경 시 즉시 반영.\r\n * @param {string} [data.assistantMode='GENERAL'] - 어시스턴트 참여 모드\r\n * ('GENERAL' | 'PEOPLE_ONLY' | 'CALL_ONLY'). 생략 시 서버 기본값 GENERAL.\r\n * @param {string} [data.roomAiType] - 방 AI 사용 방식 intent ('NONE' | 'PERSONA_MULTI' | 'PM_BACKSTAGE').\r\n * 생략 시 서버가 API 키 권한 + 첨부 페르소나로 파생 (레거시 호환). {@code 'NONE'} 이면\r\n * {@code invitedAssistantPersonaIds}/{@code assistantMode} 동시 지정 불가 (서버 400).\r\n * {@code 'PM_BACKSTAGE'} 는 PM 자동부착 — persona/assistantMode 대신 {@code engagementIntensity} 로 제어.\r\n * 권한 없는 값 지정 시 403.\r\n * <b>주의(함정):</b> PERSONA_MULTI 권한이 없는 PM 전용 키는 {@code roomAiType} 을 생략하면 서버 파생이\r\n * {@code 'NONE'}(사람 방)으로 떨어진다 (PM_BACKSTAGE 로 자동 승격되지 않음). {@link #getRoomAiMeta} 의\r\n * {@code availableRoomAiTypes} 로 확인한 타입을 반드시 명시 전송할 것.\r\n * @param {string} [data.engagementIntensity] - PM 개입 강도 ('QUIET' | 'NORMAL' | 'ACTIVE').\r\n * {@code 'PM_BACKSTAGE'} 방 전용 — 그 외 타입에 지정 시 서버 거절. 생략 시 서버 기본값 'NORMAL'.\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // 공개 그룹방\r\n * await chat.createGroupRoom({\r\n * roomName: '개발팀 잡담방',\r\n * invitedUserIds: ['user-1', 'user-2']\r\n * });\r\n *\r\n * @example\r\n * // 어시스턴트 포함 그룹방\r\n * const assistants = await chat.getAssistants();\r\n * // UI 에 표시하고 사용자가 선택한 id 들을 그대로 전달\r\n * await chat.createGroupRoom({\r\n * roomName: '계약 검토방',\r\n * invitedUserIds: ['user-1', 'user-2'],\r\n * invitedAssistantPersonaIds: [/* 사용자가 선택한 id 배열 *\\/],\r\n * assistantMode: 'GENERAL'\r\n * });\r\n *\r\n * @example\r\n * // 비밀 그룹방\r\n * await chat.createGroupRoom({\r\n * roomName: '비공개 회의실',\r\n * invitedUserIds: ['user-1'],\r\n * roomType: ChatRoomType.PRIVATE_GROUP,\r\n * password: 'secret1234'\r\n * });\r\n *\r\n * @example\r\n * // 팀 채팅방 — 초대 전용 + 전체 히스토리 (중간에 들어와도 이전 대화 열람)\r\n * await chat.createGroupRoom({\r\n * roomName: '백엔드팀',\r\n * invitedUserIds: ['user-1', 'user-2'],\r\n * roomType: ChatRoomType.TEAM\r\n * });\r\n */\r\n async createGroupRoom(data) {\r\n const roomType = data.roomType || ChatRoomType.GROUP;\r\n\r\n // 클라이언트 측 사전 검증 (서버에서도 재검증됨)\r\n if (roomType === ChatRoomType.PRIVATE_GROUP) {\r\n if (!data.password || data.password.length < 4) {\r\n throw new Error('PRIVATE_GROUP requires a password of at least 4 characters');\r\n }\r\n } else if (roomType === ChatRoomType.GROUP) {\r\n if (data.password) {\r\n throw new Error('Public GROUP rooms cannot have a password');\r\n }\r\n } else if (roomType === ChatRoomType.TEAM) {\r\n if (data.password) {\r\n throw new Error('TEAM rooms cannot have a password (invite-only)');\r\n }\r\n }\r\n\r\n return this.apiClient.post('/api/v1/rooms/group', {\r\n roomName: data.roomName,\r\n description: data.description,\r\n invitedUserIds: data.invitedUserIds,\r\n roomType,\r\n password: data.password,\r\n messageRetentionHours: data.messageRetentionHours,\r\n invitedAssistantPersonaIds: data.invitedAssistantPersonaIds,\r\n assistantMode: data.assistantMode,\r\n roomAiType: data.roomAiType,\r\n engagementIntensity: data.engagementIntensity\r\n });\r\n }\r\n\r\n /**\r\n * 사용 가능한 어시스턴트 리스트 조회.\r\n *\r\n * <p>받은 리스트를 UI 에 표시하고, 사용자가 선택한 어시스턴트의 {@code id} 를\r\n * {@link #createGroupRoom} 의 {@code invitedAssistantPersonaIds} 로 전달.</p>\r\n *\r\n * @returns {Promise<Array<{id: string, displayName: string, mentionKey: string, role: string}>>}\r\n *\r\n * @example\r\n * const assistants = await chat.getAssistants();\r\n * // [{ id, displayName, mentionKey, role }, ...]\r\n */\r\n async getAssistants() {\r\n const response = await this.apiClient.get('/api/v1/assistants');\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 현재 API 키로 생성 가능한 방 AI 타입 조회 (discovery).\r\n *\r\n * <p>방 생성 UI 에서 {@link #createGroupRoom} 의 {@code roomAiType} 으로 만들 수 있는 타입만 노출하는 데 사용한다.\r\n * 키 권한(capability)으로 결정 — {@code 'NONE'}(사람 채팅)은 항상, {@code 'PERSONA_MULTI'}/{@code 'PM_BACKSTAGE'}\r\n * 는 권한 보유 시 포함.</p>\r\n *\r\n * <p><b>중요:</b> 여기서 고른 타입을 {@link #createGroupRoom} 의 {@code roomAiType} 으로 <b>반드시 명시 전송</b>해야 한다.\r\n * 생략하면 서버가 키 권한 + persona 로 파생하는데, PERSONA_MULTI 권한이 없는 PM 전용 키는 {@code 'NONE'}(사람 방)으로\r\n * 떨어진다 (PM_BACKSTAGE 자동 승격 없음).</p>\r\n *\r\n * @returns {Promise<{availableRoomAiTypes: string[]}>}\r\n *\r\n * @example\r\n * const meta = await chat.getRoomAiMeta();\r\n * // meta.availableRoomAiTypes 예: ['NONE', 'PM_BACKSTAGE']\r\n * // → UI 에서 이 타입들만 방 생성 옵션으로 노출, 고른 타입을 createGroupRoom 의 roomAiType 으로 명시 전송\r\n */\r\n async getRoomAiMeta() {\r\n const response = await this.apiClient.get('/api/v1/rooms/ai-meta');\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 어시스턴트 메시지 평점 등록 (1~5점).\r\n *\r\n * <p>AI 어시스턴트가 생성한 메시지({@code senderType === 'ASSISTANT'})에 대해서만 의미가 있다.\r\n * UI 는 어시 메시지이면서 삭제되지 않은 메시지에만 평점 버튼을 노출하는 것을 권장한다\r\n * (서버도 사람 메시지 / 삭제 메시지는 거부).</p>\r\n *\r\n * <p>같은 메시지를 다시 평가하면 갱신된다 (단, 서버에서 해당 평점이 아직 검토 전일 때만).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 평가할 어시스턴트 메시지의 {@code messageId}\r\n * @param {number} rating - 평점 (1~5 정수)\r\n * @param {string} [comment] - 자유 코멘트 (max 2000자)\r\n * @returns {Promise<void>}\r\n *\r\n * @example\r\n * await chat.rateAssistantMessage(roomId, msg.messageId, 5, '정확한 요약이었어요');\r\n */\r\n async rateAssistantMessage(roomId, messageId, rating, comment) {\r\n // 클라이언트 측 사전 검증 (서버에서도 @Min(1)@Max(5) 재검증됨)\r\n if (!Number.isInteger(rating) || rating < 1 || rating > 5) {\r\n throw new Error('rating must be an integer between 1 and 5');\r\n }\r\n\r\n await this.apiClient.post(\r\n `/api/v1/chat-messages/${roomId}/messages/${messageId}/rating`,\r\n { rating, comment }\r\n );\r\n }\r\n\r\n /**\r\n * 어시스턴트로 대화 요약 (버튼 진입점, 동기).\r\n *\r\n * <p>지정한 페르소나가 최근 메시지 범위를 요약해 방에 어시스턴트 메시지로 broadcast 한다.\r\n * HTTP 응답은 LLM 처리(수 초) 후 결과를 반환하며, 생성된 메시지는 일반 {@code chatMessage}\r\n * 이벤트로도 수신된다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} options\r\n * @param {string} options.personaId - 요약을 수행할 어시스턴트 ID ({@link #getAssistants} 의 {@code id}). 필수.\r\n * @param {string} [options.format] - 요약 포맷 ({@link SummarizeFormat}). 미지정 시 서버 기본값 {@code SHORT}.\r\n * @param {number} [options.messageCount] - 요약 대상 최근 메시지 수 (1~50). 미지정 시 서버 기본값.\r\n * @returns {Promise<{outcome: string, messageId: (string|null), detail: (string|null)}>}\r\n * {@code outcome}: {@code 'EMITTED'} (생성됨, {@code messageId} 존재) |\r\n * {@code 'SKIPPED'} / {@code 'FAILED'} ({@code detail} 에 사유).\r\n *\r\n * @example\r\n * const result = await chat.summarizeWithAssistant(roomId, {\r\n * personaId,\r\n * format: SummarizeFormat.MINUTES,\r\n * messageCount: 30\r\n * });\r\n * if (result.outcome !== 'EMITTED') console.warn(result.detail);\r\n */\r\n async summarizeWithAssistant(roomId, options = {}) {\r\n const { personaId, format, messageCount } = options;\r\n if (!personaId) {\r\n throw new Error('summarizeWithAssistant requires options.personaId');\r\n }\r\n\r\n const response = await this.apiClient.post(\r\n `/api/v1/rooms/${roomId}/assistant/tools/summarize`,\r\n { personaId, format, messageCount }\r\n );\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 어시스턴트로 특정 메시지 번역 (버튼 진입점, 동기).\r\n *\r\n * <p>지정한 페르소나가 {@code sourceMessageId} 메시지를 {@code targetLang} 으로 번역해\r\n * 방에 어시스턴트 메시지로 broadcast 한다. 생성된 메시지는 일반 {@code chatMessage}\r\n * 이벤트로도 수신된다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} options\r\n * @param {string} options.personaId - 번역을 수행할 어시스턴트 ID ({@link #getAssistants} 의 {@code id}). 필수.\r\n * @param {string} options.sourceMessageId - 번역 대상 메시지의 {@code messageId}. 필수.\r\n * @param {string} [options.targetLang] - 목표 언어 (예: {@code 'en'}, {@code '日本語'}). 미지정 시 서버 정책.\r\n * @returns {Promise<{outcome: string, messageId: (string|null), detail: (string|null)}>}\r\n * {@code outcome}: {@code 'EMITTED'} (생성됨, {@code messageId} 존재) |\r\n * {@code 'SKIPPED'} / {@code 'FAILED'} ({@code detail} 에 사유).\r\n *\r\n * @example\r\n * const result = await chat.translateWithAssistant(roomId, {\r\n * personaId,\r\n * sourceMessageId: msg.messageId,\r\n * targetLang: 'en'\r\n * });\r\n */\r\n async translateWithAssistant(roomId, options = {}) {\r\n const { personaId, targetLang, sourceMessageId } = options;\r\n if (!personaId) {\r\n throw new Error('translateWithAssistant requires options.personaId');\r\n }\r\n if (!sourceMessageId) {\r\n throw new Error('translateWithAssistant requires options.sourceMessageId');\r\n }\r\n\r\n const response = await this.apiClient.post(\r\n `/api/v1/rooms/${roomId}/assistant/tools/translate`,\r\n { personaId, targetLang, sourceMessageId }\r\n );\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 조회 — PM_BACKSTAGE 방 전용.\r\n *\r\n * <p>방 레이어는 전역 PM 프롬프트(+회사 레이어)에 <b>합성</b>되는 방 단위 추가 지시문이다 (교체 아님).\r\n * 등록된 적이 없으면 404 ({@code PM_PROMPT_LAYER_NOT_FOUND}) — \"아직 없음\" 상태로 처리할 것.</p>\r\n *\r\n * <p>권한: 방 참여자 전체 조회 가능. PM_BACKSTAGE 가 아닌 방이면 400\r\n * ({@code PM_PROMPT_ROOM_TYPE_UNSUPPORTED}).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>} PmPromptLayer — {@code {id, scope:'ROOM', projectId, roomId, content, active, activeVersion, editorType, updatedAt}}\r\n *\r\n * @example\r\n * try {\r\n * const layer = await chat.getRoomPmPrompt(roomId);\r\n * console.log(layer.active, layer.content);\r\n * } catch (e) {\r\n * if (e.status === 404) {\r\n * // 레이어 미등록 — 입력 UI 노출\r\n * }\r\n * }\r\n */\r\n async getRoomPmPrompt(roomId) {\r\n const response = await this.apiClient.get(`/api/v1/rooms/${roomId}/pm-prompt`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 등록/수정 (upsert) — <b>방 주인(OWNER) 전용</b>.\r\n *\r\n * <p>저장 시 새 버전이 자동 기록된다 ({@link #getRoomPmPromptVersions}). 본문 한도 2,000자.\r\n * 비주인 호출은 403 ({@code NOT_ROOM_ADMIN}).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} content - 레이어 본문 (1~2,000자). 방의 목적/맥락/금지사항 등 PM 에게 줄 추가 지시문.\r\n * @returns {Promise<Object>} 저장된 PmPromptLayer\r\n *\r\n * @example\r\n * await chat.upsertRoomPmPrompt(roomId, '이 방은 모바일앱 리뉴얼 TF. 결정사항은 표로 정리해줘.');\r\n */\r\n async upsertRoomPmPrompt(roomId, content) {\r\n if (typeof content !== 'string' || !content.trim()) {\r\n throw new Error('upsertRoomPmPrompt requires non-blank content');\r\n }\r\n if (content.length > 2000) {\r\n throw new Error('upsertRoomPmPrompt content exceeds 2000 characters');\r\n }\r\n\r\n const response = await this.apiClient.put(`/api/v1/rooms/${roomId}/pm-prompt`, { content });\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 합성 복귀 (활성화) — 방 주인(OWNER) 전용.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>} 변경된 PmPromptLayer ({@code active: true})\r\n */\r\n async activateRoomPmPrompt(roomId) {\r\n const response = await this.apiClient.patch(`/api/v1/rooms/${roomId}/pm-prompt/activate`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 합성 제외 (비활성화, soft delete) — 방 주인(OWNER) 전용.\r\n *\r\n * <p>본문/이력은 보존되고 PM 응답 합성에서만 빠진다. {@link #activateRoomPmPrompt} 로 복귀.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>} 변경된 PmPromptLayer ({@code active: false})\r\n */\r\n async deactivateRoomPmPrompt(roomId) {\r\n const response = await this.apiClient.patch(`/api/v1/rooms/${roomId}/pm-prompt/deactivate`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 버전 이력 조회 — 방 참여자 전체.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Array<Object>>} 버전 목록 — 각 항목 {@code {version, content, active, editorType, createdAt}} ({@code active}=현재 활성 버전)\r\n */\r\n async getRoomPmPromptVersions(roomId) {\r\n const response = await this.apiClient.get(`/api/v1/rooms/${roomId}/pm-prompt/versions`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 버전 활성화 — 방 주인(OWNER) 전용.\r\n *\r\n * <p>active 포인터 이동 방식 — 지정 버전을 활성으로 전환하고 라이브 본문을 그 버전으로 투영한다.\r\n * <b>새 버전을 만들지 않는다</b>(버전 수 불변, 글로벌/회사/방 PM 통일 모델). 존재하지 않는\r\n * 버전이면 404 ({@code PM_PROMPT_LAYER_VERSION_NOT_FOUND}).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {number} version - 활성화할 버전 번호 ({@link #getRoomPmPromptVersions} 의 {@code version})\r\n * @returns {Promise<Object>} 활성 버전이 반영된 PmPromptLayer\r\n */\r\n async activateRoomPmPromptVersion(roomId, version) {\r\n if (!Number.isInteger(version) || version < 1) {\r\n throw new Error('activateRoomPmPromptVersion requires a positive integer version');\r\n }\r\n\r\n const response = await this.apiClient.post(\r\n `/api/v1/rooms/${roomId}/pm-prompt/versions/${version}/activate`\r\n );\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * PM 프롬프트 합성 미리보기 — 방 참여자 전체.\r\n *\r\n * <p>전역(+회사)(+방) 레이어가 합성된 최종 SYSTEM 본문을 반환한다. 컨텍스트 정책 등\r\n * 런타임 부착분은 미포함 — 레이어 합성 결과까지만.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<{composedPrompt: string}>}\r\n *\r\n * @example\r\n * const { composedPrompt } = await chat.previewRoomPmPrompt(roomId);\r\n */\r\n async previewRoomPmPrompt(roomId) {\r\n const response = await this.apiClient.get(`/api/v1/rooms/${roomId}/pm-prompt/preview`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방 입장.\r\n *\r\n * <p>비밀방 ({@code PRIVATE_GROUP}) 은 최초 입장 시 또는 나갔다 재입장 시 비밀번호 필요.\r\n * 이미 active 참가자라면 비밀번호 없이 재접근 가능 (서버의 fast path).</p>\r\n *\r\n * <p>팀방 ({@code TEAM}) 은 초대 전용 — 직접 입장 시도는 {@code TEAM_ROOM_INVITE_ONLY}(403).\r\n * {@link #inviteToGroupRoom} 으로 초대받아야 입장된다 (이미 멤버면 fast path 멱등 통과).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} [password] - 비밀방 입장 비밀번호 (비밀방 최초/재입장 시 필수)\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // 공개방 입장\r\n * await chat.joinGroupRoom('room-id');\r\n *\r\n * @example\r\n * // 비밀방 입장\r\n * await chat.joinGroupRoom('room-id', 'secret1234');\r\n */\r\n async joinGroupRoom(roomId, password) {\r\n const body = password ? { password } : undefined;\r\n return this.apiClient.post(`/api/v1/rooms/group/${roomId}/join`, body);\r\n }\r\n\r\n /**\r\n * 채팅방 나가기 (구독도 자동 해제됨)\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async leaveRoom(roomId) {\r\n const result = await this.apiClient.post(`/api/v1/rooms/${roomId}/leave`);\r\n this.unsubscribeRoom(roomId);\r\n return result;\r\n }\r\n\r\n /**\r\n * 그룹 채팅방 설정 수정 (방장만).\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} data - 수정할 필드 (null/undefined 이면 변경 안 함)\r\n * @param {string} [data.roomName] - 방 이름\r\n * @param {string} [data.description] - 설명\r\n * @param {string} [data.roomType] - 공개/비공개 전환 ('GROUP' | 'PRIVATE_GROUP').\r\n * 'PRIVATE_GROUP' 전환 시 password 필수, 'GROUP' 전환 시 기존 비번 제거. 생략 시 변경 안 함.\r\n * @param {string} [data.password] - 비밀번호 (PRIVATE_GROUP). 공백만으로는 설정 불가, 4~50자.\r\n * @param {number} [data.messageRetentionHours] - 보관 기간 (시간)\r\n * @param {boolean} [data.anyoneCanInvite] - 누구나 초대 가능 여부\r\n * @param {string} [data.assistantMode] - 어시스턴트 참여 모드\r\n * ('GENERAL' | 'PEOPLE_ONLY' | 'CALL_ONLY'). PERSONA_MULTI 방 전용.\r\n * @param {string} [data.engagementIntensity] - PM 개입 강도 ('QUIET' | 'NORMAL' | 'ACTIVE').\r\n * PM_BACKSTAGE 방 전용 — PM 참여 레벨 변경. 키 PM_BACKSTAGE 권한 필요(없으면 403).\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // PERSONA_MULTI 방 — 어시스턴트 모드 변경\r\n * await chat.updateGroupRoom('room-id', { assistantMode: 'CALL_ONLY' });\r\n *\r\n * @example\r\n * // PM_BACKSTAGE 방 — PM 개입 강도 변경\r\n * await chat.updateGroupRoom('room-id', { engagementIntensity: 'QUIET' });\r\n *\r\n * @example\r\n * // 공개 → 비공개 전환 (비밀번호 필수)\r\n * await chat.updateGroupRoom('room-id', { roomType: 'PRIVATE_GROUP', password: 'secret12' });\r\n *\r\n * @example\r\n * // 비공개 → 공개 전환 (기존 비밀번호 제거됨)\r\n * await chat.updateGroupRoom('room-id', { roomType: 'GROUP' });\r\n */\r\n async updateGroupRoom(roomId, data) {\r\n return this.apiClient.put(`/api/v1/rooms/group/${roomId}`, data);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방에 회원 / 어시스턴트 추가 초대.\r\n *\r\n * <p>두 가지 호출 형식 지원 (backward-compat):</p>\r\n * <ul>\r\n * <li>{@code inviteToGroupRoom(roomId, ['user-1', 'user-2'])} — 회원만 초대 (기존)</li>\r\n * <li>{@code inviteToGroupRoom(roomId, { userIds, assistantPersonaIds })} — 회원 + 어시 (신규)</li>\r\n * </ul>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string[]|{userIds?: string[], assistantPersonaIds?: string[]}} userIdsOrData\r\n * 배열이면 회원 ID 목록, 객체면 {@code userIds} / {@code assistantPersonaIds}.\r\n * 둘 중 하나 이상 필수.\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // 회원만\r\n * await chat.inviteToGroupRoom(roomId, ['user-1', 'user-2']);\r\n *\r\n * @example\r\n * // 회원 + 어시스턴트\r\n * await chat.inviteToGroupRoom(roomId, {\r\n * userIds: ['user-3'],\r\n * assistantPersonaIds: [/* 어시스턴트 id 배열 *\\/]\r\n * });\r\n */\r\n async inviteToGroupRoom(roomId, userIdsOrData) {\r\n const body = Array.isArray(userIdsOrData)\r\n ? { userIds: userIdsOrData }\r\n : {\r\n userIds: userIdsOrData?.userIds,\r\n assistantPersonaIds: userIdsOrData?.assistantPersonaIds\r\n };\r\n return this.apiClient.post(`/api/v1/rooms/group/${roomId}/invite`, body);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방에서 참가자 단순 추방 — 방 관리자(OWNER/ADMIN) 만 호출 가능.\r\n *\r\n * <p>대상은 방에서 제거되지만 재입장 / 재초대 가능. 영구 차단이 필요하면\r\n * {@link #banMember} 사용.</p>\r\n *\r\n * <p>서버 응답:</p>\r\n * <ul>\r\n * <li>400 {@code CANNOT_KICK_SELF} — 자기 자신 추방 시도</li>\r\n * <li>403 {@code NOT_ROOM_ADMIN} — 방장 아님</li>\r\n * <li>404 {@code USER_NOT_IN_ROOM} — 대상이 현재 참가자 아님</li>\r\n * </ul>\r\n *\r\n * @param {string} roomId\r\n * @param {string} userId - 추방할 사용자 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async kickMember(roomId, userId) {\r\n return this.apiClient.delete(`/api/v1/rooms/${roomId}/members/${userId}`);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방에서 참가자 추방 + 영구 차단 — 방 관리자(OWNER/ADMIN) 만 호출 가능.\r\n *\r\n * <p>{@link #kickMember} 와 동일하게 방에서 제거하되 {@code bannedUserIds} 에 추가되어\r\n * {@link #unbanMember} 호출 전까지 재입장 / 재초대가 거부된다. MVP 정책상 현재 또는\r\n * 과거 참가자만 차단 가능 (선제적 차단 불가 — 404 {@code USER_NOT_IN_ROOM}).</p>\r\n *\r\n * @param {string} roomId\r\n * @param {string} userId - 차단할 사용자 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async banMember(roomId, userId) {\r\n return this.apiClient.post(`/api/v1/rooms/${roomId}/banned-members`, { userId });\r\n }\r\n\r\n /**\r\n * 영구 차단 해제 — 방 관리자(OWNER/ADMIN) 만 호출 가능.\r\n *\r\n * <p>{@code bannedUserIds} 에서 제거만 수행. 실제 재입장은 별도 초대 / join 호출 필요.</p>\r\n *\r\n * @param {string} roomId\r\n * @param {string} userId - 해제할 사용자 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async unbanMember(roomId, userId) {\r\n return this.apiClient.delete(`/api/v1/rooms/${roomId}/banned-members/${userId}`);\r\n }\r\n\r\n /**\r\n * 방의 영구 차단 사용자 목록 조회 — 방 관리자(OWNER/ADMIN) 만 호출 가능.\r\n *\r\n * <p>응답 각 item: {@code { userId, nickname, profileUrl }}.\r\n * 일반 참가자가 호출 시 403 {@code NOT_ROOM_ADMIN}.</p>\r\n *\r\n * @param {string} roomId\r\n * @returns {Promise<Array<{userId: string, nickname: string, profileUrl: string}>>}\r\n */\r\n async getBannedMembers(roomId) {\r\n const response = await this.apiClient.get(`/api/v1/rooms/${roomId}/banned-members`);\r\n return response && typeof response === 'object' && 'data' in response\r\n ? response.data\r\n : response;\r\n }\r\n\r\n /**\r\n * 메시지 고정 (방장만).\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 고정할 메시지 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async pinMessage(roomId, messageId) {\r\n return this.apiClient.post(`/api/v1/rooms/group/${roomId}/pin/${messageId}`);\r\n }\r\n\r\n /**\r\n * 메시지 고정 해제 (방장만).\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async unpinMessage(roomId) {\r\n return this.apiClient.post(`/api/v1/rooms/group/${roomId}/unpin`);\r\n }\r\n\r\n /**\r\n * 이모지 리액션 토글.\r\n * <p>한 사용자는 메시지당 하나의 이모지만 보유.\r\n * 같은 이모지 재전송 시 해제, 다른 이모지 전송 시 교체.</p>\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 메시지 ID\r\n * @param {string} emoji - 이모지 (예: \"👍\", \"😂\")\r\n * @returns {Promise<Object>}\r\n */\r\n async toggleReaction(roomId, messageId, emoji) {\r\n return this.apiClient.post(`/api/v1/chat-messages/${roomId}/messages/${messageId}/reaction`, { emoji });\r\n }\r\n\r\n /**\r\n * 참가 가능한 그룹 채팅방 목록 조회\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기\r\n * @param {string} [params.lastId] - 마지막 채팅방 ID\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값\r\n * @returns {Promise<Object>}\r\n */\r\n async getAvailableGroupRooms(params = {}) {\r\n const { size = 50, lastId, lastSortValue } = params;\r\n return this.apiClient.get('/api/v1/rooms/groups/available', { size, lastId, lastSortValue });\r\n }\r\n\r\n /**\r\n * 모든 그룹 채팅방 목록 조회\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기\r\n * @param {string} [params.lastId] - 마지막 채팅방 ID\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값\r\n * @returns {Promise<Object>}\r\n */\r\n async getAllGroupRooms(params = {}) {\r\n const { size = 50, lastId, lastSortValue } = params;\r\n return this.apiClient.get('/api/v1/rooms/groups/all', { size, lastId, lastSortValue });\r\n }\r\n\r\n /**\r\n * 메시지 목록 조회\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기 (1-100)\r\n * @param {string} [params.lastId] - 마지막 메시지 ID (커서)\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값 (epoch millis)\r\n * @returns {Promise<Object>}\r\n */\r\n async getMessages(roomId, params = {}) {\r\n const { size = 50, lastId, lastSortValue } = params;\r\n const response = await this.apiClient.get(\r\n `/api/v1/chat-messages/${roomId}/list`, { size, lastId, lastSortValue }\r\n );\r\n // REST 와 WebSocket 의 타임스탬프 직렬화가 다르다 (REST=ISO 문자열, WS=epoch number).\r\n // 선언된 타입(number)으로 통일 — 기존 응답 구조(envelope)는 그대로 둔다.\r\n const page = this._unwrapSuccessResponse(response);\r\n if (page && Array.isArray(page.content)) {\r\n page.content.forEach((m) => this._normalizeMessageTimestamps(m));\r\n }\r\n return response;\r\n }\r\n\r\n /**\r\n * 링크 미리보기 조회.\r\n *\r\n * <p>서버가 OG / Twitter Card / HTML fallback 을 파싱해 미리보기 카드를 반환합니다.\r\n * 외부 사이트 차단 등으로 HTML 을 못 가져오면 공개 DNS 로 확인되는 URL 에 한해\r\n * 도메인 + favicon 기반 최소 카드를 반환할 수 있습니다.\r\n * 이 경우 {@code previewSource} 는 {@code FAVICON_FALLBACK} 입니다.\r\n * 미리보기를 만들 수 없는 URL 이면 {@code null} 을 반환합니다.</p>\r\n *\r\n * <p>민감한 URL 이 query string 으로 남지 않도록 POST body 로 호출합니다.</p>\r\n *\r\n * @param {string} url - 미리보기를 조회할 URL\r\n * @returns {Promise<Object|null>} LinkPreview 또는 null\r\n */\r\n async fetchLinkPreview(url) {\r\n const normalizedUrl = typeof url === 'string' ? url.trim() : '';\r\n if (!normalizedUrl) {\r\n throw new Error('url is required');\r\n }\r\n\r\n const response = await this.apiClient.post('/api/v1/link-preview', {\r\n url: normalizedUrl\r\n });\r\n\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 메시지 전송 (REST API).\r\n *\r\n * <p>멱등 POST — SDK 가 내부에서 UUID 를 자동 생성하며, 호출자는 {@code messageId} 를\r\n * 지정할 수 없다. 같은 요청의 네트워크 재시도(SDK 내부 구현 시) 에도 같은 UUID 가 쓰이도록\r\n * 설계된다. 서버 Unique 제약 + pre-check 로 중복 저장이 차단되며, 재시도 히트 시\r\n * {@code duplicate: true} 로 기존 결과가 그대로 반환된다.</p>\r\n *\r\n * <p>파일 분리 시 ({@code separateFiles: true}) 하나의 요청이 여러 메시지로 저장된다.\r\n * 각 메시지는 고유한 서버 {@code messageId} 를 가지며 응답의 {@code messages} 배열로 전달된다.</p>\r\n *\r\n * <p>답글: {@code data.replyToMessageId} 에 원본 메시지의 서버 {@code messageId} 를 지정.\r\n * 서버가 원본을 조회해서 snapshot 을 생성 후 저장하며, 응답/수신 메시지의 {@code replyTo}\r\n * 필드에 원본 요약이 포함됨. 원본이 존재하지 않으면 서버가 400 (REPLY_TARGET_NOT_FOUND) 반환.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} data\r\n * @param {string} [data.message] - 메시지 내용 (message 또는 fileInfos 중 하나 필수)\r\n * @param {Object[]} [data.fileInfos] - 파일 메타데이터 배열\r\n * @param {boolean} [data.separateFiles=true] - 파일 분리 여부\r\n * @param {string} [data.replyToMessageId] - 답글 대상 원본 메시지 ID (옵션)\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n * - {@code duplicate}: 멱등 재시도로 기존 결과를 반환한 경우 {@code true}\r\n * - {@code messages}: 저장된 메시지 배열 (파일 분리 시 여러 개). 각 원소의 {@code messageId} 는 서버가 부여한 고유 ID.\r\n *\r\n * @example\r\n * // 일반 메시지\r\n * const { messages } = await chat.sendMessage('room-id', { message: '안녕하세요' });\r\n * console.log(messages[0].messageId); // 서버가 생성한 ID — 답글/삭제/리액션에 사용\r\n *\r\n * @example\r\n * // 답글\r\n * await chat.sendMessage('room-id', {\r\n * message: '네 동감이에요',\r\n * replyToMessageId: 'original-msg-id'\r\n * });\r\n *\r\n * @example\r\n * // 파일 분리 전송 — messages 배열에 텍스트 + 파일별 메시지가 모두 담김\r\n * const { messages } = await chat.sendMessage('room-id', {\r\n * message: '사진 보냅니다',\r\n * fileInfos: [f0, f1, f2],\r\n * separateFiles: true\r\n * });\r\n * // messages.length === 4 (텍스트 1 + 이미지 3)\r\n */\r\n async sendMessage(roomId, data) {\r\n const messageId = this._generateMessageId();\r\n return this._sendMessageWithId(roomId, messageId, data);\r\n }\r\n\r\n /**\r\n * 메시지 전송 — Optimistic UI 버전.\r\n *\r\n * <p>{@link #sendMessage} 와 동일하게 서버에 메시지를 전송하지만, 호출 즉시\r\n * SDK 가 생성한 {@code baseMessageId} 와 응답 {@code messages} 배열과 1-1 매칭되는\r\n * 예측 {@code messageIds} 배열을 동기 반환한다. 호출자는 promise 가 resolve 되기 전에\r\n * 미리 placeholder 메시지를 UI 에 그려둘 수 있고, resolve 후 같은 ID 로 안전하게 교체할 수 있다.</p>\r\n *\r\n * <p>{@code messageIds} 는 서버의 messageId derivation 규칙 (텍스트=base, 파일=base#N) 을\r\n * SDK 가 mirror 하여 사전 계산한 값이다. 정상 케이스에서는 응답\r\n * {@code messages.map(m => m.messageId)} 와 길이/순서가 정확히 일치한다.</p>\r\n *\r\n * <p><b>분류 규칙 drift 방어</b>: 서버 파일 분류 규칙이 변경되어 예측이 어긋나면\r\n * SDK 가 warn 로그를 남기며, 이 경우 응답의 실제 {@code messageId} 로 reconcile 해야 한다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} data - {@link #sendMessage} 와 동일한 페이로드\r\n * @returns {{baseMessageId: string, messageIds: string[], promise: Promise<{duplicate: boolean, messages: Object[]}>}}\r\n *\r\n * @example\r\n * const { messageIds, promise } = chat.sendMessageOptimistic('room-id', {\r\n * message: '안녕하세요'\r\n * });\r\n * messageIds.forEach(id => appendMessage({ messageId: id, status: 'sending' }));\r\n * try {\r\n * const { messages } = await promise;\r\n * messages.forEach(m => replaceMessage(m.messageId, m));\r\n * } catch (e) {\r\n * messageIds.forEach(id => markMessageFailed(id, e));\r\n * }\r\n */\r\n sendMessageOptimistic(roomId, data) {\r\n const baseMessageId = this._generateMessageId();\r\n const messageIds = this._predictMessageIdsFromData(baseMessageId, data);\r\n\r\n const promise = this._sendMessageWithId(roomId, baseMessageId, data)\r\n .then((result) => this._attachOptimisticMetadata(messageIds, result));\r\n\r\n return { baseMessageId, messageIds, promise };\r\n }\r\n\r\n /**\r\n * messageId 를 외부에서 주입받아 실제 send API 를 호출하는 내부 메서드.\r\n * <p>{@link #sendMessage} 와 {@link #sendMessageOptimistic} 가 공유하는 단일 코어 — base messageId 를\r\n * 양쪽이 같은 값으로 사용해야 optimistic 핸들의 예측 ID 가 서버 저장 ID 와 일치한다.</p>\r\n *\r\n * @private\r\n * @param {string} roomId\r\n * @param {string} messageId - SDK 가 생성한 base messageId (UUID)\r\n * @param {Object} data\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n */\r\n async _sendMessageWithId(roomId, messageId, data) {\r\n // 메시지 전송 시 타이핑 표시 자동 중단\r\n this.stopTyping(roomId);\r\n\r\n // separateFiles 는 SDK 가 명시 전송 — 서버 default 가 변경되어도 SDK 의 prediction\r\n // 가정 (`undefined === true`) 과 wire 값이 어긋나지 않도록 fix.\r\n const response = await this.apiClient.post(`/api/v1/chat-messages/${roomId}/send`, {\r\n messageId,\r\n message: data.message,\r\n fileInfos: data.fileInfos,\r\n separateFiles: data.separateFiles !== false,\r\n replyToMessageId: data.replyToMessageId\r\n });\r\n\r\n const result = this._unwrapSuccessResponse(response);\r\n if (result && Array.isArray(result.messages)) {\r\n result.messages.forEach((m) => this._normalizeMessageTimestamps(m));\r\n }\r\n return result;\r\n }\r\n\r\n /**\r\n * 텍스트 메시지 전송 (간편 버전).\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} message - 메시지 내용\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n *\r\n * @example\r\n * const { messages } = await chat.sendTextMessage('room-id', '안녕하세요');\r\n * console.log(messages[0].messageId);\r\n */\r\n async sendTextMessage(roomId, message) {\r\n return this.sendMessage(roomId, { message });\r\n }\r\n\r\n /**\r\n * 텍스트 메시지 전송 — Optimistic UI 버전.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} message - 메시지 내용\r\n * @returns {{baseMessageId: string, messageIds: string[], promise: Promise<{duplicate: boolean, messages: Object[]}>}}\r\n *\r\n * @example\r\n * const { baseMessageId, promise } = chat.sendTextMessageOptimistic('room-id', '안녕');\r\n * appendMessage({ messageId: baseMessageId, content: '안녕', status: 'sending' });\r\n * const { messages } = await promise;\r\n * replaceMessage(baseMessageId, messages[0]);\r\n */\r\n sendTextMessageOptimistic(roomId, message) {\r\n return this.sendMessageOptimistic(roomId, { message });\r\n }\r\n\r\n /**\r\n * 채팅 첨부 파일 업로드 (Low-level).\r\n *\r\n * <p>파일 바이트를 서버로 업로드하고 서버가 생성한 {@code FileMetaData} 를 반환.\r\n * 반환값은 {@link #sendMessage} 의 {@code fileInfos} 에 그대로 넣어 전송.</p>\r\n *\r\n * <p>업로드와 메시지 전송을 분리 호출해야 하는 케이스 (업로드 후 프리뷰 표시 →\r\n * 사용자가 전송 버튼 클릭 시 메시지 전송 등) 에 사용. 한 번에 처리하려면\r\n * {@link #sendFileMessage} 가 편리.</p>\r\n *\r\n * @param {string} roomId - 업로드 대상 채팅방 ID (권한 검증 + S3 namespace)\r\n * @param {File|Blob} file - 업로드할 파일\r\n * @param {Object} [options]\r\n * @param {Function} [options.onProgress] - {@code ({loaded, total, percent}) => void} — 진행률 콜백\r\n * @param {AbortSignal} [options.signal] - 업로드 취소용 AbortSignal\r\n * @returns {Promise<Object>} FileMetaData (fileId/fileUrl/fileName/fileType/fileSize/imageInfo/videoInfo/audioInfo/documentInfo)\r\n *\r\n * @example\r\n * const meta = await chat.uploadFile('room-id', fileInput.files[0], {\r\n * onProgress: ({ percent }) => console.log(`${percent}%`)\r\n * });\r\n * await chat.sendMessage('room-id', {\r\n * fileInfos: [meta],\r\n * message: '첨부합니다'\r\n * });\r\n */\r\n async uploadFile(roomId, file, options = {}) {\r\n if (!roomId) {\r\n throw new Error('roomId is required');\r\n }\r\n if (!file) {\r\n throw new Error('file is required');\r\n }\r\n\r\n const response = await this.apiClient.upload(\r\n `/api/v1/files/${roomId}/upload`,\r\n file,\r\n {\r\n onProgress: options.onProgress,\r\n signal: options.signal\r\n }\r\n );\r\n\r\n // 서버 SuccessResponse 래퍼 안의 data 가 FileMetaData\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 파일 업로드 + 메시지 전송 통합 (High-level).\r\n *\r\n * <p>내부적으로:</p>\r\n * <ol>\r\n * <li>각 파일을 {@link #uploadFile} 로 업로드 (배열이면 병렬)</li>\r\n * <li>수신된 {@code FileMetaData} 들을 모아 {@link #sendMessage} 로 전송</li>\r\n * </ol>\r\n *\r\n * <p>업로드 중 하나라도 실패하면 전체 rejected — 이미 업로드된 S3 객체는\r\n * 고아로 남으며 라이프사이클 정책으로 정리됨 (설계 결정).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {File|Blob|Array<File|Blob>} files - 단일 파일 또는 파일 배열\r\n * @param {Object} [options]\r\n * @param {string} [options.message] - 함께 보낼 텍스트 본문\r\n * @param {boolean} [options.separateFiles] - 파일별 개별 메시지 분리 여부 (서버 설정)\r\n * @param {string} [options.replyToMessageId] - 답글 대상 원본 메시지의 서버 {@code messageId}\r\n * @param {Function} [options.onUploadProgress] - {@code ({loaded,total,percent,fileIndex}) => void} — 파일별 진행률\r\n * @param {AbortSignal} [options.signal] - 취소 시그널 — 업로드 중에만 유효\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n *\r\n * @example\r\n * // 단일 파일\r\n * await chat.sendFileMessage('room-id', file, {\r\n * message: '첨부',\r\n * onUploadProgress: ({ percent }) => console.log(`${percent}%`)\r\n * });\r\n *\r\n * @example\r\n * // 다중 파일 (병렬 업로드)\r\n * await chat.sendFileMessage('room-id', [file1, file2, file3], {\r\n * message: '여러 파일',\r\n * onUploadProgress: ({ fileIndex, percent }) =>\r\n * console.log(`파일 ${fileIndex}: ${percent}%`)\r\n * });\r\n */\r\n async sendFileMessage(roomId, files, options = {}) {\r\n const fileArray = this._normalizeFileArray(files);\r\n const metas = await this._uploadFiles(roomId, fileArray, options);\r\n const fileInfos = this._applyFileMetadata(metas, options.metadata);\r\n\r\n return this.sendMessage(roomId, {\r\n message: options.message,\r\n fileInfos,\r\n separateFiles: options.separateFiles,\r\n replyToMessageId: options.replyToMessageId\r\n });\r\n }\r\n\r\n /**\r\n * 파일 업로드 + 메시지 전송 — Optimistic UI 버전.\r\n *\r\n * <p>호출 즉시 {@code baseMessageId} 와 응답 {@code messages} 와 1-1 매칭되는 예측\r\n * {@code messageIds} 를 동기 반환한다. 호출자는 업로드 시작 전에 placeholder 메시지를\r\n * UI 에 그릴 수 있다.</p>\r\n *\r\n * <p><b>입력 검증은 동기 throw</b>: 빈 배열 / max 초과 등 {@link #_normalizeFileArray} 의\r\n * 검증 실패는 promise 가 만들어지기 전에 즉시 throw 되므로, 잘못된 입력에 대해 placeholder 가\r\n * 먼저 그려지는 일은 없다.</p>\r\n *\r\n * <p><b>업로드 실패 처리</b>: 업로드 단계에서 실패하면 promise 가 reject 된다. 호출자는 UI 의\r\n * placeholder 들을 모두 실패 상태로 표시하고, S3 에 부분 업로드된 객체는 라이프사이클 정책으로 정리된다.</p>\r\n *\r\n * @param {string} roomId\r\n * @param {File|Blob|Array<File|Blob>} files\r\n * @param {Object} [options] - {@link #sendFileMessage} 와 동일\r\n * @returns {{baseMessageId: string, messageIds: string[], promise: Promise<{duplicate: boolean, messages: Object[]}>}}\r\n *\r\n * @example\r\n * const { messageIds, promise } = chat.sendFileMessageOptimistic('room-id', files, {\r\n * message: '사진 보냅니다'\r\n * });\r\n * messageIds.forEach((id, i) => appendMessage({\r\n * messageId: id,\r\n * localFile: files[i] || null,\r\n * status: 'uploading'\r\n * }));\r\n * try {\r\n * const { messages } = await promise;\r\n * messages.forEach(m => replaceMessage(m.messageId, m));\r\n * } catch (e) {\r\n * messageIds.forEach(id => markMessageFailed(id, e));\r\n * }\r\n */\r\n sendFileMessageOptimistic(roomId, files, options = {}) {\r\n // validation 은 동기 — placeholder 가 그려지기 전에 throw 되어야 함\r\n const fileArray = this._normalizeFileArray(files);\r\n\r\n const baseMessageId = this._generateMessageId();\r\n const messageIds = this._predictMessageIds(\r\n baseMessageId,\r\n this._predictGroupCount(fileArray, options.separateFiles !== false),\r\n this._hasTextMessage(options.message)\r\n );\r\n\r\n const promise = (async () => {\r\n const metas = await this._uploadFiles(roomId, fileArray, options);\r\n const fileInfos = this._applyFileMetadata(metas, options.metadata);\r\n const result = await this._sendMessageWithId(roomId, baseMessageId, {\r\n message: options.message,\r\n fileInfos,\r\n separateFiles: options.separateFiles,\r\n replyToMessageId: options.replyToMessageId\r\n });\r\n return this._attachOptimisticMetadata(messageIds, result);\r\n })();\r\n\r\n return { baseMessageId, messageIds, promise };\r\n }\r\n\r\n /**\r\n * 파일 입력 정규화 — 단일 파일 / 배열 양쪽 허용 + 개수 검증.\r\n * <p>{@link #sendFileMessage} 와 {@link #sendFileMessageOptimistic} 가 공유.</p>\r\n *\r\n * @private\r\n * @param {File|Blob|Array<File|Blob>} files\r\n * @returns {Array<File|Blob>}\r\n * @throws {Error} 빈 배열 또는 MAX_FILES_PER_MESSAGE 초과\r\n */\r\n _normalizeFileArray(files) {\r\n const fileArray = Array.isArray(files) ? files : [files];\r\n if (fileArray.length === 0) {\r\n throw new Error('At least one file is required');\r\n }\r\n if (fileArray.length > MAX_FILES_PER_MESSAGE) {\r\n throw new Error(`A maximum of ${MAX_FILES_PER_MESSAGE} files can be sent in one message`);\r\n }\r\n return fileArray;\r\n }\r\n\r\n /**\r\n * 파일 배열 병렬 업로드 — 각 파일의 진행률은 fileIndex 로 구분.\r\n * <p>{@link #sendFileMessage} 와 {@link #sendFileMessageOptimistic} 가 공유.</p>\r\n *\r\n * @private\r\n * @param {string} roomId\r\n * @param {Array<File|Blob>} fileArray\r\n * @param {Object} options - signal, onUploadProgress 사용\r\n * @returns {Promise<Object[]>} FileMetaData 배열\r\n */\r\n async _uploadFiles(roomId, fileArray, options = {}) {\r\n return Promise.all(\r\n fileArray.map((file, index) =>\r\n this.uploadFile(roomId, file, {\r\n signal: options.signal,\r\n onProgress: options.onUploadProgress\r\n ? (p) => options.onUploadProgress({ ...p, fileIndex: index })\r\n : undefined\r\n })\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 업로드된 FileMetaData 배열에 고객사 pass-through metadata 를 병합.\r\n *\r\n * <p>{@link #sendFileMessage} / {@link #sendFileMessageOptimistic} 가 공유.\r\n * 호출자가 {@code options.metadata} 를 안 주면 metas 가 그대로 반환되어 기존 동작과 100% 호환.</p>\r\n *\r\n * @private\r\n * @param {Object[]} metas - uploadFile 결과의 FileMetaData 배열\r\n * @param {Object|Array<Object|null|undefined>|null|undefined} metadata - 단일 객체(broadcast) 또는 index 별 배열\r\n * @returns {Object[]} metadata 가 병합된 새 배열 (기존 meta 객체는 수정하지 않음)\r\n */\r\n _applyFileMetadata(metas, metadata) {\r\n if (metadata === undefined || metadata === null) {\r\n return metas;\r\n }\r\n\r\n return metas.map((meta, index) => {\r\n const itemMetadata = Array.isArray(metadata)\r\n ? metadata[index]\r\n : metadata;\r\n\r\n if (itemMetadata === undefined || itemMetadata === null) {\r\n return meta;\r\n }\r\n\r\n return {\r\n ...meta,\r\n metadata: itemMetadata\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * 답글 전송 (간편 버전) — 텍스트 답글 전용.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} message - 답글 내용\r\n * @param {string} replyToMessageId - 원본 메시지의 서버 {@code messageId}\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n *\r\n * @example\r\n * // \"안녕하세요\" 메시지에 답장\r\n * await chat.sendReply('room-id', '네 반가워요', 'original-msg-id');\r\n */\r\n async sendReply(roomId, message, replyToMessageId) {\r\n return this.sendMessage(roomId, { message, replyToMessageId });\r\n }\r\n\r\n /**\r\n * 답글 전송 — Optimistic UI 버전.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} message - 답글 내용\r\n * @param {string} replyToMessageId - 원본 메시지의 서버 {@code messageId}\r\n * @returns {{baseMessageId: string, messageIds: string[], promise: Promise<{duplicate: boolean, messages: Object[]}>}}\r\n */\r\n sendReplyOptimistic(roomId, message, replyToMessageId) {\r\n return this.sendMessageOptimistic(roomId, { message, replyToMessageId });\r\n }\r\n\r\n /**\r\n * 메시지 삭제.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 메시지 ID\r\n * @param {string} [deleteType='ALL'] - 삭제 유형.\r\n * {@code 'ALL'} — 모두에게 삭제 (양쪽 \"삭제된 메시지입니다\" 표시).\r\n * {@code 'SELF'} — 나만 삭제 (내 화면에서만 숨김, 상대방은 원본 유지).\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // 모두에게 삭제\r\n * await chat.deleteMessage('room-id', 'msg-id');\r\n *\r\n * @example\r\n * // 나만 삭제\r\n * await chat.deleteMessage('room-id', 'msg-id', 'SELF');\r\n */\r\n async deleteMessage(roomId, messageId, deleteType = 'ALL') {\r\n return this.apiClient.post(`/api/v1/chat-messages/${roomId}/messages/${messageId}/delete`, {\r\n deleteType\r\n });\r\n }\r\n\r\n /**\r\n * 메시지 수정.\r\n *\r\n * <p>본인 메시지만 수정 가능. 수정 후 '수정됨' 라벨이 표시됩니다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 메시지 ID\r\n * @param {string} message - 수정할 내용 (max 5000자)\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * await chat.editMessage('room-id', 'msg-id', '수정된 내용입니다');\r\n */\r\n async editMessage(roomId, messageId, message) {\r\n return this.apiClient.put(`/api/v1/chat-messages/${roomId}/messages/${messageId}`, {\r\n message\r\n });\r\n }\r\n\r\n /**\r\n * 서버의 {@code SendMessageService.hasTextMessage} mirror — null/공백 문자열을 모두 \"텍스트 없음\" 으로 취급.\r\n * <p>서버 기준 ({@code message != null && !message.isBlank()}) 과 동일해야 messageIds prediction 이 일치한다.</p>\r\n *\r\n * @private\r\n * @param {string|null|undefined} message\r\n * @returns {boolean}\r\n */\r\n _hasTextMessage(message) {\r\n return typeof message === 'string' && message.trim().length > 0;\r\n }\r\n\r\n /**\r\n * MIME 타입 → 서버 {@code ChatMessageType} 분류 (서버 {@code FileGroupingService.classifyFileType} mirror).\r\n * <p>서버 도메인 규칙과 동일해야 group count 예측이 일치한다. 서버 변경 시 본 메서드도 동기화 필요.</p>\r\n *\r\n * @private\r\n * @param {string} mimeType\r\n * @returns {string} 'IMAGE' | 'VIDEO' | 'AUDIO' | 'DOCUMENT' | 'FILE'\r\n */\r\n _classifyFileType(mimeType) {\r\n const mime = (mimeType || '').toLowerCase();\r\n if (mime.startsWith('image/')) return 'IMAGE';\r\n if (mime.startsWith('video/')) return 'VIDEO';\r\n if (mime.startsWith('audio/')) return 'AUDIO';\r\n if (mime.includes('pdf') || mime.includes('document')) return 'DOCUMENT';\r\n return 'FILE';\r\n }\r\n\r\n /**\r\n * 파일 그룹 수 예측 (서버 {@code FileGroupingService.groupFiles} mirror).\r\n * <p>{@code separateFiles=true} 면 파일 개수, {@code false} 면 타입별 distinct 개수.\r\n * File 객체 ({@code .type}) / FileMetaData 객체 ({@code .fileType}) 양쪽 입력 모두 처리.</p>\r\n *\r\n * @private\r\n * @param {Array<File|Blob|Object>} fileArray\r\n * @param {boolean} separateFiles\r\n * @returns {number}\r\n */\r\n _predictGroupCount(fileArray, separateFiles) {\r\n if (!fileArray || fileArray.length === 0) return 0;\r\n if (separateFiles) return fileArray.length;\r\n\r\n // 입력 순서 보존하면서 distinct 타입 카운트 (서버 LinkedHashMap 동작과 동일)\r\n const typesSeen = new Set();\r\n for (const file of fileArray) {\r\n const mime = file && (file.type || file.fileType) || '';\r\n typesSeen.add(this._classifyFileType(mime));\r\n }\r\n return typesSeen.size;\r\n }\r\n\r\n /**\r\n * 서버 저장 messageId 배열 예측 (서버 {@code SendMessageService} mirror).\r\n * <p>규칙 ({@code SendMessageService.java:102-106, 197}):\r\n * {@code expectedMessageCount = (hasMessage?1:0) + groupCount}, 1 이면 base, 2 이상이면\r\n * 텍스트=base / 파일=base#0..#(N-1).</p>\r\n *\r\n * @private\r\n * @param {string} baseMessageId\r\n * @param {number} groupCount\r\n * @param {boolean} hasMessage\r\n * @returns {string[]} 응답 messages 와 같은 순서\r\n */\r\n _predictMessageIds(baseMessageId, groupCount, hasMessage) {\r\n const expectedCount = (hasMessage ? 1 : 0) + groupCount;\r\n if (expectedCount === 0) return [];\r\n if (expectedCount === 1) return [baseMessageId];\r\n\r\n const ids = [];\r\n if (hasMessage) ids.push(baseMessageId);\r\n for (let i = 0; i < groupCount; i++) {\r\n ids.push(`${baseMessageId}#${i}`);\r\n }\r\n return ids;\r\n }\r\n\r\n /**\r\n * {@code SendMessageData} 페이로드로부터 messageIds 예측.\r\n * <p>{@link #sendMessageOptimistic} 가 사용 — {@code data.fileInfos} 가 있으면 그걸로\r\n * group count 예측, 없으면 텍스트 단건으로 처리.</p>\r\n *\r\n * @private\r\n */\r\n _predictMessageIdsFromData(baseMessageId, data) {\r\n const fileInfos = (data && data.fileInfos) || [];\r\n const separate = !data || data.separateFiles !== false;\r\n const groupCount = this._predictGroupCount(fileInfos, separate);\r\n const hasMessage = this._hasTextMessage(data && data.message);\r\n return this._predictMessageIds(baseMessageId, groupCount, hasMessage);\r\n }\r\n\r\n /**\r\n * 예측 messageIds 와 서버 응답을 비교하여 result 에 optimistic metadata 첨부.\r\n * <p>서버 분류 규칙 drift 를 런타임에 감지하기 위한 방어 로직. mismatch 발생 시\r\n * warn 로그 + 응답에 {@code optimistic.predictionMatched: false} 를 실어 호출자가\r\n * 프로그래밍적으로 fallback reconcile 을 결정할 수 있게 한다.</p>\r\n *\r\n * <p>길이 mismatch 와 같은 길이의 값/순서 mismatch 모두 동일하게 {@code predictionMatched: false}\r\n * 로 표시된다. 호출자는 README \"Drift 방어\" 섹션의 fallback 패턴을 사용하면 된다.</p>\r\n *\r\n * @private\r\n * @param {string[]} predictedMessageIds\r\n * @param {{duplicate: boolean, messages: Array<{messageId: string}>}} result\r\n * @returns {{duplicate: boolean, messages: Array, optimistic: {predictedMessageIds: string[], actualMessageIds: string[], predictionMatched: boolean}}}\r\n */\r\n _attachOptimisticMetadata(predictedMessageIds, result) {\r\n const actualMessageIds = (result && Array.isArray(result.messages))\r\n ? result.messages.map(m => (m && m.messageId) || null)\r\n : [];\r\n\r\n const predictionMatched =\r\n actualMessageIds.length === predictedMessageIds.length &&\r\n predictedMessageIds.every((id, i) => id === actualMessageIds[i]);\r\n\r\n if (!predictionMatched) {\r\n this.logger.warn('Optimistic messageId prediction mismatch — server may have changed grouping rules. Use result.optimistic.actualMessageIds to reconcile.', {\r\n predicted: predictedMessageIds,\r\n actual: actualMessageIds\r\n });\r\n }\r\n\r\n return {\r\n ...result,\r\n optimistic: {\r\n predictedMessageIds,\r\n actualMessageIds,\r\n predictionMatched\r\n }\r\n };\r\n }\r\n\r\n /**\r\n * 메시지 ID 생성 (UUID v4 형식)\r\n * @private\r\n * @returns {string}\r\n */\r\n _generateMessageId() {\r\n // crypto.randomUUID() 우선 사용 (높은 엔트로피), 미지원 환경 fallback\r\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\r\n return crypto.randomUUID();\r\n }\r\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\r\n const r = Math.random() * 16 | 0;\r\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\r\n return v.toString(16);\r\n });\r\n }\r\n\r\n /**\r\n * 서버 SuccessResponse 래퍼가 있으면 data 만 꺼내고, 아니면 원본 응답을 그대로 반환합니다.\r\n * data 가 null 이어도 그대로 null 을 반환해야 하므로 nullish coalescing 을 쓰지 않습니다.\r\n *\r\n * @private\r\n * @param {*} response\r\n * @returns {*}\r\n */\r\n _unwrapSuccessResponse(response) {\r\n return response\r\n && typeof response === 'object'\r\n && Object.prototype.hasOwnProperty.call(response, 'data')\r\n ? response.data\r\n : response;\r\n }\r\n\r\n /**\r\n * REST 메시지 응답의 타임스탬프를 epoch millis(number)로 정규화합니다 (in-place).\r\n *\r\n * <p>서버의 두 전달 경로가 서로 다르게 직렬화한다 — WebSocket 이벤트는 epoch number,\r\n * REST(목록/전송 응답)는 ISO 문자열(타임존 없는 LocalDateTime). 선언된 타입\r\n * ({@code ChatMessage.sentAt: number})으로 통일해 소비자 분기를 제거한다.</p>\r\n *\r\n * <p>⚠️ 한계: 서버 ISO 문자열에 타임존이 없어 브라우저 로컬 타임존으로 해석된다 —\r\n * 서버(KST)와 사용자 타임존이 다르면 오프셋만큼 어긋날 수 있다. 근본 해소는\r\n * 백엔드 직렬화 통일(후속 결정) 영역.</p>\r\n *\r\n * @private\r\n * @param {Object} message - REST 응답의 메시지 객체 (수정됨)\r\n * @returns {Object} 같은 객체\r\n */\r\n _normalizeMessageTimestamps(message) {\r\n if (!message || typeof message !== 'object') {\r\n return message;\r\n }\r\n message.sentAt = this._toEpochMillis(message.sentAt);\r\n message.editedAt = this._toEpochMillis(message.editedAt);\r\n if (message.replyTo && typeof message.replyTo === 'object') {\r\n message.replyTo.sentAt = this._toEpochMillis(message.replyTo.sentAt);\r\n }\r\n return message;\r\n }\r\n\r\n /**\r\n * ISO 문자열이면 epoch millis 로 변환, number/null/undefined 는 그대로 통과.\r\n * 파싱 불가 문자열은 원본 유지 (조용한 데이터 손실 방지).\r\n *\r\n * @private\r\n * @param {*} value\r\n * @returns {*}\r\n */\r\n _toEpochMillis(value) {\r\n if (typeof value !== 'string') {\r\n return value;\r\n }\r\n const parsed = Date.parse(value);\r\n return Number.isNaN(parsed) ? value : parsed;\r\n }\r\n\r\n // ==================== WebSocket 구독 ====================\r\n\r\n /**\r\n * 채팅방 구독.\r\n *\r\n * <p>구독 시 메시지, 읽음, 타이핑, 멤버 변경 이벤트를 자동으로 수신.\r\n * 멤버 변경은 roomList 구독 이벤트를 내부 변환하여\r\n * {@code memberJoined} / {@code memberLeft} 로 emit.</p>\r\n *\r\n * <p>자동 읽음 처리는 {@link #setActiveRoom} 으로 현재 보고 있는 방을 설정해야 동작합니다.\r\n * 구독만으로는 읽음 처리되지 않으며, 다른 방을 보고 있으면 안읽음 카운트가 유지됩니다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<void>}\r\n *\r\n * @fires memberJoined - { roomId, members: [{ userId, nickname, profileUrl }], participantCount, timestamp }\r\n * @fires memberLeft - { roomId, members: [{ userId, nickname, profileUrl }], participantCount, timestamp }\r\n */\r\n async subscribeRoom(roomId) {\r\n if (this.subscribedRooms.has(roomId)) {\r\n this.logger.warn(`Already subscribed to room: ${roomId}`);\r\n return;\r\n }\r\n\r\n // 메시지 구독\r\n const chatDestination = WebSocketPaths.getChatDestination(roomId);\r\n await this.connectionManager.subscribe(chatDestination, (message) => {\r\n this._handleChatMessage(roomId, message);\r\n });\r\n\r\n // 읽음 이벤트 구독\r\n const readDestination = WebSocketPaths.getChatReadDestination(roomId);\r\n await this.connectionManager.subscribe(readDestination, (message) => {\r\n this._handleReadEvent(roomId, message);\r\n });\r\n\r\n // 타이핑 이벤트 구독\r\n const typingDestination = WebSocketPaths.getChatTypingDestination(roomId);\r\n await this.connectionManager.subscribe(typingDestination, (event) => {\r\n this._handleTypingEvent(roomId, event);\r\n });\r\n\r\n // 멤버 변경 이벤트 — roomList 이벤트를 방 수준으로 변환하여 자동 emit\r\n // 서버가 members 배열(입장/퇴장 사용자들) + participantCount(정확한 인원수) 포함\r\n const memberJoinedHandler = (event) => {\r\n if (event.roomId === roomId) {\r\n this.emit('memberJoined', {\r\n roomId,\r\n members: event.members || [],\r\n participantCount: event.activeParticipantCount,\r\n timestamp: event.timestamp\r\n });\r\n }\r\n };\r\n const memberLeftHandler = (event) => {\r\n if (event.roomId === roomId) {\r\n this.emit('memberLeft', {\r\n roomId,\r\n members: event.members || [],\r\n participantCount: event.activeParticipantCount,\r\n timestamp: event.timestamp\r\n });\r\n }\r\n };\r\n this.on('roomListJoined', memberJoinedHandler);\r\n this.on('roomListLeft', memberLeftHandler);\r\n\r\n this.subscribedRooms.set(roomId, {\r\n chatDestination,\r\n readDestination,\r\n typingDestination,\r\n memberJoinedHandler,\r\n memberLeftHandler,\r\n subscribedAt: new Date()\r\n });\r\n\r\n this.logger.info(`Subscribed to room: ${roomId}`);\r\n this.emit('roomSubscribed', { roomId });\r\n }\r\n\r\n /**\r\n * 채팅방 구독 해제\r\n * @param {string} roomId - 채팅방 ID\r\n */\r\n unsubscribeRoom(roomId) {\r\n const subscription = this.subscribedRooms.get(roomId);\r\n if (!subscription) {\r\n this.logger.warn(`Not subscribed to room: ${roomId}`);\r\n return;\r\n }\r\n\r\n this.connectionManager.unsubscribe(subscription.chatDestination);\r\n this.connectionManager.unsubscribe(subscription.readDestination);\r\n if (subscription.typingDestination) {\r\n this.connectionManager.unsubscribe(subscription.typingDestination);\r\n }\r\n\r\n // 멤버 변경 리스너 해제\r\n if (subscription.memberJoinedHandler) {\r\n this.off('roomListJoined', subscription.memberJoinedHandler);\r\n }\r\n if (subscription.memberLeftHandler) {\r\n this.off('roomListLeft', subscription.memberLeftHandler);\r\n }\r\n\r\n // 타이핑 타이머 정리 (outgoing + incoming assistant 둘 다)\r\n this._clearTypingTimer(roomId);\r\n this._clearAssistantTypingTimer(roomId);\r\n\r\n // 현재 보고 있는 방이면 activeRoom 해제\r\n if (this._activeRoomId === roomId) {\r\n this._activeRoomId = null;\r\n }\r\n\r\n this.subscribedRooms.delete(roomId);\r\n\r\n // 방별 dedup bucket 정리 — 장시간 세션에서 많은 방을 오갈 때 메모리 누수 차단.\r\n // 각 bucket 은 방당 최대 _maxSeenPerRoom(200) entries 라 개별은 작지만 방 수가 늘면 누적.\r\n this._seenChatMessageIdsByRoom.delete(roomId);\r\n this._seenRoomListMessageIdsByRoom.delete(roomId);\r\n\r\n this.logger.info(`Unsubscribed from room: ${roomId}`);\r\n this.emit('roomUnsubscribed', { roomId });\r\n }\r\n\r\n /**\r\n * 모든 채팅방 구독 해제\r\n */\r\n unsubscribeAllRooms() {\r\n this.subscribedRooms.forEach((_, roomId) => {\r\n this.unsubscribeRoom(roomId);\r\n });\r\n }\r\n\r\n /**\r\n * 현재 보고 있는 채팅방 설정.\r\n * 이 방에서 수신되는 다른 사용자의 메시지는 자동으로 읽음 처리됩니다.\r\n * 다른 방이나 리스트 화면으로 이동 시 {@link #clearActiveRoom} 호출.\r\n *\r\n * @param {string} roomId - 현재 보고 있는 채팅방 ID\r\n */\r\n setActiveRoom(roomId) {\r\n this._activeRoomId = roomId;\r\n this.logger.debug(`Active room set: ${roomId}`);\r\n }\r\n\r\n /**\r\n * 현재 보고 있는 채팅방 해제 (리스트 화면 등으로 이동 시).\r\n * 자동 읽음 처리가 중단됩니다.\r\n */\r\n clearActiveRoom() {\r\n this.logger.debug(`Active room cleared (was: ${this._activeRoomId})`);\r\n this._activeRoomId = null;\r\n }\r\n\r\n /**\r\n * 현재 보고 있는 채팅방 ID 반환.\r\n * @returns {string|null}\r\n */\r\n getActiveRoom() {\r\n return this._activeRoomId;\r\n }\r\n\r\n /**\r\n * 채팅방 리스트 실시간 업데이트 구독 (카톡 스타일).\r\n *\r\n * 리스트 화면에서 호출. 구독 후 다음 이벤트를 받을 수 있음:\r\n * - roomListUpdate — 모든 이벤트 (통합, eventType 으로 분기)\r\n * - roomListMessage — 새 메시지 수신 (MESSAGE_RECEIVED)\r\n * - roomListMessageDeleted — 현재 lastMessage 삭제 (MESSAGE_DELETED)\r\n * - roomListMessageUpdated — 현재 lastMessage 편집 (MESSAGE_UPDATED)\r\n * - roomListCreated — 새 방 생성 (ROOM_CREATED)\r\n * - roomListJoined — 방 입장 (ROOM_JOINED)\r\n * - roomListLeft — 방 퇴장 (ROOM_LEFT)\r\n * - roomListSelfLeft — 본인이 나간 경우 (리스트에서 제거 신호)\r\n *\r\n * @returns {Promise<void>}\r\n *\r\n * @example\r\n * await client.chat.subscribeRoomList();\r\n *\r\n * client.on('roomListMessage', (event) => {\r\n * // 해당 방을 리스트 최상단으로 이동\r\n * // event.actorId !== currentUserId 일 때만 unread +1\r\n * moveRoomToTop(event.roomId);\r\n * updateLastMessage(event.roomId, event.lastMessage, event.lastMessageAt);\r\n * if (event.actorId !== currentUserId) {\r\n * incrementUnread(event.roomId, event.unreadCountDelta);\r\n * }\r\n * });\r\n *\r\n * client.on('roomListSelfLeft', (event) => {\r\n * // 본인이 나간 방 → 리스트에서 제거\r\n * removeRoomFromList(event.roomId);\r\n * });\r\n */\r\n async subscribeRoomList() {\r\n if (this.roomListSubscribed) {\r\n this.logger.warn('Already subscribed to room list');\r\n return;\r\n }\r\n\r\n const destination = WebSocketPaths.ROOM_LIST_USER_DESTINATION;\r\n await this.connectionManager.subscribe(destination, (event) => {\r\n // noinspection JSCheckFunctionSignatures\r\n this._handleRoomListEvent(event);\r\n });\r\n\r\n this.roomListSubscribed = true;\r\n this.logger.info('Subscribed to room list');\r\n this.emit('roomListSubscribed', {});\r\n }\r\n\r\n /**\r\n * 채팅방 리스트 구독 해제.\r\n */\r\n unsubscribeRoomList() {\r\n if (!this.roomListSubscribed) {\r\n this.logger.warn('Not subscribed to room list');\r\n return;\r\n }\r\n\r\n this.connectionManager.unsubscribe(WebSocketPaths.ROOM_LIST_USER_DESTINATION);\r\n this.roomListSubscribed = false;\r\n\r\n this.logger.info('Unsubscribed from room list');\r\n this.emit('roomListUnsubscribed', {});\r\n }\r\n\r\n /**\r\n * 채팅방 리스트 구독 여부.\r\n * @returns {boolean}\r\n */\r\n isRoomListSubscribed() {\r\n return this.roomListSubscribed;\r\n }\r\n\r\n /**\r\n * 메시지 읽음 처리\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 메시지 ID\r\n */\r\n markAsRead(roomId, messageId) {\r\n if (!this.connectionManager.isConnected()) {\r\n this.logger.warn('Cannot mark as read: not connected');\r\n return false;\r\n }\r\n\r\n return this.connectionManager.send(WebSocketPaths.CHAT_READ, {\r\n roomId,\r\n messageId\r\n });\r\n }\r\n\r\n // ==================== 이벤트 핸들러 ====================\r\n\r\n /**\r\n * 채팅 메시지 수신 처리.\r\n *\r\n * <p>서버는 동일 채널(/topic/chat/{roomId})로 신규/수정/삭제/리액션/OG 첨부\r\n * 이벤트를 모두 발행하고, 페이로드의 {@code eventType} 으로 구분한다.\r\n * 클라는 eventType 에 따라 별도 이벤트로 re-emit.</p>\r\n *\r\n * <ul>\r\n * <li>{@code MESSAGE_CREATED} → {@code message} + {@code newMessage} (상대 메시지면) + 자동 읽음</li>\r\n * <li>{@code MESSAGE_UPDATED} → {@code message} + {@code messageUpdated}</li>\r\n * <li>{@code MESSAGE_DELETED} → {@code message} + {@code messageDeleted}</li>\r\n * <li>{@code REACTION_CHANGED} → {@code message} + {@code reactionChanged}</li>\r\n * <li>{@code LINK_PREVIEW_ATTACHED} → {@code message} + {@code linkPreviewAttached}</li>\r\n * <li>{@code MESSAGE_TRANSLATED} → {@code message} + {@code messageTranslated} (translations 머지)</li>\r\n * </ul>\r\n *\r\n * <p>{@code message} 이벤트는 모든 케이스에서 발행되어 하위 호환을 유지한다 —\r\n * 기존 구독 코드는 messageId 로 merge 하면 계속 작동.</p>\r\n *\r\n * @private\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} message - 수신된 메시지 (eventType 필드 포함)\r\n */\r\n _handleChatMessage(roomId, message) {\r\n this.logger.debug(`Message received in room ${roomId} (${message.eventType}):`, message);\r\n\r\n // MESSAGE_CREATED 만 dedup 대상 — 다른 eventType (UPDATED/DELETED/REACTION/LINK_PREVIEW) 는\r\n // 같은 messageId 의 의도된 재발행이라 dedup 하면 안 됨.\r\n if (message.eventType === 'MESSAGE_CREATED'\r\n && this._shouldDedupMessage(this._seenChatMessageIdsByRoom, roomId, message.messageId)) {\r\n this.logger.debug(`Duplicate MESSAGE_CREATED suppressed: room=${roomId}, messageId=${message.messageId}`);\r\n return;\r\n }\r\n\r\n // 편집(MESSAGE_UPDATED) 시 서버가 번역을 무효화한다. payload 에 번역 필드가 없으면 generic emit 前에\r\n // 명시적 null 처리 — generic 'message' / 상위 'chatMessage' 소비자도 stale 번역을 즉시 지우도록 (다국어 §9).\r\n if (message.eventType === 'MESSAGE_UPDATED') {\r\n if (message.translations === undefined) message.translations = null;\r\n if (message.sourceLang === undefined) message.sourceLang = null;\r\n if (message.translationsOf === undefined) message.translationsOf = null;\r\n }\r\n\r\n // 모든 이벤트는 통합 이벤트로 pass-through (하위 호환)\r\n this.emit('message', { roomId, message });\r\n\r\n switch (message.eventType) {\r\n case 'MESSAGE_CREATED':\r\n // 어시 메시지 도착 = \"AI 응답 준비중\" 종료 신호. typing timer 즉시 해제 + typing=false emit.\r\n // 서버는 typing=false 를 발행하지 않고 client-side 가 책임 (assistant typing 설계).\r\n // userId / userName 은 서버 typing=true 발행과 정확히 일치하는 generic marker 사용 —\r\n // UI 가 roomId+userId 로 typing state 추적 시 true/false 가 같은 키로 매칭되어야 안 꺼지는\r\n // 케이스 방지. message.userId (페르소나 ID) 를 그대로 쓰면 키 불일치로 영구 표시 위험.\r\n if (message.senderType === 'ASSISTANT') {\r\n this._clearAssistantTypingTimer(roomId);\r\n this.emit('typing', {\r\n roomId,\r\n userId: this._assistantTypingUserId,\r\n userName: this._assistantTypingUserName,\r\n typing: false,\r\n senderType: 'ASSISTANT'\r\n });\r\n }\r\n\r\n // 상대방 메시지일 때만 newMessage + 자동 읽음 처리\r\n if (message.userId !== this.userId) {\r\n this.emit('newMessage', { roomId, message });\r\n\r\n // 현재 보고 있는 방에서만 자동 읽음 처리\r\n if (this._activeRoomId === roomId && message.messageId) {\r\n this.markAsRead(roomId, message.messageId);\r\n }\r\n }\r\n break;\r\n\r\n case 'MESSAGE_UPDATED':\r\n // 번역 stale 클리어(번역 필드 null 보정)는 generic emit 前에 이미 수행됨 (_handleChatMessage 상단).\r\n this.emit('messageUpdated', { roomId, message });\r\n break;\r\n\r\n case 'MESSAGE_DELETED':\r\n this.emit('messageDeleted', { roomId, message });\r\n break;\r\n\r\n case 'REACTION_CHANGED':\r\n this.emit('reactionChanged', { roomId, message });\r\n break;\r\n\r\n case 'LINK_PREVIEW_ATTACHED':\r\n this.emit('linkPreviewAttached', { roomId, message });\r\n break;\r\n\r\n case 'MESSAGE_TRANSLATED':\r\n // 서버가 비동기 번역 완료 후 패치 — message.translations/sourceLang 채워져 옴.\r\n // 소비자는 messageId 로 기존 메시지에 머지 (linkPreviewAttached 와 동일 패턴).\r\n this.emit('messageTranslated', { roomId, message });\r\n break;\r\n\r\n default:\r\n // 미지의 eventType — 구버전 서버(eventType 없음)와 호환 유지.\r\n // 안전장치로 기존 동작(신규 메시지 알림) 수행.\r\n this.logger.warn(`Unknown eventType \"${message.eventType}\" — falling back to legacy behavior`);\r\n if (message.userId !== this.userId) {\r\n this.emit('newMessage', { roomId, message });\r\n if (this._activeRoomId === roomId && message.messageId) {\r\n this.markAsRead(roomId, message.messageId);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 채팅방 리스트 업데이트 이벤트 수신 처리.\r\n * @private\r\n * @param {Object} event - 서버 RoomListEvent\r\n * @param {string} event.eventType - RoomListEventType\r\n * @param {string} event.roomId\r\n * @param {string} event.actorId - 이벤트 발생 사용자\r\n * @param {number} [event.unreadCountDelta]\r\n * @param {string} [event.lastMessage]\r\n * @param {string} [event.lastMessageType]\r\n * @param {string} [event.lastMessageAt]\r\n * @param {string} event.timestamp\r\n */\r\n _handleRoomListEvent(event) {\r\n this.logger.debug('Room list event:', event);\r\n\r\n // MESSAGE_RECEIVED 의 messageId 기반 dedup — chat WebSocket 측과 별도 키 공간.\r\n // 서버가 messageId 를 안 내려준 구버전 페이로드면 dedup 통과 (backward compatible).\r\n if (event.eventType === RoomListEventType.MESSAGE_RECEIVED\r\n && event.messageId\r\n && this._shouldDedupMessage(this._seenRoomListMessageIdsByRoom, event.roomId, event.messageId)) {\r\n this.logger.debug(`Duplicate roomList MESSAGE_RECEIVED suppressed: room=${event.roomId}, messageId=${event.messageId}`);\r\n return;\r\n }\r\n\r\n // 1. 통합 이벤트 (모든 타입)\r\n this.emit('roomListUpdate', event);\r\n\r\n // 2. 타입별 이벤트\r\n switch (event.eventType) {\r\n case RoomListEventType.MESSAGE_RECEIVED:\r\n this.emit('roomListMessage', event);\r\n break;\r\n case RoomListEventType.MESSAGE_DELETED:\r\n this.emit('roomListMessageDeleted', event);\r\n break;\r\n case RoomListEventType.MESSAGE_UPDATED:\r\n this.emit('roomListMessageUpdated', event);\r\n break;\r\n case RoomListEventType.ROOM_CREATED:\r\n this.emit('roomListCreated', event);\r\n break;\r\n case RoomListEventType.ROOM_JOINED:\r\n this.emit('roomListJoined', event);\r\n break;\r\n case RoomListEventType.ROOM_LEFT:\r\n this.emit('roomListLeft', event);\r\n // 본인이 나간 경우 편의 이벤트 (리스트에서 제거해야 함)\r\n if (event.actorId === this.userId) {\r\n this.emit('roomListSelfLeft', event);\r\n }\r\n break;\r\n case RoomListEventType.ROOM_KICKED:\r\n this.emit('roomListKicked', event);\r\n // 본인이 추방당한 경우 편의 이벤트 — UI 에서 \"추방되었습니다\" 표시 + 리스트 제거\r\n if (this._isSelfInMembers(event)) {\r\n this.emit('roomListSelfKicked', event);\r\n }\r\n break;\r\n case RoomListEventType.ROOM_BANNED:\r\n this.emit('roomListBanned', event);\r\n // 본인이 영구 차단당한 경우 편의 이벤트 — \"영구 차단되었습니다\" 표시 + 리스트 제거\r\n if (this._isSelfInMembers(event)) {\r\n this.emit('roomListSelfBanned', event);\r\n }\r\n break;\r\n case RoomListEventType.ROOM_UPDATED:\r\n this.emit('roomListRoomUpdated', event);\r\n break;\r\n case RoomListEventType.MESSAGE_RETENTION_CLEANUP:\r\n // noinspection JSUnresolvedReference\r\n this.emit('retentionCleanup', {\r\n roomId: event.roomId,\r\n cutoffTime: event.cutoffTime\r\n });\r\n break;\r\n default:\r\n this.logger.warn('Unknown room list event type:', event.eventType);\r\n }\r\n }\r\n\r\n /**\r\n * 읽음 이벤트 수신 처리\r\n * @private\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} event - 읽음 이벤트 (단일 또는 배치)\r\n */\r\n _handleReadEvent(roomId, event) {\r\n this.logger.debug(`Read event in room ${roomId}:`, event);\r\n\r\n const resolvedRoomId = event.roomId || roomId;\r\n\r\n // 배치 이벤트인 경우 (방 입장 시 일괄 읽음 처리)\r\n if (event.events && Array.isArray(event.events)) {\r\n event.events.forEach(e => {\r\n this.emit('messageRead', {\r\n roomId: resolvedRoomId,\r\n messageId: e.messageId,\r\n userId: event.userId,\r\n remainingUnreadCount: e.remainingUnreadCount\r\n });\r\n });\r\n }\r\n // 단일 이벤트인 경우 (실시간 개별 읽음 처리)\r\n else {\r\n this.emit('messageRead', {\r\n roomId: resolvedRoomId,\r\n messageId: event.messageId,\r\n userId: event.userId,\r\n remainingUnreadCount: event.remainingUnreadCount\r\n });\r\n }\r\n }\r\n\r\n // ==================== 타이핑 표시 ====================\r\n\r\n /**\r\n * 타이핑 시작 알림.\r\n *\r\n * <p>호출할 때마다 내부 타이머가 리셋됩니다 (debounce).\r\n * 3초간 추가 호출이 없으면 자동으로 {@link #stopTyping} 이 호출됩니다.\r\n * 메시지 전송 ({@link #sendMessage}) 시에도 자동 중단됩니다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n *\r\n * @example\r\n * // input 이벤트에 연결\r\n * inputField.addEventListener('input', () => {\r\n * client.chat.startTyping('room-id');\r\n * });\r\n */\r\n startTyping(roomId) {\r\n if (!this.connectionManager.isConnected()) return;\r\n\r\n const existingTimer = this._typingTimers.get(roomId);\r\n\r\n // throttle: 이미 typing 상태면 stop 타이머만 리셋 (STOMP 재전송 안 함)\r\n if (existingTimer) {\r\n clearTimeout(existingTimer);\r\n } else {\r\n // 최초 또는 stop 이후 첫 호출에만 STOMP 전송\r\n this.connectionManager.send(WebSocketPaths.CHAT_TYPING, {\r\n roomId,\r\n typing: true\r\n });\r\n }\r\n\r\n // 3초 후 자동 stopTyping\r\n const timer = setTimeout(() => {\r\n this.stopTyping(roomId);\r\n }, 3000);\r\n\r\n this._typingTimers.set(roomId, timer);\r\n }\r\n\r\n /**\r\n * 타이핑 중단 알림.\r\n *\r\n * <p>직접 호출하거나, {@link #startTyping} 의 3초 타이머 만료 시,\r\n * 또는 메시지 전송 시 자동 호출됩니다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n */\r\n stopTyping(roomId) {\r\n this._clearTypingTimer(roomId);\r\n\r\n if (!this.connectionManager.isConnected()) return;\r\n\r\n this.connectionManager.send(WebSocketPaths.CHAT_TYPING, {\r\n roomId,\r\n typing: false\r\n });\r\n }\r\n\r\n /**\r\n * 이벤트 members 배열에 본인이 포함돼 있는지 확인.\r\n * <p>ROOM_KICKED / ROOM_BANNED 이벤트에서 \"내가 추방당했는가\" 판별용. actorId (추방한 방장) 가 아니라\r\n * members 배열(추방된 사용자) 을 보는 이유는, 같은 이벤트를 방장 본인도 수신하기 때문.</p>\r\n * @private\r\n */\r\n _isSelfInMembers(event) {\r\n return Array.isArray(event.members)\r\n && event.members.some(m => m && m.userId === this.userId);\r\n }\r\n\r\n /**\r\n * 타이핑 이벤트 수신 처리.\r\n * <p>본인 이벤트는 필터링하여 emit 하지 않음.</p>\r\n * @private\r\n */\r\n _handleTypingEvent(roomId, event) {\r\n // 본인 이벤트 필터 — USER 타이핑만 적용 (어시는 본인 userId 매칭 불가, generic marker 사용)\r\n if (event.senderType !== 'ASSISTANT' && event.userId === this.userId) return;\r\n\r\n // 어시 typing=true 수신 시 client-side timeout 시작 — 서버 측 typing=false 발행이 없으므로\r\n // (1) 어시 메시지 도착 (_handleChatMessage) 또는 (2) 본 timeout 만료 중 먼저 도달 시 typing=false emit.\r\n if (event.senderType === 'ASSISTANT' && event.typing === true) {\r\n this._startAssistantTypingTimer(roomId);\r\n }\r\n\r\n this.emit('typing', {\r\n roomId,\r\n userId: event.userId,\r\n userName: event.userName,\r\n typing: event.typing,\r\n senderType: event.senderType || 'USER' // 구버전 서버 호환 — senderType 없으면 USER\r\n });\r\n }\r\n\r\n /**\r\n * 어시스턴트 typing 자동 해제 타이머 시작/재시작.\r\n * 같은 방에 이미 timer 있으면 reset (새로운 어시 응답 라운드).\r\n * @private\r\n */\r\n _startAssistantTypingTimer(roomId) {\r\n this._clearAssistantTypingTimer(roomId);\r\n const timer = setTimeout(() => {\r\n this._assistantTypingTimers.delete(roomId);\r\n // timeout 만료 = LLM 지연 또는 서버 실패 — UI 가 영구 \"답변 준비중\" 에 갇히지 않도록 해제 emit.\r\n // userId / userName 은 generic marker 일관 (typing=true 발행과 매칭 — P1 fix).\r\n this.emit('typing', {\r\n roomId,\r\n userId: this._assistantTypingUserId,\r\n userName: this._assistantTypingUserName,\r\n typing: false,\r\n senderType: 'ASSISTANT'\r\n });\r\n this.logger.debug(`Assistant typing auto-cleared (timeout): room=${roomId}`);\r\n }, this._assistantTypingTimeoutMs);\r\n this._assistantTypingTimers.set(roomId, timer);\r\n }\r\n\r\n /**\r\n * 어시스턴트 typing 자동 해제 타이머 취소.\r\n * 어시 메시지 도착 시 즉시 호출 (timeout 도달 전 해제).\r\n * @private\r\n */\r\n _clearAssistantTypingTimer(roomId) {\r\n const timer = this._assistantTypingTimers.get(roomId);\r\n if (timer) {\r\n clearTimeout(timer);\r\n this._assistantTypingTimers.delete(roomId);\r\n }\r\n }\r\n\r\n /**\r\n * 타이핑 타이머 정리.\r\n * @private\r\n */\r\n _clearTypingTimer(roomId) {\r\n const timer = this._typingTimers.get(roomId);\r\n if (timer) {\r\n clearTimeout(timer);\r\n this._typingTimers.delete(roomId);\r\n }\r\n }\r\n\r\n // ==================== 유틸리티 ====================\r\n\r\n /**\r\n * 구독 중인 채팅방 목록\r\n * @returns {string[]}\r\n */\r\n getSubscribedRooms() {\r\n return Array.from(this.subscribedRooms.keys());\r\n }\r\n\r\n /**\r\n * 채팅방 구독 여부 확인\r\n * @param {string} roomId\r\n * @returns {boolean}\r\n */\r\n isSubscribed(roomId) {\r\n return this.subscribedRooms.has(roomId);\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리.\r\n *\r\n * <p>{@link #unsubscribeRoom} 에서 방별 타이머를 정리하지만, 구독 목록에 없는\r\n * roomId 의 debounce 타이머가 남아 있을 수 있어 Map 전체를 명시적으로 비운다.</p>\r\n */\r\n destroy() {\r\n this.unsubscribeAllRooms();\r\n if (this.roomListSubscribed) {\r\n this.unsubscribeRoomList();\r\n }\r\n\r\n this._typingTimers.forEach((timer) => clearTimeout(timer));\r\n this._typingTimers.clear();\r\n\r\n // 어시 typing 수신 타이머도 전체 cleanup — unsubscribeRoom 이 방별로 정리하지만 구독 목록에\r\n // 없는 roomId 의 타이머가 남아 있을 수 있어 명시 정리 (메모리 누수 차단).\r\n this._assistantTypingTimers.forEach((timer) => clearTimeout(timer));\r\n this._assistantTypingTimers.clear();\r\n\r\n // dedup bucket 전체 clear — unsubscribeRoom 이 방별로 정리하지만 구독 외 경로로 누적된 bucket\r\n // (예: 구독 안 한 방의 push/listener) 이 있을 수 있어 안전하게 명시 정리.\r\n this._seenChatMessageIdsByRoom.clear();\r\n this._seenRoomListMessageIdsByRoom.clear();\r\n\r\n this.removeAllListeners();\r\n this.logger.info('ChatClient destroyed');\r\n }\r\n}\r\n\r\nexport default ChatClient;\r\n","/**\r\n * MediaStreamManager\r\n * 로컬 미디어 스트림 관리 (카메라/마이크/화면공유)\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport { ErrorTypes, LogLevel } from '../constants.js';\r\n\r\nclass MediaStreamManager extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options = {}) {\r\n super();\r\n\r\n this.localStream = null;\r\n this.screenStream = null;\r\n this.videoEnabled = true;\r\n this.audioEnabled = true;\r\n\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'MediaStreamManager');\r\n this._deviceChangeHandler = null;\r\n }\r\n\r\n /**\r\n * 사용자 미디어 가져오기\r\n * @param {Object} constraints - 미디어 제약조건\r\n * @returns {Promise<MediaStream>}\r\n */\r\n async getUserMedia(constraints = { video: true, audio: true }) {\r\n try {\r\n this.localStream = await navigator.mediaDevices.getUserMedia(constraints);\r\n this.videoEnabled = constraints.video !== false;\r\n this.audioEnabled = constraints.audio !== false;\r\n\r\n this.emit('streamStarted', { stream: this.localStream });\r\n this.logger.info('User media stream started');\r\n return this.localStream;\r\n } catch (error) {\r\n this.logger.error('Failed to get user media:', error);\r\n this.emit('error', {\r\n type: ErrorTypes.MEDIA_ACCESS_DENIED,\r\n message: this._getMediaErrorMessage(error),\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 미디어 에러 메시지 변환\r\n * @private\r\n * @param {Error} error - 에러 객체\r\n * @returns {string} 변환된 에러 메시지\r\n */\r\n _getMediaErrorMessage(error) {\r\n switch (error.name) {\r\n case 'NotAllowedError':\r\n return 'Camera/Microphone permission denied';\r\n case 'NotFoundError':\r\n return 'Camera/Microphone not found';\r\n case 'NotReadableError':\r\n return 'Camera/Microphone is already in use';\r\n case 'OverconstrainedError':\r\n return 'Camera/Microphone constraints cannot be satisfied';\r\n case 'AbortError':\r\n return 'Media access aborted';\r\n default:\r\n return error.message || 'Unknown media error';\r\n }\r\n }\r\n\r\n /**\r\n * 화면 공유 가져오기\r\n * @param {Object} [options] - 화면 공유 옵션\r\n * @returns {Promise<MediaStream>}\r\n */\r\n async getDisplayMedia(options = { video: true, audio: false }) {\r\n try {\r\n this.screenStream = await navigator.mediaDevices.getDisplayMedia(options);\r\n\r\n // 화면 공유 종료 감지\r\n this.screenStream.getVideoTracks()[0].onended = () => {\r\n this.emit('screenShareEnded', {});\r\n this.screenStream = null;\r\n };\r\n\r\n this.emit('screenShareStarted', { stream: this.screenStream });\r\n this.logger.info('Screen share stream started');\r\n return this.screenStream;\r\n } catch (error) {\r\n this.logger.error('Failed to get display media:', error);\r\n this.emit('error', {\r\n type: ErrorTypes.SCREEN_SHARE_DENIED,\r\n message: error.name === 'NotAllowedError'\r\n ? 'Screen share permission denied'\r\n : error.message,\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 비디오 토글\r\n * @returns {boolean} 현재 상태\r\n */\r\n toggleVideo() {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return this.videoEnabled;\r\n }\r\n\r\n const videoTracks = this.localStream.getVideoTracks();\r\n if (videoTracks.length === 0) {\r\n this.logger.warn('No video track available');\r\n return this.videoEnabled;\r\n }\r\n\r\n this.videoEnabled = !this.videoEnabled;\r\n videoTracks.forEach(track => {\r\n track.enabled = this.videoEnabled;\r\n });\r\n\r\n this.emit('videoToggled', { enabled: this.videoEnabled });\r\n this.logger.debug(`Video toggled: ${this.videoEnabled}`);\r\n return this.videoEnabled;\r\n }\r\n\r\n /**\r\n * 오디오 토글\r\n * @returns {boolean} 현재 상태\r\n */\r\n toggleAudio() {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return this.audioEnabled;\r\n }\r\n\r\n const audioTracks = this.localStream.getAudioTracks();\r\n if (audioTracks.length === 0) {\r\n this.logger.warn('No audio track available');\r\n return this.audioEnabled;\r\n }\r\n\r\n this.audioEnabled = !this.audioEnabled;\r\n audioTracks.forEach(track => {\r\n track.enabled = this.audioEnabled;\r\n });\r\n\r\n this.emit('audioToggled', { enabled: this.audioEnabled });\r\n this.logger.debug(`Audio toggled: ${this.audioEnabled}`);\r\n return this.audioEnabled;\r\n }\r\n\r\n /**\r\n * 비디오 상태 설정\r\n * @param {boolean} enabled\r\n */\r\n setVideoEnabled(enabled) {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return;\r\n }\r\n\r\n const videoTracks = this.localStream.getVideoTracks();\r\n videoTracks.forEach(track => {\r\n track.enabled = enabled;\r\n });\r\n this.videoEnabled = enabled;\r\n this.emit('videoToggled', { enabled });\r\n }\r\n\r\n /**\r\n * 오디오 상태 설정\r\n * @param {boolean} enabled\r\n */\r\n setAudioEnabled(enabled) {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return;\r\n }\r\n\r\n const audioTracks = this.localStream.getAudioTracks();\r\n audioTracks.forEach(track => {\r\n track.enabled = enabled;\r\n });\r\n this.audioEnabled = enabled;\r\n this.emit('audioToggled', { enabled });\r\n }\r\n\r\n /**\r\n * 로컬 스트림 가져오기\r\n * @returns {MediaStream|null}\r\n */\r\n getLocalStream() {\r\n return this.localStream;\r\n }\r\n\r\n /**\r\n * 화면 공유 스트림 가져오기\r\n * @returns {MediaStream|null}\r\n */\r\n getScreenStream() {\r\n return this.screenStream;\r\n }\r\n\r\n /**\r\n * 현재 비디오 상태\r\n * @returns {boolean}\r\n */\r\n isVideoEnabled() {\r\n return this.videoEnabled;\r\n }\r\n\r\n /**\r\n * 현재 오디오 상태\r\n * @returns {boolean}\r\n */\r\n isAudioEnabled() {\r\n return this.audioEnabled;\r\n }\r\n\r\n /**\r\n * 모든 트랙 중지\r\n */\r\n stopAll() {\r\n if (this.localStream) {\r\n this.localStream.getTracks().forEach(track => track.stop());\r\n this.localStream = null;\r\n this.emit('streamStopped', {});\r\n this.logger.info('All media tracks stopped');\r\n }\r\n\r\n if (this.screenStream) {\r\n this.screenStream.getTracks().forEach(track => track.stop());\r\n this.screenStream = null;\r\n }\r\n }\r\n\r\n /**\r\n * 특정 트랙 교체 (내부용 - switchDevice에서 호출)\r\n * @private\r\n * @param {MediaStreamTrack} oldTrack\r\n * @param {MediaStreamTrack} newTrack\r\n */\r\n replaceTrack(oldTrack, newTrack) {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return;\r\n }\r\n\r\n this.localStream.removeTrack(oldTrack);\r\n this.localStream.addTrack(newTrack);\r\n oldTrack.stop();\r\n\r\n this.emit('trackReplaced', { oldTrack, newTrack });\r\n this.logger.debug(`Track replaced: ${oldTrack.kind}`);\r\n }\r\n\r\n /**\r\n * 특정 종류의 트랙 가져오기\r\n * @param {string} kind - 'video' or 'audio'\r\n * @returns {MediaStreamTrack|null}\r\n */\r\n getTrack(kind) {\r\n if (!this.localStream) return null;\r\n\r\n const tracks = kind === 'video'\r\n ? this.localStream.getVideoTracks()\r\n : this.localStream.getAudioTracks();\r\n\r\n return tracks.length > 0 ? tracks[0] : null;\r\n }\r\n\r\n /**\r\n * 디바이스 목록 가져오기\r\n * @returns {Promise<Object>}\r\n */\r\n async getDevices() {\r\n try {\r\n const devices = await navigator.mediaDevices.enumerateDevices();\r\n\r\n return {\r\n videoInputs: devices.filter(d => d.kind === 'videoinput'),\r\n audioInputs: devices.filter(d => d.kind === 'audioinput'),\r\n audioOutputs: devices.filter(d => d.kind === 'audiooutput')\r\n };\r\n } catch (error) {\r\n this.logger.error('Failed to enumerate devices:', error);\r\n this.emit('error', {\r\n type: ErrorTypes.ENUMERATE_DEVICES_FAILED,\r\n message: error.message,\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 특정 디바이스로 전환\r\n * @param {string} deviceId - 디바이스 ID\r\n * @param {string} kind - 'video' or 'audio'\r\n * @returns {Promise<MediaStreamTrack>}\r\n * @throws {Error} 스트림이 없거나 디바이스 전환 실패 시\r\n */\r\n async switchDevice(deviceId, kind) {\r\n if (!this.localStream) {\r\n throw new Error('No local stream available');\r\n }\r\n\r\n try {\r\n const constraints = kind === 'video'\r\n ? { video: { deviceId: { exact: deviceId } }, audio: false }\r\n : { video: false, audio: { deviceId: { exact: deviceId } } };\r\n\r\n const newStream = await navigator.mediaDevices.getUserMedia(constraints);\r\n const newTrack = newStream.getTracks()[0];\r\n\r\n const oldTrack = kind === 'video'\r\n ? this.localStream.getVideoTracks()[0]\r\n : this.localStream.getAudioTracks()[0];\r\n\r\n if (oldTrack) {\r\n this.replaceTrack(oldTrack, newTrack);\r\n\r\n this.emit('deviceSwitched', {\r\n kind,\r\n deviceId,\r\n newTrack\r\n });\r\n\r\n this.logger.info(`${kind} device switched to ${deviceId}`);\r\n }\r\n\r\n return newTrack;\r\n\r\n } catch (error) {\r\n this.logger.error(`Failed to switch ${kind} device:`, error);\r\n this.emit('error', {\r\n type: ErrorTypes.DEVICE_SWITCH_FAILED,\r\n message: error.message,\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 디바이스 변경 감지 시작\r\n */\r\n startDeviceChangeDetection() {\r\n if (this._deviceChangeHandler) {\r\n return;\r\n }\r\n\r\n this._deviceChangeHandler = async () => {\r\n try {\r\n const devices = await this.getDevices();\r\n this.emit('deviceChange', devices);\r\n this.logger.debug('Device change detected');\r\n } catch (error) {\r\n this.logger.warn('Failed to get devices on change:', error);\r\n }\r\n };\r\n\r\n navigator.mediaDevices.addEventListener('devicechange', this._deviceChangeHandler);\r\n this.logger.debug('Device change detection started');\r\n }\r\n\r\n /**\r\n * 디바이스 변경 감지 중지\r\n */\r\n stopDeviceChangeDetection() {\r\n if (this._deviceChangeHandler) {\r\n navigator.mediaDevices.removeEventListener('devicechange', this._deviceChangeHandler);\r\n this._deviceChangeHandler = null;\r\n this.logger.debug('Device change detection stopped');\r\n }\r\n }\r\n\r\n /**\r\n * 스트림 정보\r\n * @returns {Object}\r\n */\r\n getStreamInfo() {\r\n if (!this.localStream) {\r\n return {\r\n hasStream: false,\r\n videoEnabled: this.videoEnabled,\r\n audioEnabled: this.audioEnabled,\r\n tracks: []\r\n };\r\n }\r\n\r\n return {\r\n hasStream: true,\r\n videoEnabled: this.videoEnabled,\r\n audioEnabled: this.audioEnabled,\r\n tracks: this.localStream.getTracks().map(track => ({\r\n kind: track.kind,\r\n id: track.id,\r\n label: track.label,\r\n enabled: track.enabled,\r\n readyState: track.readyState,\r\n muted: track.muted\r\n }))\r\n };\r\n }\r\n\r\n /**\r\n * 비디오 해상도 변경\r\n * @param {Object} constraints - { width, height, frameRate }\r\n */\r\n async applyVideoConstraints(constraints) {\r\n if (!this.localStream) {\r\n throw new Error('No local stream available');\r\n }\r\n\r\n const videoTrack = this.localStream.getVideoTracks()[0];\r\n if (!videoTrack) {\r\n throw new Error('No video track available');\r\n }\r\n\r\n try {\r\n await videoTrack.applyConstraints(constraints);\r\n this.emit('videoConstraintsApplied', { constraints });\r\n this.logger.info('Video constraints applied:', constraints);\r\n } catch (error) {\r\n this.logger.error('Failed to apply video constraints:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 현재 비디오 설정 가져오기\r\n * @returns {MediaTrackSettings|null}\r\n */\r\n getVideoSettings() {\r\n if (!this.localStream) return null;\r\n\r\n const videoTrack = this.localStream.getVideoTracks()[0];\r\n return videoTrack ? videoTrack.getSettings() : null;\r\n }\r\n\r\n /**\r\n * 현재 오디오 설정 가져오기\r\n * @returns {MediaTrackSettings|null}\r\n */\r\n getAudioSettings() {\r\n if (!this.localStream) return null;\r\n\r\n const audioTrack = this.localStream.getAudioTracks()[0];\r\n return audioTrack ? audioTrack.getSettings() : null;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리\r\n */\r\n destroy() {\r\n this.stopAll();\r\n this.stopDeviceChangeDetection();\r\n this.removeAllListeners();\r\n this.logger.info('MediaStreamManager destroyed');\r\n }\r\n}\r\n\r\nexport default MediaStreamManager;\r\n","/**\r\n * PeerConnectionManager\r\n * WebRTC Peer 연결 관리 (Perfect Negotiation 패턴)\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport { ErrorTypes, DefaultConfig, LogLevel } from '../constants.js';\r\n\r\nclass PeerConnectionManager extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {Object[]} [options.iceServers] - ICE 서버 설정\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options = {}) {\r\n super();\r\n\r\n this.iceServers = options.iceServers || DefaultConfig.iceServers;\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'PeerConnectionManager');\r\n\r\n // 피어 연결 맵: peerId -> { connection, polite, makingOffer, ignoreOffer }\r\n this.peers = new Map();\r\n\r\n // 로컬 스트림\r\n this.localStream = null;\r\n }\r\n\r\n /**\r\n * 로컬 스트림 설정\r\n * @param {MediaStream} stream\r\n */\r\n setLocalStream(stream) {\r\n this.localStream = stream;\r\n\r\n // 기존 연결에 트랙 추가\r\n this.peers.forEach((peer) => {\r\n this._addTracksToConnection(peer.connection);\r\n });\r\n }\r\n\r\n /**\r\n * ICE 서버 설정\r\n * @param {Object[]} servers\r\n */\r\n setIceServers(servers) {\r\n this.iceServers = servers;\r\n }\r\n\r\n /**\r\n * 피어 연결 생성\r\n * @param {string} peerId - 피어 ID\r\n * @param {boolean} polite - Polite 피어 여부 (Perfect Negotiation)\r\n * @param {Object} [options] - 추가 옵션\r\n * @param {boolean} [options.skipAutoNegotiation=false] - 자동 negotiation 건너뛰기\r\n * @returns {RTCPeerConnection}\r\n */\r\n createPeerConnection(peerId, polite = false, options = {}) {\r\n if (this.peers.has(peerId)) {\r\n this.logger.warn(`Peer connection already exists: ${peerId}`);\r\n return this.peers.get(peerId).connection;\r\n }\r\n\r\n const config = {\r\n iceServers: this.iceServers,\r\n iceCandidatePoolSize: 10\r\n };\r\n\r\n const connection = new RTCPeerConnection(config);\r\n\r\n const peerState = {\r\n connection,\r\n polite,\r\n makingOffer: false,\r\n ignoreOffer: false,\r\n isSettingRemoteAnswerPending: false,\r\n skipAutoNegotiation: options.skipAutoNegotiation || false\r\n };\r\n\r\n this.peers.set(peerId, peerState);\r\n\r\n // 이벤트 핸들러 설정\r\n this._setupConnectionHandlers(peerId, connection, peerState);\r\n\r\n // 로컬 트랙 추가\r\n if (this.localStream) {\r\n this._addTracksToConnection(connection);\r\n }\r\n\r\n this.logger.info(`Peer connection created: ${peerId} (polite: ${polite}, skipAutoNegotiation: ${peerState.skipAutoNegotiation})`);\r\n return connection;\r\n }\r\n\r\n /**\r\n * 연결 이벤트 핸들러 설정\r\n * @private\r\n * @param {string} peerId - 피어 ID\r\n * @param {RTCPeerConnection} connection - 피어 연결\r\n * @param {Object} peerState - 피어 상태 객체\r\n */\r\n _setupConnectionHandlers(peerId, connection, peerState) {\r\n // ICE Candidate 이벤트\r\n connection.onicecandidate = (event) => {\r\n if (event.candidate) {\r\n this.emit('iceCandidate', {\r\n peerId,\r\n candidate: event.candidate\r\n });\r\n }\r\n };\r\n\r\n // ICE 연결 상태 변경\r\n connection.oniceconnectionstatechange = () => {\r\n this.logger.debug(`ICE connection state (${peerId}): ${connection.iceConnectionState}`);\r\n\r\n this.emit('iceConnectionStateChange', {\r\n peerId,\r\n state: connection.iceConnectionState\r\n });\r\n\r\n if (connection.iceConnectionState === 'failed') {\r\n this.emit('error', {\r\n type: ErrorTypes.ICE_CONNECTION_FAILED,\r\n peerId,\r\n message: 'ICE connection failed'\r\n });\r\n }\r\n };\r\n\r\n // 연결 상태 변경\r\n connection.onconnectionstatechange = () => {\r\n this.logger.debug(`Connection state (${peerId}): ${connection.connectionState}`);\r\n\r\n this.emit('connectionStateChange', {\r\n peerId,\r\n state: connection.connectionState\r\n });\r\n\r\n if (connection.connectionState === 'connected') {\r\n this.emit('peerConnected', { peerId });\r\n } else if (connection.connectionState === 'disconnected' || connection.connectionState === 'failed') {\r\n this.emit('peerDisconnected', { peerId });\r\n }\r\n };\r\n\r\n // 협상 필요 (Perfect Negotiation)\r\n connection.onnegotiationneeded = async () => {\r\n // 1:1 통화에서 수동으로 offer를 생성할 때는 자동 negotiation 건너뛰기\r\n if (peerState.skipAutoNegotiation) {\r\n this.logger.debug(`Skipping auto negotiation for ${peerId} (manual offer will be sent)`);\r\n return;\r\n }\r\n\r\n try {\r\n peerState.makingOffer = true;\r\n await connection.setLocalDescription();\r\n\r\n this.emit('negotiationNeeded', {\r\n peerId,\r\n description: connection.localDescription\r\n });\r\n\r\n } catch (error) {\r\n this.logger.error(`Negotiation error (${peerId}):`, error);\r\n } finally {\r\n peerState.makingOffer = false;\r\n }\r\n };\r\n\r\n // 원격 트랙 수신\r\n connection.ontrack = (event) => {\r\n this.logger.info(`Remote track received from ${peerId}:`, event.track.kind);\r\n\r\n this.emit('remoteTrack', {\r\n peerId,\r\n track: event.track,\r\n streams: event.streams\r\n });\r\n };\r\n\r\n // 데이터 채널 수신\r\n connection.ondatachannel = (event) => {\r\n this.logger.info(`Data channel received from ${peerId}`);\r\n this.emit('dataChannel', {\r\n peerId,\r\n channel: event.channel\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * 연결에 트랙 추가\r\n * @private\r\n * @param {RTCPeerConnection} connection - 피어 연결\r\n */\r\n _addTracksToConnection(connection) {\r\n if (!this.localStream) return;\r\n\r\n const existingSenders = connection.getSenders();\r\n\r\n this.localStream.getTracks().forEach(track => {\r\n // 이미 추가된 트랙인지 확인\r\n const existingSender = existingSenders.find(sender =>\r\n sender.track && sender.track.kind === track.kind\r\n );\r\n\r\n if (!existingSender) {\r\n connection.addTrack(track, this.localStream);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Offer 생성 및 전송\r\n * @param {string} peerId\r\n * @returns {Promise<RTCSessionDescription>}\r\n */\r\n async createOffer(peerId) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n throw new Error(`Peer not found: ${peerId}`);\r\n }\r\n\r\n try {\r\n const offer = await peer.connection.createOffer();\r\n await peer.connection.setLocalDescription(offer);\r\n\r\n this.logger.debug(`Offer created for ${peerId}`);\r\n return peer.connection.localDescription;\r\n\r\n } catch (error) {\r\n this.logger.error(`Failed to create offer for ${peerId}:`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Answer 생성\r\n * @param {string} peerId\r\n * @returns {Promise<RTCSessionDescription>}\r\n */\r\n async createAnswer(peerId) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n throw new Error(`Peer not found: ${peerId}`);\r\n }\r\n\r\n try {\r\n const answer = await peer.connection.createAnswer();\r\n await peer.connection.setLocalDescription(answer);\r\n\r\n this.logger.debug(`Answer created for ${peerId}`);\r\n return peer.connection.localDescription;\r\n\r\n } catch (error) {\r\n this.logger.error(`Failed to create answer for ${peerId}:`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 원격 Description 설정 (Perfect Negotiation)\r\n * @param {string} peerId\r\n * @param {RTCSessionDescription} description\r\n */\r\n async handleRemoteDescription(peerId, description) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n throw new Error(`Peer not found: ${peerId}`);\r\n }\r\n\r\n const { connection, polite, makingOffer } = peer;\r\n\r\n // Perfect Negotiation: offer 충돌 감지\r\n const offerCollision = description.type === 'offer' &&\r\n (makingOffer || connection.signalingState !== 'stable');\r\n\r\n // Impolite는 충돌 시 상대 offer 무시\r\n peer.ignoreOffer = !polite && offerCollision;\r\n\r\n if (peer.ignoreOffer) {\r\n this.logger.debug(`Ignoring offer collision from ${peerId} (I am impolite)`);\r\n return;\r\n }\r\n\r\n try {\r\n // Perfect Negotiation: Polite가 충돌 시 자신의 offer를 roll back\r\n if (offerCollision && polite) {\r\n this.logger.debug(`Offer collision detected, rolling back local offer for ${peerId} (I am polite)`);\r\n await connection.setLocalDescription({ type: 'rollback' });\r\n }\r\n\r\n peer.isSettingRemoteAnswerPending = description.type === 'answer';\r\n await connection.setRemoteDescription(description);\r\n peer.isSettingRemoteAnswerPending = false;\r\n\r\n this.logger.debug(`Remote ${description.type} set for ${peerId}`);\r\n\r\n // Offer를 받았으면 Answer 생성\r\n if (description.type === 'offer') {\r\n const answer = await this.createAnswer(peerId);\r\n this.emit('answerCreated', { peerId, answer });\r\n }\r\n\r\n } catch (error) {\r\n this.logger.error(`Failed to set remote description for ${peerId}:`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * ICE Candidate 추가\r\n * @param {string} peerId\r\n * @param {RTCIceCandidate} candidate\r\n * @returns {Promise<boolean>} 추가 성공 여부 (false면 나중에 다시 시도 필요)\r\n */\r\n async addIceCandidate(peerId, candidate) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n this.logger.warn(`Peer not found for ICE candidate: ${peerId}`);\r\n return false;\r\n }\r\n\r\n // remoteDescription이 설정되지 않았으면 추가 불가\r\n if (!peer.connection.remoteDescription) {\r\n this.logger.debug(`Remote description not set yet for ${peerId}, ICE candidate queued`);\r\n return false;\r\n }\r\n\r\n try {\r\n await peer.connection.addIceCandidate(candidate);\r\n this.logger.debug(`ICE candidate added for ${peerId}`);\r\n return true;\r\n } catch (error) {\r\n if (!peer.ignoreOffer) {\r\n this.logger.error(`Failed to add ICE candidate for ${peerId}:`, error);\r\n }\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 특정 피어 연결 가져오기\r\n * @param {string} peerId\r\n * @returns {RTCPeerConnection|null}\r\n */\r\n getPeerConnection(peerId) {\r\n const peer = this.peers.get(peerId);\r\n return peer ? peer.connection : null;\r\n }\r\n\r\n /**\r\n * 모든 피어 ID 목록\r\n * @returns {string[]}\r\n */\r\n getPeerIds() {\r\n return Array.from(this.peers.keys());\r\n }\r\n\r\n /**\r\n * 피어 연결 종료\r\n * @param {string} peerId\r\n */\r\n closePeerConnection(peerId) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n return;\r\n }\r\n\r\n peer.connection.close();\r\n this.peers.delete(peerId);\r\n\r\n this.emit('peerClosed', { peerId });\r\n this.logger.info(`Peer connection closed: ${peerId}`);\r\n }\r\n\r\n /**\r\n * 모든 피어 연결 종료\r\n */\r\n closeAllPeerConnections() {\r\n this.peers.forEach((peer, peerId) => {\r\n peer.connection.close();\r\n this.logger.debug(`Peer connection closed: ${peerId}`);\r\n });\r\n this.peers.clear();\r\n this.emit('allPeersClosed', {});\r\n }\r\n\r\n /**\r\n * 트랙 교체\r\n * @param {MediaStreamTrack} oldTrack - 교체할 기존 트랙\r\n * @param {MediaStreamTrack} newTrack - 새 트랙\r\n * @returns {Promise<void>}\r\n */\r\n async replaceTrack(oldTrack, newTrack) {\r\n const promises = [];\r\n\r\n this.peers.forEach((peer, peerId) => {\r\n const sender = peer.connection.getSenders().find(s =>\r\n s.track && s.track.kind === oldTrack.kind\r\n );\r\n\r\n if (sender) {\r\n promises.push(\r\n sender.replaceTrack(newTrack)\r\n .then(() => this.logger.debug(`Track replaced for ${peerId}`))\r\n .catch(error => this.logger.error(`Failed to replace track for ${peerId}:`, error))\r\n );\r\n }\r\n });\r\n\r\n await Promise.all(promises);\r\n }\r\n\r\n /**\r\n * 연결 통계 가져오기\r\n * @param {string} peerId\r\n * @returns {Promise<RTCStatsReport|null>}\r\n */\r\n async getStats(peerId) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n return null;\r\n }\r\n\r\n return peer.connection.getStats();\r\n }\r\n\r\n /**\r\n * 모든 연결 상태 요약\r\n * @returns {Object}\r\n */\r\n getConnectionSummary() {\r\n const summary = {};\r\n\r\n this.peers.forEach((peer, peerId) => {\r\n summary[peerId] = {\r\n connectionState: peer.connection.connectionState,\r\n iceConnectionState: peer.connection.iceConnectionState,\r\n signalingState: peer.connection.signalingState,\r\n polite: peer.polite\r\n };\r\n });\r\n\r\n return summary;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리\r\n */\r\n destroy() {\r\n this.closeAllPeerConnections();\r\n this.localStream = null;\r\n this.removeAllListeners();\r\n this.logger.info('PeerConnectionManager destroyed');\r\n }\r\n}\r\n\r\nexport default PeerConnectionManager;\r\n","/**\r\n * WebRTCClient\r\n * WebRTC 통화 기능 클라이언트\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport MediaStreamManager from './MediaStreamManager.js';\r\nimport PeerConnectionManager from './PeerConnectionManager.js';\r\nimport { WebSocketPaths, SignalTypes, ErrorTypes, DefaultConfig, LogLevel } from '../constants.js';\r\n\r\nclass WebRTCClient extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {Object} options.connectionManager - ConnectionManager 인스턴스\r\n * @param {Object} options.apiClient - ApiClient 인스턴스\r\n * @param {string} options.userId - 사용자 ID\r\n * @param {Object[]} [options.iceServers] - ICE 서버 설정\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n super();\r\n\r\n this.connectionManager = options.connectionManager;\r\n this.apiClient = options.apiClient;\r\n this.userId = options.userId;\r\n this.logLevel = options.logLevel || LogLevel.WARN;\r\n\r\n this.logger = new Logger(this.logLevel, 'WebRTCClient');\r\n\r\n // 미디어 및 피어 연결 매니저\r\n this.mediaManager = new MediaStreamManager({ logLevel: this.logLevel });\r\n this.peerManager = new PeerConnectionManager({\r\n iceServers: options.iceServers || DefaultConfig.iceServers,\r\n logLevel: this.logLevel\r\n });\r\n\r\n // 현재 통화 상태\r\n this.currentRoom = null;\r\n this.isGroupCall = false;\r\n this.participants = new Map();\r\n\r\n // ICE 서버 초기화 상태\r\n this._iceServersFetched = false;\r\n\r\n // 1:1 통화 대기 상태 (CALL_REQUEST 전송 후 CALL_ACCEPT 대기)\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n\r\n // ICE candidate 큐 (peer connection 생성 전에 도착한 candidate 저장)\r\n this._pendingIceCandidates = new Map();\r\n\r\n // 이벤트 연결\r\n this._setupEventHandlers();\r\n }\r\n\r\n /**\r\n * ICE 서버 설정 초기화 (TURN credentials 가져오기)\r\n * 통화 시작 전 호출하면 더 빠른 연결 가능\r\n * @returns {Promise<void>}\r\n */\r\n async initializeIceServers() {\r\n if (this._iceServersFetched) {\r\n return;\r\n }\r\n\r\n try {\r\n const data = await this.getTurnCredentials();\r\n if (data && data.iceServers) {\r\n this.peerManager.setIceServers(data.iceServers);\r\n this._iceServersFetched = true;\r\n this.logger.info('ICE servers fetched successfully');\r\n }\r\n } catch (error) {\r\n this.logger.warn('Failed to fetch TURN credentials, using fallback ICE servers:', error.message);\r\n this._setFallbackIceServers();\r\n }\r\n }\r\n\r\n /**\r\n * 기본 ICE 서버 설정 (Fallback)\r\n * @private\r\n */\r\n _setFallbackIceServers() {\r\n this.peerManager.setIceServers([\r\n { urls: 'stun:stun.l.google.com:19302' },\r\n { urls: 'stun:stun1.l.google.com:19302' }\r\n ]);\r\n }\r\n\r\n /**\r\n * 이벤트 핸들러 설정\r\n * @private\r\n */\r\n _setupEventHandlers() {\r\n // 미디어 이벤트\r\n this.mediaManager.on('streamStarted', (data) => this.emit('localStreamStarted', data));\r\n this.mediaManager.on('streamStopped', (data) => this.emit('localStreamStopped', data));\r\n this.mediaManager.on('videoToggled', () => this._sendMediaState());\r\n this.mediaManager.on('audioToggled', () => this._sendMediaState());\r\n this.mediaManager.on('screenShareStarted', (data) => this.emit('screenShareStarted', data));\r\n this.mediaManager.on('screenShareEnded', (data) => this.emit('screenShareEnded', data));\r\n this.mediaManager.on('error', (error) => this.emit('error', error));\r\n\r\n // 피어 연결 이벤트\r\n this.peerManager.on('remoteTrack', (data) => this.emit('remoteTrack', data));\r\n this.peerManager.on('peerConnected', (data) => this.emit('peerConnected', data));\r\n this.peerManager.on('peerDisconnected', (data) => this.emit('peerDisconnected', data));\r\n this.peerManager.on('peerClosed', (data) => this.emit('peerClosed', data));\r\n this.peerManager.on('error', (error) => this.emit('error', error));\r\n\r\n // ICE Candidate 전송\r\n this.peerManager.on('iceCandidate', ({ peerId, candidate }) => {\r\n this._sendSignal({\r\n type: SignalTypes.ICE_CANDIDATE,\r\n receiverId: peerId,\r\n data: { candidate }\r\n });\r\n });\r\n\r\n // Negotiation (Offer 전송)\r\n this.peerManager.on('negotiationNeeded', ({ peerId, description }) => {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_OFFER,\r\n receiverId: peerId,\r\n data: { sdp: description }\r\n });\r\n });\r\n\r\n // Answer 전송\r\n this.peerManager.on('answerCreated', ({ peerId, answer }) => {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_ANSWER,\r\n receiverId: peerId,\r\n data: { sdp: answer }\r\n });\r\n });\r\n }\r\n\r\n // ==================== REST API ====================\r\n\r\n /**\r\n * TURN 서버 인증 정보 가져오기\r\n * @returns {Promise<Object>}\r\n */\r\n async getTurnCredentials() {\r\n const response = await this.apiClient.get('/api/v1/webrtc/turn-credentials');\r\n return response.data;\r\n }\r\n\r\n /**\r\n * 통화방 생성\r\n * @public\r\n * @param {Object} data\r\n * @param {string} data.roomName - 방 이름\r\n * @param {boolean} data.isGroup - 그룹 통화 여부\r\n * @param {string} [data.chatRoomId] - 연결할 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async createCallRoom(data) {\r\n return this.apiClient.post('/api/v1/webrtc/rooms', data);\r\n }\r\n\r\n /**\r\n * 통화방 정보 조회\r\n * @public\r\n * @param {string} roomId - 통화방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async getCallRoom(roomId) {\r\n return this.apiClient.get(`/api/v1/webrtc/rooms/${roomId}`);\r\n }\r\n\r\n /**\r\n * 통화방 참여\r\n * @public\r\n * @param {string} roomId - 통화방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async joinCallRoomApi(roomId) {\r\n return this.apiClient.post(`/api/v1/webrtc/rooms/${roomId}/join`);\r\n }\r\n\r\n /**\r\n * 통화방 나가기\r\n * @public\r\n * @param {string} roomId - 통화방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async leaveCallRoomApi(roomId) {\r\n return this.apiClient.delete(`/api/v1/webrtc/rooms/${roomId}/leave`);\r\n }\r\n\r\n // ==================== 통화 수신 대기 ====================\r\n\r\n /**\r\n * 통화 수신 대기 활성화\r\n * connect() 후 자동 호출되지만, 수동으로도 호출 가능\r\n * 개인 WebRTC 채널(/user/queue/webrtc)을 구독하여 초대 알림 수신\r\n * @public\r\n * @returns {Promise<void>}\r\n */\r\n async enableIncomingCalls() {\r\n if (this._incomingCallsEnabled) {\r\n this.logger.debug('Incoming calls already enabled');\r\n return;\r\n }\r\n\r\n await this._subscribeToDirectSignaling();\r\n this._incomingCallsEnabled = true;\r\n this.logger.info('Incoming calls enabled - now listening for call invitations');\r\n }\r\n\r\n /**\r\n * 통화 수신 대기 비활성화\r\n * @public\r\n */\r\n disableIncomingCalls() {\r\n if (!this._incomingCallsEnabled) {\r\n return;\r\n }\r\n\r\n const directDestination = WebSocketPaths.getWebRTCUserDestination();\r\n this.connectionManager.unsubscribe(directDestination);\r\n this._incomingCallsEnabled = false;\r\n this.logger.info('Incoming calls disabled');\r\n }\r\n\r\n /**\r\n * 통화 수신 대기 상태 확인\r\n * @public\r\n * @returns {boolean}\r\n */\r\n isIncomingCallsEnabled() {\r\n return this._incomingCallsEnabled || false;\r\n }\r\n\r\n // ==================== 통화 기능 ====================\r\n\r\n /**\r\n * 통화 시작 (미디어 및 구독)\r\n * @param {Object} options\r\n * @param {string} options.roomId - 통화방 ID\r\n * @param {boolean} [options.isGroup=false] - 그룹 통화 여부\r\n * @param {Object} [options.mediaConstraints] - 미디어 제약조건\r\n * @returns {Promise<MediaStream>}\r\n */\r\n async startCall(options) {\r\n const { roomId, isGroup = false, mediaConstraints = { video: true, audio: true } } = options;\r\n\r\n try {\r\n // TURN 서버 설정 초기화\r\n await this.initializeIceServers();\r\n\r\n // 로컬 미디어 획득\r\n const localStream = await this.mediaManager.getUserMedia(mediaConstraints);\r\n this.peerManager.setLocalStream(localStream);\r\n\r\n // 현재 방 설정\r\n this.currentRoom = roomId;\r\n this.isGroupCall = isGroup;\r\n\r\n // WebSocket 구독\r\n await this._subscribeToSignaling(roomId, isGroup);\r\n\r\n // 입장 시그널 전송\r\n this._sendSignal({\r\n type: SignalTypes.JOIN_ROOM,\r\n roomId\r\n });\r\n\r\n this.logger.info(`Call started in room: ${roomId}`);\r\n this.emit('callStarted', { roomId, isGroup, localStream });\r\n\r\n return localStream;\r\n\r\n } catch (error) {\r\n this.logger.error('Failed to start call:', error);\r\n this.emit('error', {\r\n type: ErrorTypes.PEER_CONNECTION_FAILED,\r\n message: error.message,\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 1:1 통화 요청\r\n * @param {string} targetUserId - 상대방 사용자 ID\r\n * @param {Object} [mediaConstraints] - 미디어 제약조건\r\n */\r\n async callUser(targetUserId, mediaConstraints = { video: true, audio: true }) {\r\n try {\r\n // TURN 서버 설정 초기화\r\n await this.initializeIceServers();\r\n\r\n // 로컬 미디어 획득\r\n const localStream = await this.mediaManager.getUserMedia(mediaConstraints);\r\n this.peerManager.setLocalStream(localStream);\r\n\r\n // 1:1 통화 구독\r\n await this._subscribeToDirectSignaling();\r\n\r\n // CALL_ACCEPT 대기 상태 설정\r\n // PeerConnection은 CALL_ACCEPT를 받은 후에 생성하여 자동 negotiation 방지\r\n this._waitingForCallAccept = true;\r\n this._pendingCallTarget = targetUserId;\r\n\r\n // 통화 요청 시그널\r\n this._sendSignal({\r\n type: SignalTypes.CALL_REQUEST,\r\n receiverId: targetUserId\r\n });\r\n\r\n this.currentRoom = null;\r\n this.isGroupCall = false;\r\n\r\n this.emit('callRequested', { targetUserId, localStream });\r\n\r\n } catch (error) {\r\n this.logger.error('Failed to call user:', error);\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 통화 수락\r\n * @param {string} callerId - 발신자 ID\r\n * @param {Object} [mediaConstraints] - 미디어 제약조건\r\n */\r\n async acceptCall(callerId, mediaConstraints = { video: true, audio: true }) {\r\n try {\r\n // TURN 서버 설정 초기화\r\n await this.initializeIceServers();\r\n\r\n // 로컬 미디어 획득 (수신자도 미디어 필요)\r\n const localStream = await this.mediaManager.getUserMedia(mediaConstraints);\r\n this.peerManager.setLocalStream(localStream);\r\n\r\n // 피어 연결 생성 (Polite - 수신자)\r\n // skipAutoNegotiation: true - 수신자는 offer를 받아서 answer를 보내므로 자동 negotiation 불필요\r\n this.peerManager.createPeerConnection(callerId, true, { skipAutoNegotiation: true });\r\n\r\n // 수락 시그널\r\n this._sendSignal({\r\n type: SignalTypes.CALL_ACCEPT,\r\n receiverId: callerId\r\n });\r\n\r\n this.emit('callAccepted', { callerId });\r\n\r\n } catch (error) {\r\n this.logger.error('Failed to accept call:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 통화 거절\r\n * @param {string} callerId - 발신자 ID\r\n */\r\n rejectCall(callerId) {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_REJECT,\r\n receiverId: callerId\r\n });\r\n\r\n this.emit('callRejected', { callerId });\r\n }\r\n\r\n /**\r\n * 통화 취소 (발신자가 수신자 응답 전 취소)\r\n */\r\n cancelCall() {\r\n if (this._waitingForCallAccept && this._pendingCallTarget) {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_CANCEL,\r\n receiverId: this._pendingCallTarget\r\n });\r\n\r\n this.emit('callCancelled', { targetUserId: this._pendingCallTarget });\r\n\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n\r\n // 미디어 정리\r\n this.mediaManager.stopAll();\r\n }\r\n }\r\n\r\n /**\r\n * 통화 종료\r\n */\r\n endCall() {\r\n // 대기 중인 통화가 있으면 취소 처리\r\n if (this._waitingForCallAccept && this._pendingCallTarget) {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_CANCEL,\r\n receiverId: this._pendingCallTarget\r\n });\r\n }\r\n\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n\r\n if (this.currentRoom) {\r\n // 퇴장 시그널 전송\r\n this._sendSignal({\r\n type: SignalTypes.LEAVE_ROOM,\r\n roomId: this.currentRoom\r\n });\r\n\r\n // 구독 해제\r\n this._unsubscribeFromSignaling();\r\n }\r\n\r\n // 모든 피어에게 종료 시그널\r\n this.peerManager.getPeerIds().forEach(peerId => {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_END,\r\n receiverId: peerId\r\n });\r\n });\r\n\r\n // 정리\r\n this.peerManager.closeAllPeerConnections();\r\n this.mediaManager.stopAll();\r\n this.participants.clear();\r\n this._pendingIceCandidates.clear();\r\n\r\n const roomId = this.currentRoom;\r\n this.currentRoom = null;\r\n this.isGroupCall = false;\r\n\r\n this.emit('callEnded', { roomId });\r\n this.logger.info('Call ended');\r\n }\r\n\r\n // ==================== 미디어 제어 ====================\r\n\r\n /**\r\n * 비디오 토글\r\n * @returns {boolean}\r\n */\r\n toggleVideo() {\r\n return this.mediaManager.toggleVideo();\r\n }\r\n\r\n /**\r\n * 오디오 토글\r\n * @returns {boolean}\r\n */\r\n toggleAudio() {\r\n return this.mediaManager.toggleAudio();\r\n }\r\n\r\n /**\r\n * 화면 공유 시작\r\n * @returns {Promise<MediaStream>}\r\n */\r\n async startScreenShare() {\r\n const screenStream = await this.mediaManager.getDisplayMedia();\r\n\r\n // 피어들에게 화면 공유 트랙 전송\r\n const videoTrack = screenStream.getVideoTracks()[0];\r\n const localVideoTrack = this.mediaManager.getTrack('video');\r\n\r\n if (localVideoTrack && videoTrack) {\r\n await this.peerManager.replaceTrack(localVideoTrack, videoTrack);\r\n }\r\n\r\n // 화면 공유 종료 시 원복 — 복원 시점의 현재 카메라 트랙 사용 (공유 중 switchDevice 대응)\r\n videoTrack.onended = async () => {\r\n const currentVideoTrack = this.mediaManager.getTrack('video');\r\n if (currentVideoTrack) {\r\n await this.peerManager.replaceTrack(videoTrack, currentVideoTrack);\r\n }\r\n this.emit('screenShareEnded', {});\r\n };\r\n\r\n return screenStream;\r\n }\r\n\r\n /**\r\n * 화면 공유 중지\r\n */\r\n stopScreenShare() {\r\n const screenStream = this.mediaManager.getScreenStream();\r\n if (screenStream) {\r\n screenStream.getTracks().forEach(track => track.stop());\r\n }\r\n }\r\n\r\n /**\r\n * 로컬 스트림 가져오기\r\n * @public\r\n * @returns {MediaStream|null}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n getLocalStream() {\r\n return this.mediaManager.getLocalStream();\r\n }\r\n\r\n /**\r\n * 디바이스 목록 가져오기\r\n * @returns {Promise<Object>}\r\n */\r\n async getDevices() {\r\n return this.mediaManager.getDevices();\r\n }\r\n\r\n /**\r\n * 디바이스 전환\r\n * @param {string} deviceId\r\n * @param {string} kind - 'video' or 'audio'\r\n */\r\n async switchDevice(deviceId, kind) {\r\n // switchDevice가 localStream을 교체하므로, 교체 전 기존 트랙을 먼저 저장\r\n const oldTrack = this.mediaManager.getTrack(kind);\r\n const newTrack = await this.mediaManager.switchDevice(deviceId, kind);\r\n\r\n // 피어들에게 트랙 교체\r\n if (oldTrack) {\r\n await this.peerManager.replaceTrack(oldTrack, newTrack);\r\n }\r\n\r\n return newTrack;\r\n }\r\n\r\n /**\r\n * 비디오 상태 직접 설정\r\n * @public\r\n * @param {boolean} enabled\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n setVideoEnabled(enabled) {\r\n this.mediaManager.setVideoEnabled(enabled);\r\n this._sendMediaState();\r\n }\r\n\r\n /**\r\n * 오디오 상태 직접 설정\r\n * @public\r\n * @param {boolean} enabled\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n setAudioEnabled(enabled) {\r\n this.mediaManager.setAudioEnabled(enabled);\r\n this._sendMediaState();\r\n }\r\n\r\n /**\r\n * 디바이스 변경 감지 시작\r\n * @public\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n startDeviceChangeDetection() {\r\n this.mediaManager.startDeviceChangeDetection();\r\n this.mediaManager.on('deviceChange', (devices) => {\r\n this.emit('deviceChange', devices);\r\n });\r\n }\r\n\r\n /**\r\n * 디바이스 변경 감지 중지\r\n * @public\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n stopDeviceChangeDetection() {\r\n this.mediaManager.stopDeviceChangeDetection();\r\n }\r\n\r\n /**\r\n * 비디오 해상도/프레임레이트 변경\r\n * @public\r\n * @param {Object} constraints - { width, height, frameRate }\r\n * @returns {Promise<void>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async applyVideoConstraints(constraints) {\r\n return this.mediaManager.applyVideoConstraints(constraints);\r\n }\r\n\r\n /**\r\n * 현재 비디오 설정 가져오기\r\n * @public\r\n * @returns {MediaTrackSettings|null}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n getVideoSettings() {\r\n return this.mediaManager.getVideoSettings();\r\n }\r\n\r\n /**\r\n * 현재 오디오 설정 가져오기\r\n * @public\r\n * @returns {MediaTrackSettings|null}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n getAudioSettings() {\r\n return this.mediaManager.getAudioSettings();\r\n }\r\n\r\n // ==================== 시그널링 ====================\r\n\r\n /**\r\n * 시그널링 구독\r\n * @private\r\n */\r\n async _subscribeToSignaling(roomId, isGroup) {\r\n if (isGroup) {\r\n // 그룹 통화: /topic/webrtc/{roomId}\r\n const destination = WebSocketPaths.getWebRTCDestination(roomId);\r\n await this.connectionManager.subscribe(destination, (message) => {\r\n this._handleSignal(message);\r\n });\r\n }\r\n\r\n // 1:1 또는 그룹 모두에서 개인 큐 구독\r\n await this._subscribeToDirectSignaling();\r\n }\r\n\r\n /**\r\n * 1:1 시그널링 구독\r\n * @private\r\n */\r\n async _subscribeToDirectSignaling() {\r\n const destination = WebSocketPaths.getWebRTCUserDestination();\r\n await this.connectionManager.subscribe(destination, (message) => {\r\n this._handleSignal(message);\r\n });\r\n }\r\n\r\n /**\r\n * 시그널링 구독 해제\r\n * @private\r\n */\r\n _unsubscribeFromSignaling() {\r\n if (this.currentRoom && this.isGroupCall) {\r\n const destination = WebSocketPaths.getWebRTCDestination(this.currentRoom);\r\n this.connectionManager.unsubscribe(destination);\r\n }\r\n\r\n // 수신 대기가 활성화된 경우 direct 채널 구독 유지 (그룹 통화 종료 후에도 수신 가능)\r\n if (!this._incomingCallsEnabled) {\r\n const directDestination = WebSocketPaths.getWebRTCUserDestination();\r\n this.connectionManager.unsubscribe(directDestination);\r\n }\r\n }\r\n\r\n /**\r\n * 시그널 전송\r\n * @private\r\n */\r\n _sendSignal(signal) {\r\n const payload = {\r\n type: signal.type,\r\n roomId: signal.roomId || this.currentRoom,\r\n receiverId: signal.receiverId,\r\n data: signal.data\r\n };\r\n\r\n this.connectionManager.send(WebSocketPaths.WEBRTC_SIGNAL, payload);\r\n this.logger.debug('Signal sent:', payload);\r\n }\r\n\r\n /**\r\n * 미디어 상태 전송\r\n * @private\r\n */\r\n _sendMediaState() {\r\n if (!this.currentRoom && this.peerManager.getPeerIds().length === 0) {\r\n return;\r\n }\r\n\r\n const mediaState = {\r\n videoEnabled: this.mediaManager.isVideoEnabled(),\r\n audioEnabled: this.mediaManager.isAudioEnabled()\r\n };\r\n\r\n this._sendSignal({\r\n type: SignalTypes.VIDEO_STATE_CHANGED,\r\n data: mediaState\r\n });\r\n\r\n this.emit('mediaStateChanged', mediaState);\r\n }\r\n\r\n /**\r\n * 시그널 처리\r\n * @private\r\n */\r\n async _handleSignal(message) {\r\n const { type, senderId, data } = message;\r\n\r\n // 자신의 시그널 무시\r\n if (senderId === this.userId) {\r\n return;\r\n }\r\n\r\n this.logger.debug(`Signal received: ${type} from ${senderId}`);\r\n\r\n switch (type) {\r\n case SignalTypes.JOIN_ROOM:\r\n case SignalTypes.PEER_JOINED:\r\n await this._handleUserJoined(senderId);\r\n break;\r\n\r\n case SignalTypes.LEAVE_ROOM:\r\n case SignalTypes.PEER_LEFT:\r\n this._handleUserLeft(senderId);\r\n break;\r\n\r\n case SignalTypes.CALL_OFFER:\r\n await this._handleOffer(senderId, data.sdp);\r\n break;\r\n\r\n case SignalTypes.CALL_ANSWER:\r\n await this._handleAnswer(senderId, data.sdp);\r\n break;\r\n\r\n case SignalTypes.ICE_CANDIDATE:\r\n await this._handleIceCandidate(senderId, data.candidate);\r\n break;\r\n\r\n case SignalTypes.VIDEO_STATE_CHANGED:\r\n case SignalTypes.AUDIO_STATE_CHANGED:\r\n this._handleMediaState(senderId, data);\r\n break;\r\n\r\n case SignalTypes.CALL_REQUEST:\r\n // 통화 중이거나 통화 요청 대기 중이면 BUSY 응답\r\n if (this.isInCall() || this._waitingForCallAccept) {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_BUSY,\r\n receiverId: senderId\r\n });\r\n this.emit('incomingCallWhileBusy', { callerId: senderId });\r\n } else {\r\n this.emit('incomingCall', { callerId: senderId });\r\n }\r\n break;\r\n\r\n case SignalTypes.CALL_BUSY:\r\n // 상대방이 통화 중\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n this.mediaManager.stopAll();\r\n this.emit('callBusy', { userId: senderId });\r\n break;\r\n\r\n case SignalTypes.CALL_INVITATION:\r\n // 그룹 통화 초대 (createCallRoom으로 초대받은 경우)\r\n this.emit('callInvitation', {\r\n callRoomId: data?.callRoomId || message.roomId,\r\n title: data?.title,\r\n hostUserId: data?.hostUserId || senderId,\r\n maxParticipants: data?.maxParticipants,\r\n createdAt: data?.createdAt\r\n });\r\n break;\r\n\r\n case SignalTypes.CALL_ACCEPT:\r\n await this._handleCallAccepted(senderId);\r\n break;\r\n\r\n case SignalTypes.CALL_REJECT:\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n // 발신자 미디어 정리 (거절당했으므로 통화 불가)\r\n this.mediaManager.stopAll();\r\n this.emit('callRejected', { userId: senderId });\r\n break;\r\n\r\n case SignalTypes.CALL_CANCEL:\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n this.emit('callCancelled', { userId: senderId });\r\n break;\r\n\r\n case SignalTypes.CALL_END:\r\n this._handleCallEnded(senderId);\r\n break;\r\n\r\n default:\r\n this.logger.warn(`Unknown signal type: ${type}`);\r\n }\r\n }\r\n\r\n /**\r\n * 사용자 입장 처리\r\n * @private\r\n */\r\n async _handleUserJoined(userId) {\r\n this.participants.set(userId, { joinedAt: new Date() });\r\n\r\n // userId 비교로 Polite/Impolite 결정 (작은 쪽이 Polite)\r\n // Perfect Negotiation에서 충돌 시 한 쪽만 양보하도록 보장\r\n const polite = this.userId < userId;\r\n this.peerManager.createPeerConnection(userId, polite);\r\n\r\n this.emit('userJoined', { userId });\r\n this.logger.info(`User joined: ${userId} (I am ${polite ? 'polite' : 'impolite'})`);\r\n }\r\n\r\n /**\r\n * 사용자 퇴장 처리\r\n * @private\r\n */\r\n _handleUserLeft(userId) {\r\n this.participants.delete(userId);\r\n this.peerManager.closePeerConnection(userId);\r\n\r\n this.emit('userLeft', { userId });\r\n this.logger.info(`User left: ${userId}`);\r\n }\r\n\r\n /**\r\n * Offer 처리\r\n * @private\r\n */\r\n async _handleOffer(senderId, sdp) {\r\n // 피어 연결이 없으면 생성\r\n if (!this.peerManager.getPeerConnection(senderId)) {\r\n // userId 비교로 Polite/Impolite 결정\r\n const polite = this.userId < senderId;\r\n // skipAutoNegotiation: true - offer를 먼저 처리한 후 answer 전송\r\n // 트랙 추가 시 자동 negotiation이 발생하면 offer 충돌 발생\r\n this.peerManager.createPeerConnection(senderId, polite, { skipAutoNegotiation: true });\r\n }\r\n\r\n await this.peerManager.handleRemoteDescription(senderId, sdp);\r\n\r\n // remoteDescription 설정 후 대기 중인 ICE candidates 처리\r\n await this._processPendingIceCandidates(senderId);\r\n }\r\n\r\n /**\r\n * Answer 처리\r\n * @private\r\n */\r\n async _handleAnswer(senderId, sdp) {\r\n await this.peerManager.handleRemoteDescription(senderId, sdp);\r\n // remoteDescription 설정 후 대기 중인 ICE candidates 처리\r\n await this._processPendingIceCandidates(senderId);\r\n }\r\n\r\n /**\r\n * ICE Candidate 처리\r\n * @private\r\n */\r\n async _handleIceCandidate(senderId, candidate) {\r\n // peer connection이 아직 없으면 큐에 저장\r\n if (!this.peerManager.getPeerConnection(senderId)) {\r\n this._queueIceCandidate(senderId, candidate);\r\n return;\r\n }\r\n\r\n // addIceCandidate가 false 반환하면 (remoteDescription 미설정) 큐에 저장\r\n const added = await this.peerManager.addIceCandidate(senderId, new RTCIceCandidate(candidate));\r\n if (!added) {\r\n this._queueIceCandidate(senderId, candidate);\r\n }\r\n }\r\n\r\n /**\r\n * ICE candidate를 큐에 저장\r\n * @private\r\n */\r\n _queueIceCandidate(peerId, candidate) {\r\n if (!this._pendingIceCandidates.has(peerId)) {\r\n this._pendingIceCandidates.set(peerId, []);\r\n }\r\n this._pendingIceCandidates.get(peerId).push(candidate);\r\n this.logger.debug(`ICE candidate queued for ${peerId}`);\r\n }\r\n\r\n /**\r\n * 대기 중인 ICE candidate 처리\r\n * @private\r\n */\r\n async _processPendingIceCandidates(peerId) {\r\n const candidates = this._pendingIceCandidates.get(peerId);\r\n if (!candidates || candidates.length === 0) {\r\n return;\r\n }\r\n\r\n this.logger.debug(`Processing ${candidates.length} pending ICE candidates for ${peerId}`);\r\n\r\n for (const candidate of candidates) {\r\n await this.peerManager.addIceCandidate(peerId, new RTCIceCandidate(candidate));\r\n }\r\n\r\n this._pendingIceCandidates.delete(peerId);\r\n }\r\n\r\n /**\r\n * 미디어 상태 처리\r\n * @private\r\n */\r\n _handleMediaState(userId, state) {\r\n this.emit('participantMediaState', {\r\n userId,\r\n videoEnabled: state.videoEnabled,\r\n audioEnabled: state.audioEnabled\r\n });\r\n }\r\n\r\n /**\r\n * 통화 수락 처리 (발신자 측에서 CALL_ACCEPT 수신 시)\r\n * @private\r\n */\r\n async _handleCallAccepted(userId) {\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n\r\n // 이제 PeerConnection 생성 (Impolite - 발신자)\r\n // skipAutoNegotiation: true - 트랙 추가 시 자동 negotiation 건너뛰고 수동으로 offer 전송\r\n this.peerManager.createPeerConnection(userId, false, { skipAutoNegotiation: true });\r\n\r\n // Offer 생성 및 전송\r\n const offer = await this.peerManager.createOffer(userId);\r\n this._sendSignal({\r\n type: SignalTypes.CALL_OFFER,\r\n receiverId: userId,\r\n data: { sdp: offer }\r\n });\r\n\r\n this.emit('callAccepted', { userId });\r\n }\r\n\r\n /**\r\n * 통화 종료 처리\r\n * @private\r\n */\r\n _handleCallEnded(userId) {\r\n this.peerManager.closePeerConnection(userId);\r\n this.participants.delete(userId);\r\n\r\n this.emit('participantLeft', { userId });\r\n\r\n // 모든 참여자가 나갔으면 통화 종료\r\n if (this.peerManager.getPeerIds().length === 0) {\r\n this.endCall();\r\n }\r\n }\r\n\r\n // ==================== 유틸리티 ====================\r\n\r\n /**\r\n * 현재 통화 중인지 확인\r\n * @returns {boolean}\r\n */\r\n isInCall() {\r\n return this.currentRoom !== null || this.peerManager.getPeerIds().length > 0;\r\n }\r\n\r\n /**\r\n * 현재 통화방 ID\r\n * @returns {string|null}\r\n */\r\n getCurrentRoom() {\r\n return this.currentRoom;\r\n }\r\n\r\n /**\r\n * 참여자 목록\r\n * @returns {string[]}\r\n */\r\n getParticipants() {\r\n return Array.from(this.participants.keys());\r\n }\r\n\r\n /**\r\n * 연결 상태 요약\r\n * @public\r\n * @returns {Object}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n getConnectionSummary() {\r\n return this.peerManager.getConnectionSummary();\r\n }\r\n\r\n /**\r\n * 특정 피어의 연결 통계 가져오기 (디버깅/모니터링용)\r\n * @public\r\n * @param {string} peerId - 피어 ID\r\n * @returns {Promise<RTCStatsReport|null>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async getStats(peerId) {\r\n return this.peerManager.getStats(peerId);\r\n }\r\n\r\n /**\r\n * 미디어 상태\r\n * @returns {Object}\r\n */\r\n getMediaState() {\r\n return {\r\n videoEnabled: this.mediaManager.isVideoEnabled(),\r\n audioEnabled: this.mediaManager.isAudioEnabled(),\r\n streamInfo: this.mediaManager.getStreamInfo()\r\n };\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n this.mediaManager.setLogLevel(level);\r\n this.peerManager.setLogLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리\r\n */\r\n destroy() {\r\n this.endCall();\r\n this.mediaManager.destroy();\r\n this.peerManager.destroy();\r\n this.removeAllListeners();\r\n this.logger.info('WebRTCClient destroyed');\r\n }\r\n}\r\n\r\nexport default WebRTCClient;\r\n","/**\r\n * PushManager\r\n * FCM 웹 푸시 알림 관리 (Firebase 초기화, 서비스 워커, 권한, 토큰)\r\n */\r\n\r\nimport Logger from '../utils/Logger.js';\r\nimport { LogLevel } from '../constants.js';\r\n\r\n/**\r\n * 푸시 활성화 실패 사유 분류.\r\n *\r\n * <p>호출자가 {@code error.message} 문자열 매칭 대신 {@code error.code} 로 분기할 수 있도록\r\n * 표준화된 enum 값을 제공한다.</p>\r\n *\r\n * <ul>\r\n * <li>{@code UNSUPPORTED_BROWSER} — Notification / Service Worker 미지원 환경</li>\r\n * <li>{@code FIREBASE_NOT_INSTALLED} — firebase 패키지 동적 import 실패</li>\r\n * <li>{@code SW_REGISTER_FAILED} — 서비스 워커 등록 실패 (firebase-messaging-sw.js 누락 등)</li>\r\n * <li>{@code PERMISSION_DENIED} — 브라우저 알림 권한 거부 (이미 거부 상태 또는 이번 요청에서 거부)</li>\r\n * <li>{@code TOKEN_FAILED} — FCM 토큰 발급 실패</li>\r\n * <li>{@code SERVER_REGISTER_FAILED} — TalkFlow 서버에 디바이스 토큰 등록 실패</li>\r\n * </ul>\r\n */\r\nexport const PushErrorCode = Object.freeze({\r\n UNSUPPORTED_BROWSER: 'UNSUPPORTED_BROWSER',\r\n FIREBASE_NOT_INSTALLED: 'FIREBASE_NOT_INSTALLED',\r\n SW_REGISTER_FAILED: 'SW_REGISTER_FAILED',\r\n PERMISSION_DENIED: 'PERMISSION_DENIED',\r\n TOKEN_FAILED: 'TOKEN_FAILED',\r\n SERVER_REGISTER_FAILED: 'SERVER_REGISTER_FAILED'\r\n});\r\n\r\n/**\r\n * 푸시 활성화 과정에서 발생한 분류된 에러.\r\n *\r\n * <p>일반 {@link Error} 와 달리 {@code code} 필드로 분류되어 있어\r\n * 호출자가 사유별로 다른 UX (권한 거부 → 설정 가이드, 토큰 실패 → 재시도 등) 를 적용할 수 있다.</p>\r\n */\r\nexport class PushError extends Error {\r\n /**\r\n * @param {string} code - {@link PushErrorCode} 값 중 하나\r\n * @param {string} message - 사람이 읽는 메시지\r\n * @param {Error} [cause] - 원인 에러 (네트워크 실패 등)\r\n */\r\n constructor(code, message, cause) {\r\n super(message);\r\n this.name = 'PushError';\r\n this.code = code;\r\n if (cause !== undefined) {\r\n this.cause = cause;\r\n }\r\n }\r\n}\r\n\r\n// TalkFlow 기본 Firebase 설정 (고객별 분리 시 서버에서 내려받는 구조로 확장 가능)\r\nconst DEFAULT_FIREBASE_CONFIG = {\r\n apiKey: \"AIzaSyB8VhRg6rSvUI7K3Ua7h6sBpLvaQGmIkRc\",\r\n authDomain: \"chatting-c3e5d.firebaseapp.com\",\r\n projectId: \"chatting-c3e5d\",\r\n storageBucket: \"chatting-c3e5d.firebasestorage.app\",\r\n messagingSenderId: \"1020496565673\",\r\n appId: \"1:1020496565673:web:5039167257fd83f5ce20b8\"\r\n};\r\n\r\nconst DEFAULT_VAPID_KEY = 'BGP8qaSm1ntjWd4n9pc0lX_rw4BmMxm9u4pvRIANCitbmYaV0iy-gn05suTKBo88kUBejxdxM8sb6x2nt3avu8c';\r\nconst PENDING_NAVIGATION_DB_NAME = 'talkflowPush';\r\nconst PENDING_NAVIGATION_STORE_NAME = 'pendingNavigation';\r\n// SW 와 동일한 key 규칙 사용 — 멀티테넌트에서 다른 projectId 의 pending 을 건드리지 않도록 한다.\r\nconst PENDING_NAVIGATION_KEY_PREFIX = 'pending-room';\r\nconst PENDING_NAVIGATION_LEGACY_KEY = 'pending-room';\r\n\r\n// deviceId 저장 — 같은 IndexedDB 의 별도 store 사용 (XSS 방어 + 기존 인프라 재사용).\r\n// DB 버전은 2 로 bump — v1 에서 pendingNavigation 만 있던 상태에서 deviceInfo store 추가.\r\nconst DEVICE_STORE_NAME = 'deviceInfo';\r\nconst DEVICE_ID_KEY = 'device-id';\r\nconst DEVICE_ID_DB_VERSION = 2;\r\n// 기존 localStorage 에 저장된 deviceId 를 IndexedDB 로 옮긴 뒤 제거하기 위한 legacy key.\r\nconst LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY = 'talkflow_device_id';\r\n\r\nfunction buildPendingNavigationKey(projectId) {\r\n if (projectId && typeof projectId === 'string' && projectId.trim() !== '') {\r\n return PENDING_NAVIGATION_KEY_PREFIX + ':' + projectId;\r\n }\r\n return PENDING_NAVIGATION_LEGACY_KEY;\r\n}\r\n\r\nfunction openPendingNavigationDb() {\r\n if (typeof indexedDB === 'undefined') {\r\n return Promise.resolve(null);\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const request = indexedDB.open(PENDING_NAVIGATION_DB_NAME, DEVICE_ID_DB_VERSION);\r\n\r\n request.onupgradeneeded = () => {\r\n const db = request.result;\r\n // v1 → v2: deviceInfo store 추가.\r\n // 기존 pendingNavigation store 는 그대로 유지 (contains 체크로 멱등).\r\n if (!db.objectStoreNames.contains(PENDING_NAVIGATION_STORE_NAME)) {\r\n db.createObjectStore(PENDING_NAVIGATION_STORE_NAME, { keyPath: 'id' });\r\n }\r\n if (!db.objectStoreNames.contains(DEVICE_STORE_NAME)) {\r\n db.createObjectStore(DEVICE_STORE_NAME, { keyPath: 'id' });\r\n }\r\n };\r\n\r\n request.onsuccess = () => resolve(request.result);\r\n request.onerror = () => reject(request.error || new Error('IndexedDB open failed'));\r\n });\r\n}\r\n\r\n/**\r\n * IndexedDB 에서 deviceId 를 읽는다.\r\n * @returns {Promise<string|null>} 저장된 deviceId, 없으면 null. IndexedDB 사용 불가 시 null.\r\n */\r\nfunction readDeviceIdFromIndexedDb() {\r\n return openPendingNavigationDb().then(db => {\r\n if (!db) return null;\r\n return new Promise((resolve, reject) => {\r\n const tx = db.transaction(DEVICE_STORE_NAME, 'readonly');\r\n const store = tx.objectStore(DEVICE_STORE_NAME);\r\n const request = store.get(DEVICE_ID_KEY);\r\n request.onsuccess = () => {\r\n const record = request.result;\r\n resolve(record && record.value ? record.value : null);\r\n };\r\n request.onerror = () => reject(request.error || new Error('IndexedDB read failed'));\r\n tx.oncomplete = () => db.close();\r\n tx.onerror = () => db.close();\r\n tx.onabort = () => db.close();\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * IndexedDB 에 deviceId 를 저장한다.\r\n *\r\n * <p><b>반환값 의미</b>:</p>\r\n * <ul>\r\n * <li>{@code true} — transaction 이 complete 까지 성공적으로 완료됨.</li>\r\n * <li>{@code false} — open / transaction 실패, IDB 사용 불가, 기타 예외.</li>\r\n * </ul>\r\n *\r\n * <p>호출자는 반환값이 {@code true} 일 때만 \"영속성 확보\" 로 간주해야 한다.\r\n * 특히 legacy localStorage 정리는 read-back 검증까지 거친 뒤 수행.</p>\r\n *\r\n * @param {string} deviceId\r\n * @returns {Promise<boolean>}\r\n */\r\nfunction writeDeviceIdToIndexedDb(deviceId) {\r\n return openPendingNavigationDb().then(db => {\r\n if (!db) return false;\r\n return new Promise(resolve => {\r\n try {\r\n const tx = db.transaction(DEVICE_STORE_NAME, 'readwrite');\r\n tx.objectStore(DEVICE_STORE_NAME).put({ id: DEVICE_ID_KEY, value: deviceId });\r\n tx.oncomplete = () => { db.close(); resolve(true); };\r\n tx.onerror = () => { db.close(); resolve(false); };\r\n tx.onabort = () => { db.close(); resolve(false); };\r\n } catch (e) {\r\n try { db.close(); } catch (_) { /* ignore */ }\r\n resolve(false);\r\n }\r\n });\r\n }).catch(() => false);\r\n}\r\n\r\n/**\r\n * 신규 deviceId 생성.\r\n */\r\nfunction generateDeviceId() {\r\n const random = (typeof crypto !== 'undefined' && crypto.randomUUID)\r\n ? crypto.randomUUID()\r\n : Date.now().toString(36) + Math.random().toString(36).substring(2);\r\n return 'web-' + random;\r\n}\r\n\r\nasync function consumePendingRoomFromIndexedDB(projectId) {\r\n const db = await openPendingNavigationDb();\r\n if (!db) {\r\n return null;\r\n }\r\n\r\n // 1차: projectId 별 key 를 조회한다.\r\n // 2차 (mixed rollout 호환): projectId 가 있지만 1차 miss 인 경우,\r\n // legacy 전역 key 도 fallback 으로 조회한다. 서버 배포 전 또는 구버전 SW 에서 발생한\r\n // projectId 없는 payload 는 legacy key (pending-room) 에 저장되어 있을 수 있다.\r\n // legacy record 는 반드시 record.projectId 가 비어있어야 안전하게 소비한다 —\r\n // 다른 프로젝트의 값이 섞일 여지를 원천 차단.\r\n const primaryKey = buildPendingNavigationKey(projectId);\r\n const shouldTryLegacyFallback = !!projectId && primaryKey !== PENDING_NAVIGATION_LEGACY_KEY;\r\n\r\n return new Promise((resolve, reject) => {\r\n const transaction = db.transaction(PENDING_NAVIGATION_STORE_NAME, 'readwrite');\r\n const store = transaction.objectStore(PENDING_NAVIGATION_STORE_NAME);\r\n\r\n let pendingRoomId = null;\r\n let finalizedKey = null;\r\n\r\n const primaryRequest = store.get(primaryKey);\r\n\r\n primaryRequest.onsuccess = () => {\r\n const record = primaryRequest.result;\r\n // 저장된 record 의 projectId 와 요청한 projectId 가 일치할 때만 소비.\r\n // key 분리가 정상 작동하면 이 체크는 리던던트이지만, 레거시 전역 key 로 저장된\r\n // 레코드를 다른 프로젝트가 실수로 소비하는 것을 막기 위한 방어.\r\n if (record && record.roomId) {\r\n const recordProjectId = record.projectId || null;\r\n const requestedProjectId = projectId || null;\r\n if (recordProjectId === requestedProjectId) {\r\n pendingRoomId = record.roomId;\r\n finalizedKey = primaryKey;\r\n store.delete(primaryKey);\r\n return;\r\n }\r\n }\r\n\r\n // 1차 miss — legacy fallback 시도 (mixed rollout 호환 경로).\r\n if (!shouldTryLegacyFallback) {\r\n return;\r\n }\r\n\r\n const legacyRequest = store.get(PENDING_NAVIGATION_LEGACY_KEY);\r\n legacyRequest.onsuccess = () => {\r\n const legacyRecord = legacyRequest.result;\r\n // legacy record 는 projectId 가 null/undefined 인 경우에만 안전하게 소비.\r\n // SW v1 또는 구버전 서버가 projectId 없이 저장한 경우에 해당한다.\r\n if (\r\n legacyRecord &&\r\n legacyRecord.roomId &&\r\n !legacyRecord.projectId\r\n ) {\r\n pendingRoomId = legacyRecord.roomId;\r\n finalizedKey = PENDING_NAVIGATION_LEGACY_KEY;\r\n store.delete(PENDING_NAVIGATION_LEGACY_KEY);\r\n }\r\n };\r\n // legacyRequest.onerror 는 별도 처리하지 않는다 — primary 가 성공했다면 transaction 은\r\n // 이미 유효하고, 실패하면 transaction.onerror 로 넘어간다.\r\n };\r\n\r\n primaryRequest.onerror = () => {\r\n reject(primaryRequest.error || new Error('IndexedDB read failed'));\r\n };\r\n\r\n transaction.oncomplete = () => {\r\n db.close();\r\n // finalizedKey 를 쓰진 않지만, 추후 디버깅/로그 확장 시 어느 key 를 소비했는지 구분 가능.\r\n void finalizedKey;\r\n resolve(pendingRoomId);\r\n };\r\n\r\n transaction.onerror = () => {\r\n db.close();\r\n reject(transaction.error || new Error('IndexedDB transaction failed'));\r\n };\r\n\r\n transaction.onabort = () => {\r\n db.close();\r\n reject(transaction.error || new Error('IndexedDB transaction aborted'));\r\n };\r\n });\r\n}\r\n\r\nclass PushManager {\r\n /**\r\n * @param {Object} options\r\n * @param {Object} options.apiClient - ApiClient 인스턴스\r\n * @param {string} [options.projectId] - 프로젝트 ID (멀티테넌트 환경에서 알림 라우팅 격리용)\r\n * @param {Object} [options.firebaseConfig] - Firebase 설정 (기본: TalkFlow 설정)\r\n * @param {string} [options.vapidKey] - VAPID Key (기본: TalkFlow 키)\r\n * @param {string} [options.serviceWorkerPath] - 서비스 워커 경로 (기본: '/firebase-messaging-sw.js')\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n this.apiClient = options.apiClient;\r\n this.projectId = options.projectId || null;\r\n this.firebaseConfig = options.firebaseConfig || DEFAULT_FIREBASE_CONFIG;\r\n this.vapidKey = options.vapidKey || DEFAULT_VAPID_KEY;\r\n this.serviceWorkerPath = options.serviceWorkerPath || '/firebase-messaging-sw.js';\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'PushManager');\r\n\r\n this._messaging = null;\r\n this._currentToken = null;\r\n this._enabled = false;\r\n this._enablePromise = null;\r\n this._foregroundMessageUnsubscribe = null;\r\n this._swRegistration = null;\r\n this._projectRegisteredToSW = false;\r\n this._visibilityHandler = null;\r\n this._deviceIdCache = null; // IndexedDB 조회 결과 메모리 캐시 (세션 단위)\r\n }\r\n\r\n /**\r\n * 저장된 pending roomId 를 소비한다.\r\n *\r\n * @param {string} [projectId] - 소비할 projectId. 지정 시 해당 projectId 로 저장된 pending 만 소비.\r\n * 미지정 시 레거시 전역 key 만 조회 (구버전 호환).\r\n * @returns {Promise<string|null>}\r\n */\r\n static async consumePendingRoom(projectId) {\r\n return consumePendingRoomFromIndexedDB(projectId);\r\n }\r\n\r\n /**\r\n * 현재 브라우저의 푸시 권한 상태를 조회한다.\r\n *\r\n * <p>{@link PushManager} 인스턴스가 없어도 (즉, {@code enablePushNotifications()} 호출 전) 호출 가능하다.\r\n * 호출자가 enable 호출 <b>전에</b> UI 분기를 결정할 때 사용한다.</p>\r\n *\r\n * <p>반환값:</p>\r\n * <ul>\r\n * <li>{@code 'granted'} — 이미 허용. enable 호출 시 권한창 안 뜨고 토큰 발급으로 즉시 진행.</li>\r\n * <li>{@code 'default'} — 아직 묻지 않음. enable 호출 시 권한창 표시.</li>\r\n * <li>{@code 'denied'} — 거부됨. enable 호출 시 즉시 PERMISSION_DENIED 로 실패 (브라우저 정책상 권한창 안 뜸).</li>\r\n * <li>{@code 'unsupported'} — Notification 또는 Service Worker 미지원 환경 (Safari iOS 등).</li>\r\n * </ul>\r\n *\r\n * @returns {'granted'|'denied'|'default'|'unsupported'}\r\n */\r\n static getPermissionState() {\r\n if (typeof window === 'undefined' || typeof Notification === 'undefined') {\r\n return 'unsupported';\r\n }\r\n if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {\r\n return 'unsupported';\r\n }\r\n return Notification.permission; // 'granted' | 'denied' | 'default'\r\n }\r\n\r\n /**\r\n * 푸시 알림 활성화.\r\n * Firebase 초기화 → 서비스 워커 등록 → 권한 요청 → 토큰 획득 → 서버 등록.\r\n *\r\n * <p>토큰은 SDK 내부에서만 관리되므로 호출자는 반환값을 받을 필요가 없습니다.\r\n * 상태 확인은 {@link #isEnabled} 로, 에러는 throw 로 전달됩니다.</p>\r\n *\r\n * @returns {Promise<void>}\r\n * @throws {Error} 브라우저 미지원, 권한 거부, Firebase 미설치 등\r\n */\r\n async enable() {\r\n if (this._enabled) {\r\n this.logger.debug('푸시 알림이 이미 활성화되어 있습니다.');\r\n return;\r\n }\r\n\r\n if (this._enablePromise) {\r\n this.logger.debug('푸시 알림 활성화가 이미 진행 중입니다.');\r\n return this._enablePromise;\r\n }\r\n\r\n this._enablePromise = this._enableInternal();\r\n\r\n try {\r\n return await this._enablePromise;\r\n } finally {\r\n this._enablePromise = null;\r\n }\r\n }\r\n\r\n async _enableInternal() {\r\n // 브라우저 환경 체크\r\n if (typeof window === 'undefined' || !('Notification' in window)) {\r\n throw new PushError(\r\n PushErrorCode.UNSUPPORTED_BROWSER,\r\n '푸시 알림은 브라우저 환경에서만 사용 가능합니다'\r\n );\r\n }\r\n\r\n if (!('serviceWorker' in navigator)) {\r\n throw new PushError(\r\n PushErrorCode.UNSUPPORTED_BROWSER,\r\n '이 브라우저는 서비스 워커를 지원하지 않습니다'\r\n );\r\n }\r\n\r\n // 권한이 이미 거부된 상태면 requestPermission() 은 즉시 'denied' 를 반환하고\r\n // 권한창은 뜨지 않는다. 명확한 분류 에러로 즉시 throw 하여 호출자가\r\n // \"브라우저 설정에서 풀어달라\" 가이드를 띄울 수 있도록 한다.\r\n if (Notification.permission === 'denied') {\r\n throw new PushError(\r\n PushErrorCode.PERMISSION_DENIED,\r\n '알림 권한이 이미 거부되어 있습니다. 브라우저 사이트 설정에서 허용해주세요.'\r\n );\r\n }\r\n\r\n // Firebase 동적 로드\r\n let firebaseApp, firebaseMessaging;\r\n try {\r\n firebaseApp = await import('firebase/app');\r\n firebaseMessaging = await import('firebase/messaging');\r\n } catch (e) {\r\n throw new PushError(\r\n PushErrorCode.FIREBASE_NOT_INSTALLED,\r\n 'firebase 패키지가 설치되지 않았습니다. npm install firebase 를 실행하세요.',\r\n e\r\n );\r\n }\r\n\r\n // Firebase 초기화 (이미 초기화된 경우 기존 앱 사용)\r\n // 고객 앱의 기존 Firebase와 충돌 방지 — named app으로 분리\r\n let app;\r\n try {\r\n app = firebaseApp.getApp('talkflow');\r\n } catch {\r\n app = firebaseApp.initializeApp(this.firebaseConfig, 'talkflow');\r\n }\r\n\r\n // 서비스 워커 등록\r\n const registration = await this._registerServiceWorker();\r\n\r\n // 알림 권한 요청\r\n // - 'default' 상태: 브라우저가 권한창을 표시하고 사용자 응답을 대기\r\n // - 'granted' 상태: 즉시 'granted' 반환 (창 안 뜸)\r\n // - 'denied' 상태: 위 가드에서 이미 throw 했으므로 여기 도달 불가\r\n const permission = await Notification.requestPermission();\r\n if (permission !== 'granted') {\r\n throw new PushError(\r\n PushErrorCode.PERMISSION_DENIED,\r\n '알림 권한이 거부되었습니다. 브라우저 설정에서 허용해주세요.'\r\n );\r\n }\r\n\r\n // FCM 토큰 획득\r\n this._messaging = firebaseMessaging.getMessaging(app);\r\n\r\n const tokenOptions = { serviceWorkerRegistration: registration };\r\n if (this.vapidKey) {\r\n tokenOptions.vapidKey = this.vapidKey;\r\n }\r\n\r\n try {\r\n this._currentToken = await firebaseMessaging.getToken(this._messaging, tokenOptions);\r\n } catch (e) {\r\n throw new PushError(\r\n PushErrorCode.TOKEN_FAILED,\r\n 'FCM 토큰 획득 중 오류 발생: ' + (e?.message || e),\r\n e\r\n );\r\n }\r\n\r\n if (!this._currentToken) {\r\n throw new PushError(\r\n PushErrorCode.TOKEN_FAILED,\r\n 'FCM 토큰 획득 실패 (응답이 비어있음)'\r\n );\r\n }\r\n\r\n // 서버에 토큰 등록\r\n await this._registerTokenToServer(this._currentToken);\r\n\r\n // 포그라운드 메시지 수신 핸들러\r\n if (this._foregroundMessageUnsubscribe) {\r\n this._foregroundMessageUnsubscribe();\r\n }\r\n\r\n this._foregroundMessageUnsubscribe = firebaseMessaging.onMessage(this._messaging, (payload) => {\r\n this.logger.debug('포그라운드 푸시 수신:', payload);\r\n this._onForegroundMessage(payload);\r\n });\r\n\r\n // SW 등록 객체를 보관 — 이후 projectId 재등록에 사용한다.\r\n this._swRegistration = registration;\r\n\r\n // SW 에 projectId 등록.\r\n // 멀티테넌트 환경에서 notificationclick 시 SW 가 \"어느 창이 어느 project 인지\"를 알아야\r\n // 다른 프로젝트 창에 NAVIGATE_TO_ROOM 을 보내지 않는다.\r\n await this._registerProjectToSW();\r\n\r\n // SW terminate 로 Map 이 소실될 수 있으므로 visibility 복귀 시 재등록.\r\n this._installVisibilityReRegister();\r\n\r\n // SW 업데이트 체크 (CDN handler 갱신 전파)\r\n try {\r\n await registration.update();\r\n } catch (e) {\r\n this.logger.debug('SW 업데이트 체크 실패 (무시):', e.message);\r\n }\r\n\r\n // SW 버전 감지 — 구버전 경고 용도로만 사용.\r\n // 이 값으로 서버/SDK 가 기능 분기(image 지원 여부 등)를 판단하면 안 된다.\r\n const swVersion = await this._getSWVersion(registration);\r\n if (!swVersion) {\r\n this.logger.warn(\r\n '[TalkFlow] 서비스 워커가 구버전(v1)입니다. ' +\r\n '이미지 미리보기 등 리치 알림 기능을 사용하려면 firebase-messaging-sw.js 를 ' +\r\n 'v2 로 업데이트하세요. 가이드: https://docs.talkflow.ai/push/sw-upgrade'\r\n );\r\n } else {\r\n this.logger.debug('SW 버전:', swVersion);\r\n }\r\n\r\n this._enabled = true;\r\n this.logger.info('푸시 알림 활성화 완료');\r\n }\r\n\r\n /**\r\n * 서비스 워커 등록.\r\n * updateViaCache: 'none' 으로 등록하여 registration.update() 시\r\n * importScripts 로 로드하는 CDN handler 까지 캐시 없이 갱신되도록 한다.\r\n * https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register\r\n * @private\r\n */\r\n async _registerServiceWorker() {\r\n try {\r\n const registration = await navigator.serviceWorker.register(\r\n this.serviceWorkerPath,\r\n { updateViaCache: 'none' }\r\n );\r\n this.logger.debug('서비스 워커 등록 완료:', this.serviceWorkerPath);\r\n return registration;\r\n } catch (error) {\r\n throw new PushError(\r\n PushErrorCode.SW_REGISTER_FAILED,\r\n `서비스 워커 등록 실패: ${this.serviceWorkerPath}\\n` +\r\n '프로젝트 public 폴더에 firebase-messaging-sw.js 파일을 배치하세요.',\r\n error\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * 서버에 디바이스 토큰 등록\r\n * @private\r\n */\r\n async _registerTokenToServer(token) {\r\n try {\r\n const deviceId = await this._getDeviceId();\r\n await this.apiClient.post('/api/v1/push/devices', {\r\n deviceToken: token,\r\n deviceType: 'WEB',\r\n deviceId\r\n });\r\n this.logger.info('디바이스 토큰 서버 등록 완료');\r\n } catch (error) {\r\n this.logger.error('디바이스 토큰 서버 등록 실패:', error);\r\n // PushError 가 아닌 경우 (네트워크 / 서버 5xx 등) SERVER_REGISTER_FAILED 로 wrap.\r\n // 이미 PushError 면 그대로 전파 (분류 보존).\r\n if (error instanceof PushError) {\r\n throw error;\r\n }\r\n throw new PushError(\r\n PushErrorCode.SERVER_REGISTER_FAILED,\r\n '디바이스 토큰 서버 등록 실패: ' + (error?.message || error),\r\n error\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * 브라우저 고유 식별자 조회/생성 (IndexedDB 기반).\r\n *\r\n * <p>순서:</p>\r\n * <ol>\r\n * <li>IndexedDB 에서 기존 deviceId 조회</li>\r\n * <li>없으면 localStorage 에 있는 legacy 값 있는지 확인 (기존 SDK 버전 설치 이력)</li>\r\n * <li>legacy 있으면 IndexedDB 로 이관 + localStorage 키 제거</li>\r\n * <li>둘 다 없으면 신규 생성 + IndexedDB 저장</li>\r\n * </ol>\r\n *\r\n * <p>IndexedDB 사용 불가 환경 (private mode 등) 에서는 in-memory fallback — 세션 단위\r\n * 새 deviceId 사용. 완벽한 영속성은 없지만 기본 기능은 유지.</p>\r\n *\r\n * @private\r\n * @returns {Promise<string>}\r\n */\r\n async _getDeviceId() {\r\n // 메모리 캐시 — 동일 세션 내 반복 호출 방지.\r\n if (this._deviceIdCache) {\r\n return this._deviceIdCache;\r\n }\r\n\r\n try {\r\n // 1) IndexedDB 에서 조회\r\n let deviceId = await readDeviceIdFromIndexedDb();\r\n\r\n // 2) 없으면 localStorage legacy 값 확인 + 이관\r\n if (!deviceId && typeof localStorage !== 'undefined') {\r\n const legacy = localStorage.getItem(LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY);\r\n if (legacy) {\r\n deviceId = legacy;\r\n // IDB 저장 성공 + read-back 검증 둘 다 통과한 경우에만 localStorage 정리.\r\n // 실패하거나 검증 miss 면 legacy 값 보존해 다음 방문에 재시도 가능.\r\n const saved = await writeDeviceIdToIndexedDb(deviceId);\r\n if (saved) {\r\n const readBack = await readDeviceIdFromIndexedDb();\r\n if (readBack === deviceId) {\r\n try {\r\n localStorage.removeItem(LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY);\r\n this.logger.debug('deviceId migrated from localStorage to IndexedDB');\r\n } catch (_) { /* quota/private mode 예외 무시 — legacy 남아도 다음에 재이관 시도 */ }\r\n } else {\r\n // 저장 tx 는 complete 됐는데 read-back 은 다르게 나오는 극단 케이스.\r\n // localStorage 보존 — 다음 방문에 재시도.\r\n this.logger.warn('deviceId IDB read-back 불일치, localStorage 보존');\r\n }\r\n } else {\r\n this.logger.warn('deviceId IDB 저장 실패 (private mode / quota 등), localStorage 보존');\r\n }\r\n }\r\n }\r\n\r\n // 3) 그래도 없으면 신규 생성 + 저장 시도.\r\n // 저장 실패해도 in-memory 캐시로 세션 단위 유효하므로 푸시 등록은 진행됨.\r\n // (영속화 실패는 다음 방문에 새 deviceId 생성 → 서버 쪽 device 매핑 갱신은 불가피)\r\n if (!deviceId) {\r\n deviceId = generateDeviceId();\r\n const saved = await writeDeviceIdToIndexedDb(deviceId);\r\n if (!saved) {\r\n this.logger.warn('신규 deviceId IDB 저장 실패, 이번 세션만 유효');\r\n }\r\n }\r\n\r\n this._deviceIdCache = deviceId;\r\n return deviceId;\r\n } catch (error) {\r\n // IndexedDB 장애 (open / read / transaction throw 등) 시 fallback.\r\n // IDB 가 현재 실패하므로 localStorage 를 삭제하지 않는다 — 다음 방문에 IDB 회복되면\r\n // try 블록의 이관 경로가 다시 시도됨.\r\n this.logger.warn('IndexedDB deviceId 조회/저장 실패, fallback:', error);\r\n if (!this._deviceIdCache) {\r\n // 1) localStorage legacy 우선 확인 — 기존 사용자 device 연속성 유지.\r\n // IDB 가 실패해도 localStorage 가 읽히면 기존 deviceId 재사용.\r\n let fallbackId = null;\r\n if (typeof localStorage !== 'undefined') {\r\n try {\r\n fallbackId = localStorage.getItem(LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY);\r\n } catch (_) { /* private mode 등 — localStorage 도 접근 불가 */ }\r\n }\r\n // 2) legacy 도 없으면 신규 생성 (in-memory 단일 세션).\r\n this._deviceIdCache = fallbackId || generateDeviceId();\r\n }\r\n return this._deviceIdCache;\r\n }\r\n }\r\n\r\n /**\r\n * 포그라운드 메시지 수신 콜백.\r\n * TalkFlowClient에서 오버라이드하여 activeRoom 기반 suppress 처리.\r\n * @param {Object} payload - FCM 메시지 페이로드\r\n */\r\n _onForegroundMessage(payload) {\r\n // TalkFlowClient에서 오버라이드\r\n }\r\n\r\n /**\r\n * 서비스 워커가 cold start 시 저장한 pending roomId 를 소비하고 삭제한다.\r\n * 앱 부팅 시 호출하여 알림 클릭으로 열려야 할 방으로 라우팅할 때 사용한다.\r\n *\r\n * <p>내부 projectId 가 설정되어 있으면 해당 프로젝트의 pending 만 소비한다.\r\n * 다른 프로젝트의 pending 은 건드리지 않아 그 프로젝트가 나중에 꺼낼 수 있다.</p>\r\n *\r\n * @returns {Promise<string|null>} pending roomId 또는 null\r\n */\r\n async consumePendingRoom() {\r\n return PushManager.consumePendingRoom(this.projectId);\r\n }\r\n\r\n /**\r\n * 현재 푸시 상태를 정리한다.\r\n * 로그아웃 또는 다른 사용자로 전환할 때 foreground listener 와 활성화 상태를 초기화한다.\r\n * SW 에 등록된 projectId 도 해제한다.\r\n */\r\n reset() {\r\n if (this._foregroundMessageUnsubscribe) {\r\n try {\r\n this._foregroundMessageUnsubscribe();\r\n } catch (error) {\r\n this.logger.debug('포그라운드 푸시 리스너 해제 실패 (무시):', error?.message || error);\r\n }\r\n }\r\n\r\n // visibility 재등록 핸들러 제거 — reset 이후 새 enable 전까지 자동 재등록이 일어나지 않도록.\r\n this._uninstallVisibilityReRegister();\r\n\r\n // SW 의 projectId 등록도 해제 (best-effort).\r\n this._unregisterProjectFromSW();\r\n\r\n this._foregroundMessageUnsubscribe = null;\r\n this._enablePromise = null;\r\n this._messaging = null;\r\n this._currentToken = null;\r\n this._enabled = false;\r\n this._swRegistration = null;\r\n this._projectRegisteredToSW = false;\r\n }\r\n\r\n /**\r\n * 활성 SW 에 현재 projectId 를 등록한다.\r\n * SW 는 Client.id → projectId Map 을 유지해 notificationclick 시 올바른 창을 선택한다.\r\n * @private\r\n */\r\n async _registerProjectToSW() {\r\n if (!this.projectId) {\r\n return;\r\n }\r\n try {\r\n const readyRegistration = await navigator.serviceWorker.ready;\r\n const sw =\r\n readyRegistration.active ||\r\n (this._swRegistration && this._swRegistration.active);\r\n if (!sw) {\r\n this.logger.debug('SW active worker 없음 — projectId 등록 스킵');\r\n return;\r\n }\r\n sw.postMessage({ type: 'TALKFLOW_REGISTER_PROJECT', projectId: this.projectId });\r\n this._projectRegisteredToSW = true;\r\n } catch (error) {\r\n this.logger.debug('SW projectId 등록 실패 (무시):', error?.message || error);\r\n }\r\n }\r\n\r\n /**\r\n * SW 에 등록된 projectId 해제 (best-effort).\r\n * reset 시 호출되며, navigator.serviceWorker 접근이 실패해도 예외를 삼킨다.\r\n * @private\r\n */\r\n _unregisterProjectFromSW() {\r\n if (!this._projectRegisteredToSW) {\r\n return;\r\n }\r\n try {\r\n if (typeof navigator === 'undefined' || !navigator.serviceWorker) {\r\n return;\r\n }\r\n const controller = navigator.serviceWorker.controller;\r\n if (!controller) {\r\n return;\r\n }\r\n controller.postMessage({ type: 'TALKFLOW_UNREGISTER_PROJECT' });\r\n } catch (error) {\r\n this.logger.debug('SW projectId 해제 실패 (무시):', error?.message || error);\r\n }\r\n }\r\n\r\n /**\r\n * visibility 복귀 시 SW 에 projectId 를 재등록한다.\r\n * SW 는 idle 시 terminate 되며 이때 clientProjectMap 이 소실되므로,\r\n * 창이 다시 visible 상태가 될 때 방어적으로 재등록한다.\r\n * @private\r\n */\r\n _installVisibilityReRegister() {\r\n if (typeof document === 'undefined' || this._visibilityHandler) {\r\n return;\r\n }\r\n this._visibilityHandler = () => {\r\n if (document.visibilityState === 'visible' && this._enabled && this.projectId) {\r\n // best-effort — 재등록 실패는 다음 visibility 이벤트에 다시 시도된다.\r\n this._registerProjectToSW().catch(() => {});\r\n }\r\n };\r\n document.addEventListener('visibilitychange', this._visibilityHandler);\r\n }\r\n\r\n /**\r\n * @private\r\n */\r\n _uninstallVisibilityReRegister() {\r\n if (typeof document === 'undefined' || !this._visibilityHandler) {\r\n return;\r\n }\r\n try {\r\n document.removeEventListener('visibilitychange', this._visibilityHandler);\r\n } catch (error) {\r\n // 무시\r\n }\r\n this._visibilityHandler = null;\r\n }\r\n\r\n // noinspection JSUnusedGlobalSymbols\r\n /**\r\n * 현재 FCM 토큰\r\n * @returns {string|null}\r\n */\r\n getToken() {\r\n return this._currentToken;\r\n }\r\n\r\n /**\r\n * 푸시 활성화 여부\r\n * @returns {boolean}\r\n */\r\n isEnabled() {\r\n return this._enabled;\r\n }\r\n\r\n /**\r\n * 현재 사용자의 등록된 디바이스 목록 조회 (푸시 on/off UI 용).\r\n *\r\n * <p>서버가 내려주는 필드: {@code deviceId}, {@code deviceType}, {@code enabled}, {@code createdAt}.\r\n * {@code deviceToken} 은 보안상 응답에 포함되지 않는다.</p>\r\n *\r\n * @returns {Promise<Array<{deviceId: string, deviceType: string, enabled: boolean, createdAt: string}>>}\r\n */\r\n async getMyDevices() {\r\n const response = await this.apiClient.get('/api/v1/push/devices/me');\r\n return response && typeof response === 'object' && 'data' in response\r\n ? response.data\r\n : response;\r\n }\r\n\r\n /**\r\n * 특정 디바이스의 푸시 수신 여부 토글.\r\n *\r\n * <p>본인 소유 디바이스만 수정 가능 (서버가 {@code (projectId, userId, deviceId)} 조합으로 검증).\r\n * 소유자 불일치 또는 미등록 디바이스 시 서버가 404 로 응답 → 호출자에 throw.</p>\r\n *\r\n * @param {string} deviceId - 대상 디바이스 ID (현재 디바이스면 {@link #setCurrentDeviceEnabled} 사용 권장)\r\n * @param {boolean} enabled - true 면 수신, false 면 수신 중단\r\n * @returns {Promise<void>}\r\n */\r\n async setDeviceEnabled(deviceId, enabled) {\r\n await this.apiClient.patch('/api/v1/push/devices/me/enabled', {\r\n deviceId,\r\n enabled\r\n });\r\n this.logger.info(`디바이스 푸시 enabled=${enabled}: deviceId=${deviceId}`);\r\n }\r\n\r\n /**\r\n * 현재 디바이스의 푸시 수신 여부 토글 (편의 메서드).\r\n *\r\n * <p>SDK 가 관리하는 현재 브라우저의 deviceId 로 {@link #setDeviceEnabled} 를 호출한다.\r\n * {@link #enable} 이 선행되지 않아도 호출 가능 — 서버에 디바이스가 등록돼 있으면 동작.</p>\r\n *\r\n * @param {boolean} enabled\r\n * @returns {Promise<void>}\r\n */\r\n async setCurrentDeviceEnabled(enabled) {\r\n const deviceId = await this._getDeviceId();\r\n await this.setDeviceEnabled(deviceId, enabled);\r\n }\r\n\r\n /**\r\n * 등록된 서비스 워커의 TalkFlow 버전을 조회.\r\n *\r\n * navigator.serviceWorker.ready 를 기다려 active worker 가 보장된 상태에서,\r\n * readyRegistration → register() 반환 registration 양쪽의\r\n * waiting → installing → active 순으로 SW 를 찾는다.\r\n * v1→v2 교체 직후 새 SW 가 waiting/installing 상태에 있을 때\r\n * 구 active(v1) 대신 신규(v2)를 우선 감지하여 거짓 경고를 방지한다.\r\n * 같은 scope 에서는 두 객체가 동일 참조이지만, 타이밍 차이에 대한 방어 코드.\r\n *\r\n * v1(구버전) SW 는 메시지 핸들러가 없으므로 2초 timeout 후 null 반환.\r\n *\r\n * @param {ServiceWorkerRegistration} registration - register() 가 반환한 등록 객체\r\n * @returns {Promise<string|null>} 버전 문자열 또는 null (구버전/미응답)\r\n * @private\r\n */\r\n async _getSWVersion(registration) {\r\n try {\r\n const readyRegistration = await navigator.serviceWorker.ready;\r\n\r\n const sw =\r\n readyRegistration.waiting ||\r\n readyRegistration.installing ||\r\n readyRegistration.active ||\r\n registration.waiting ||\r\n registration.installing ||\r\n registration.active;\r\n if (!sw) return null;\r\n\r\n return new Promise((resolve) => {\r\n const timeout = setTimeout(() => resolve(null), 2000);\r\n\r\n const channel = new MessageChannel();\r\n channel.port1.onmessage = (event) => {\r\n clearTimeout(timeout);\r\n resolve(event.data?.version || null);\r\n };\r\n\r\n try {\r\n sw.postMessage({ type: 'TALKFLOW_SW_VERSION' }, [channel.port2]);\r\n } catch (e) {\r\n clearTimeout(timeout);\r\n resolve(null);\r\n }\r\n });\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n}\r\n\r\nexport default PushManager;\r\n","const CONNECTION_EVENT_MAP = [\r\n ['connected', 'connected'],\r\n ['disconnected', 'disconnected'],\r\n ['reconnecting', 'reconnecting'],\r\n ['error', 'connectionError']\r\n];\r\n\r\nconst CHAT_EVENT_MAP = [\r\n ['message', 'chatMessage'],\r\n ['newMessage', 'newChatMessage'],\r\n ['messageUpdated', 'messageUpdated'],\r\n ['messageDeleted', 'messageDeleted'],\r\n ['reactionChanged', 'reactionChanged'],\r\n ['linkPreviewAttached', 'linkPreviewAttached'],\r\n ['messageTranslated', 'messageTranslated'],\r\n ['messageRead', 'messageRead'],\r\n ['typing', 'typing'],\r\n ['memberJoined', 'memberJoined'],\r\n ['memberLeft', 'memberLeft'],\r\n ['roomSubscribed', 'roomSubscribed'],\r\n ['roomUnsubscribed', 'roomUnsubscribed'],\r\n ['roomListSubscribed', 'roomListSubscribed'],\r\n ['roomListUnsubscribed', 'roomListUnsubscribed'],\r\n ['roomListUpdate', 'roomListUpdate'],\r\n ['roomListMessage', 'roomListMessage'],\r\n ['roomListCreated', 'roomListCreated'],\r\n ['roomListJoined', 'roomListJoined'],\r\n ['roomListLeft', 'roomListLeft'],\r\n ['roomListSelfLeft', 'roomListSelfLeft'],\r\n ['roomListKicked', 'roomListKicked'],\r\n ['roomListSelfKicked', 'roomListSelfKicked'],\r\n ['roomListBanned', 'roomListBanned'],\r\n ['roomListSelfBanned', 'roomListSelfBanned'],\r\n ['roomListRoomUpdated', 'roomListRoomUpdated'],\r\n ['retentionCleanup', 'retentionCleanup']\r\n];\r\n\r\nconst WEBRTC_EVENT_MAP = [\r\n ['localStreamStarted', 'localStreamStarted'],\r\n ['localStreamStopped', 'localStreamStopped'],\r\n ['remoteTrack', 'remoteTrack'],\r\n ['screenShareStarted', 'screenShareStarted'],\r\n ['screenShareEnded', 'screenShareEnded'],\r\n ['deviceChange', 'deviceChange'],\r\n ['mediaStateChanged', 'mediaStateChanged'],\r\n ['callStarted', 'callStarted'],\r\n ['callEnded', 'callEnded'],\r\n ['callRequested', 'callRequested'],\r\n ['callAccepted', 'callAccepted'],\r\n ['callRejected', 'callRejected'],\r\n ['callCancelled', 'callCancelled'],\r\n ['callBusy', 'callBusy'],\r\n ['incomingCall', 'incomingCall'],\r\n ['incomingCallWhileBusy', 'incomingCallWhileBusy'],\r\n ['callInvitation', 'callInvitation'],\r\n ['userJoined', 'userJoined'],\r\n ['userLeft', 'userLeft'],\r\n ['participantLeft', 'participantLeft'],\r\n ['participantMediaState', 'participantMediaState'],\r\n ['peerConnected', 'peerConnected'],\r\n ['peerDisconnected', 'peerDisconnected'],\r\n ['peerClosed', 'peerClosed'],\r\n ['error', 'webrtcError']\r\n];\r\n\r\nfunction forwardMappedEvents(source, target, eventMap) {\r\n eventMap.forEach(([sourceEvent, targetEvent]) => {\r\n source.on(sourceEvent, (data) => {\r\n target.emit(targetEvent, data);\r\n });\r\n });\r\n}\r\n\r\nexport function setupEventForwarding(client) {\r\n if (!client.connectionManager) {\r\n return;\r\n }\r\n\r\n client.connectionManager.on('stateChange', ({ state, prevState }) => {\r\n client._state = state;\r\n client.emit('stateChange', { state, prevState });\r\n });\r\n\r\n forwardMappedEvents(client.connectionManager, client, CONNECTION_EVENT_MAP);\r\n\r\n if (client.chat) {\r\n forwardMappedEvents(client.chat, client, CHAT_EVENT_MAP);\r\n }\r\n\r\n if (client.webrtc) {\r\n forwardMappedEvents(client.webrtc, client, WEBRTC_EVENT_MAP);\r\n }\r\n}\r\n","const CHAT_DELEGATE_METHODS = [\r\n 'getRooms',\r\n 'getRoom',\r\n 'getRoomInfo',\r\n 'setMyRoomLanguage',\r\n 'createOneToOneRoom',\r\n 'createGroupRoom',\r\n 'joinGroupRoom',\r\n 'leaveRoom',\r\n 'updateGroupRoom',\r\n 'inviteToGroupRoom',\r\n 'kickMember',\r\n 'banMember',\r\n 'unbanMember',\r\n 'getBannedMembers',\r\n 'getAvailableGroupRooms',\r\n 'getAllGroupRooms',\r\n 'enterRoom',\r\n 'getMessages',\r\n 'fetchLinkPreview',\r\n 'sendMessage',\r\n 'sendMessageOptimistic',\r\n 'sendTextMessage',\r\n 'sendTextMessageOptimistic',\r\n 'sendReply',\r\n 'sendReplyOptimistic',\r\n 'uploadFile',\r\n 'sendFileMessage',\r\n 'sendFileMessageOptimistic',\r\n 'editMessage',\r\n 'deleteMessage',\r\n 'markAsRead',\r\n 'pinMessage',\r\n 'unpinMessage',\r\n 'toggleReaction',\r\n 'subscribeRoom',\r\n 'unsubscribeRoom',\r\n 'unsubscribeAllRooms',\r\n 'setActiveRoom',\r\n 'clearActiveRoom',\r\n 'getActiveRoom',\r\n 'subscribeRoomList',\r\n 'unsubscribeRoomList',\r\n 'isRoomListSubscribed',\r\n 'getSubscribedRooms',\r\n 'isSubscribed',\r\n 'startTyping',\r\n 'stopTyping',\r\n 'getAssistants',\r\n 'getRoomAiMeta',\r\n 'rateAssistantMessage',\r\n 'summarizeWithAssistant',\r\n 'translateWithAssistant',\r\n 'getRoomPmPrompt',\r\n 'upsertRoomPmPrompt',\r\n 'activateRoomPmPrompt',\r\n 'deactivateRoomPmPrompt',\r\n 'getRoomPmPromptVersions',\r\n 'activateRoomPmPromptVersion',\r\n 'previewRoomPmPrompt'\r\n];\r\n\r\nconst WEBRTC_DELEGATE_METHODS = [\r\n 'initializeIceServers',\r\n 'getTurnCredentials',\r\n 'createCallRoom',\r\n 'getCallRoom',\r\n 'joinCallRoomApi',\r\n 'leaveCallRoomApi',\r\n 'enableIncomingCalls',\r\n 'disableIncomingCalls',\r\n 'isIncomingCallsEnabled',\r\n 'startCall',\r\n 'callUser',\r\n 'acceptCall',\r\n 'rejectCall',\r\n 'cancelCall',\r\n 'endCall',\r\n 'toggleVideo',\r\n 'toggleAudio',\r\n 'setVideoEnabled',\r\n 'setAudioEnabled',\r\n 'startScreenShare',\r\n 'stopScreenShare',\r\n 'getLocalStream',\r\n 'getDevices',\r\n 'switchDevice',\r\n 'startDeviceChangeDetection',\r\n 'stopDeviceChangeDetection',\r\n 'applyVideoConstraints',\r\n 'getVideoSettings',\r\n 'getAudioSettings',\r\n 'isInCall',\r\n 'getCurrentRoom',\r\n 'getParticipants',\r\n 'getMediaState',\r\n 'getConnectionSummary',\r\n 'getStats'\r\n];\r\n\r\nfunction defineDelegates(prototype, targetKey, methods) {\r\n methods.forEach((method) => {\r\n prototype[method] = function delegatedMethod(...args) {\r\n return this[targetKey][method](...args);\r\n };\r\n });\r\n}\r\n\r\nexport function applyDelegatedMethods(TalkFlowClient) {\r\n defineDelegates(TalkFlowClient.prototype, 'chat', CHAT_DELEGATE_METHODS);\r\n defineDelegates(TalkFlowClient.prototype, 'webrtc', WEBRTC_DELEGATE_METHODS);\r\n}\r\n","/**\r\n * TalkFlowClient\r\n * 채팅 + WebRTC 통합 클라이언트\r\n */\r\n\r\nimport EventEmitter from './utils/EventEmitter.js';\r\nimport Logger from './utils/Logger.js';\r\nimport ApiClient from './utils/ApiClient.js';\r\nimport { extractUserIdFromJWT } from './utils/jwtUtils.js';\r\nimport { ConnectionState, ErrorTypes, DefaultConfig, LogLevel, Environment, getServerUrl } from './constants.js';\r\nimport { initializeSubClients, applySessionMethods } from './talkflow/session.js';\r\nimport { setupEventForwarding } from './talkflow/eventForwarding.js';\r\nimport { applyDelegatedMethods } from './talkflow/delegates.js';\r\n\r\n// noinspection JSUnresolvedReference\r\nclass TalkFlowClient extends EventEmitter {\r\n /**\r\n * TalkFlowClient 생성\r\n *\r\n * <p>JWT 토큰은 고객사 backend 에서 발급받아서 SDK 에 전달해야 합니다.\r\n * 브라우저에서 직접 JWT 를 발급하는 기능은 제공하지 않습니다 (보안상).\r\n * 자세한 내용은 README \"프로덕션 인증 플로우\" 섹션 참고.</p>\r\n *\r\n * @param {Object} options - 설정 옵션\r\n * @param {string} options.apiKey - Client API 키 (필수, 브라우저 노출 안전)\r\n * @param {string} options.projectId - 프로젝트 ID (필수)\r\n * @param {string} options.jwtToken - JWT 토큰 (필수, 고객사 backend 에서 발급)\r\n * @param {string} [options.env='production'] - 환경 ('development' | 'staging' | 'production')\r\n * @param {string} [options.serverUrl] - 서버 URL (직접 지정 시 env 무시)\r\n * @param {boolean} [options.useSockJS=true] - SockJS 사용 여부\r\n * @param {number} [options.reconnectDelay=5000] - 재연결 지연 (ms)\r\n * @param {number} [options.maxReconnectAttempts=10] - 최대 재연결 시도\r\n * @param {Object[]} [options.iceServers] - ICE 서버 설정\r\n * @param {boolean} [options.autoSubscribeRoomList=true] - connect() 시 채팅방 리스트 자동 구독 여부 (카톡 스타일)\r\n * @param {number} [options.logLevel=LogLevel.WARN] - 로그 레벨\r\n *\r\n * @example\r\n * // 1단계: 고객사 backend 가 talkflow JWT 를 발급받음 (Server API Key 사용)\r\n * // POST https://chat.apiorbit.net/api/v1/users/auth\r\n * // Headers: X-API-KEY: <SERVER_KEY>, X-PROJECT-ID: <project-id>\r\n * // Body: { userId: 'user-123', nickname: '홍길동' }\r\n * // Response: { accessToken, refreshToken }\r\n *\r\n * // 2단계: 고객사 backend 가 최종 사용자 브라우저에 JWT 전달 (자사 API 응답 등)\r\n *\r\n * // 3단계: 브라우저에서 SDK 초기화\r\n * const client = new TalkFlowClient({\r\n * apiKey: 'CLIENT_KEY', // 브라우저에 노출되는 Client Key\r\n * projectId: 'your-project-id',\r\n * jwtToken: receivedJwt, // 고객사 backend 로부터 받은 JWT\r\n * env: 'production'\r\n * });\r\n *\r\n * await client.connect();\r\n */\r\n constructor(options) {\r\n super();\r\n\r\n // 필수 옵션 검증\r\n this._validateOptions(options);\r\n\r\n // 환경에 따른 서버 URL 결정\r\n // serverUrl이 직접 지정되면 우선 사용, 아니면 env에 따라 자동 결정\r\n const env = options.env || DefaultConfig.environment;\r\n const serverUrl = (options.serverUrl || getServerUrl(env)).replace(/\\/$/, '');\r\n\r\n this.options = {\r\n serverUrl,\r\n env,\r\n apiKey: options.apiKey,\r\n projectId: options.projectId,\r\n jwtToken: options.jwtToken || null,\r\n useSockJS: options.useSockJS !== false,\r\n reconnectDelay: options.reconnectDelay || DefaultConfig.reconnectDelay,\r\n maxReconnectAttempts: options.maxReconnectAttempts || DefaultConfig.maxReconnectAttempts,\r\n iceServers: options.iceServers || DefaultConfig.iceServers,\r\n // connect() 시 채팅방 리스트 자동 구독 여부 (카톡 스타일).\r\n // 기본 true — 앱 어디서든 리스트 이벤트를 놓치지 않음 (배지/알림 등).\r\n // false 로 설정 시 수동으로 client.chat.subscribeRoomList() 호출 필요.\r\n autoSubscribeRoomList: options.autoSubscribeRoomList !== false,\r\n logLevel: options.logLevel !== undefined ? options.logLevel : LogLevel.WARN\r\n };\r\n\r\n // 로거 초기화\r\n this.logger = new Logger(this.options.logLevel, 'TalkFlowClient');\r\n\r\n // 상태\r\n this._initialized = false;\r\n this._state = ConnectionState.DISCONNECTED;\r\n\r\n // public 읽기 전용 surface 의 backing field.\r\n // types/index.d.ts 는 chat/webrtc/pushManager/userId 를 readonly 로 선언 —\r\n // 외부 계약(readonly) 과 내부 재할당 필요성을 동시에 만족시키기 위해 private\r\n // backing field 에 저장하고 동명 getter 로 공개한다.\r\n this._userId = null;\r\n\r\n // JWT가 있으면 userId 추출\r\n if (this.options.jwtToken) {\r\n this._userId = extractUserIdFromJWT(this.options.jwtToken);\r\n }\r\n\r\n // API 클라이언트 초기화 (JWT 없이도 동작)\r\n this.apiClient = new ApiClient({\r\n baseUrl: this.options.serverUrl,\r\n apiKey: this.options.apiKey,\r\n projectId: this.options.projectId,\r\n jwtToken: this.options.jwtToken,\r\n logLevel: this.options.logLevel\r\n });\r\n\r\n // ConnectionManager, ChatClient, WebRTCClient는 지연 초기화\r\n this.connectionManager = null;\r\n this._chat = null;\r\n this._webrtc = null;\r\n this._pushManager = null;\r\n this._pushEnablePromise = null;\r\n\r\n // JWT가 있으면 즉시 서브 클라이언트 초기화\r\n if (this.options.jwtToken && this._userId) {\r\n this._initializeSubClients();\r\n }\r\n\r\n this._initialized = true;\r\n this.logger.info('TalkFlowClient initialized', {\r\n userId: this._userId,\r\n hasToken: !!this.options.jwtToken\r\n });\r\n }\r\n\r\n // ==================== Public readonly surface (getter) ====================\r\n\r\n /** @returns {ChatClient|null} */\r\n get chat() { return this._chat; }\r\n\r\n /** @returns {WebRTCClient|null} */\r\n get webrtc() { return this._webrtc; }\r\n\r\n /** @returns {PushManager|null} */\r\n get pushManager() { return this._pushManager; }\r\n\r\n /** @returns {string|null} */\r\n get userId() { return this._userId; }\r\n\r\n /**\r\n * 옵션 검증\r\n * @private\r\n * @param {Object} options - 설정 옵션\r\n * @throws {Error} 필수 옵션이 없는 경우\r\n */\r\n _validateOptions(options) {\r\n if (!options) {\r\n throw new Error('Options are required');\r\n }\r\n if (!options.apiKey) {\r\n throw new Error('apiKey is required');\r\n }\r\n if (!options.projectId) {\r\n throw new Error('projectId is required');\r\n }\r\n\r\n // Server Key (sk-) 브라우저 사용 경고\r\n // - development: 경고만 (registerUser 빠른 테스트 허용)\r\n // - staging/production: 경고 + 강력 안내\r\n if (options.apiKey.startsWith('sk-')) {\r\n const env = options.env || 'production';\r\n if (env === 'development') {\r\n console.warn(\r\n '⚠️ [TalkFlow] Server Key (sk-) 를 개발 모드에서 사용 중입니다. ' +\r\n '프로덕션 배포 전에 반드시 Client Key (ck-) 로 교체하세요.'\r\n );\r\n } else {\r\n console.error(\r\n '🚨 [TalkFlow] Server Key (sk-) 가 비개발 환경 (' + env + ') 에서 감지되었습니다. ' +\r\n '보안 위험: Server Key 를 브라우저에 포함하면 공격자가 임의 사용자로 JWT 를 발급받을 수 있습니다. ' +\r\n '반드시 Client Key (ck-) 로 교체하세요. ' +\r\n '서버에서도 프로덕션 모드에서 Server Key 의 브라우저 호출을 차단합니다.'\r\n );\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 서브 클라이언트 초기화\r\n * @private\r\n */\r\n _initializeSubClients() {\r\n initializeSubClients(this);\r\n }\r\n\r\n /**\r\n * 이벤트 포워딩 설정\r\n * @private\r\n */\r\n _setupEventForwarding() {\r\n setupEventForwarding(this);\r\n }\r\n\r\n /**\r\n * 연결 상태 확인\r\n * @returns {boolean}\r\n */\r\n isConnected() {\r\n return this.connectionManager ? this.connectionManager.isConnected() : false;\r\n }\r\n\r\n /**\r\n * 현재 연결 상태\r\n * @returns {string}\r\n */\r\n getState() {\r\n return this._state;\r\n }\r\n\r\n /**\r\n * 토큰 보유 여부\r\n * @returns {boolean}\r\n */\r\n hasToken() {\r\n return !!this.options.jwtToken;\r\n }\r\n\r\n // ==================== 사용자 API ====================\r\n\r\n /**\r\n * 사용자 인증 (등록/로그인).\r\n * 인증 성공 시 반환된 JWT 토큰을 자동으로 설정합니다.\r\n *\r\n * <h3>⚠️ Server API Key 전용</h3>\r\n * <p>이 메서드는 JWT 를 발급하는 엔드포인트 ({@code POST /api/v1/users/auth}) 를 호출합니다.\r\n * <strong>프로덕션 환경에서는 Client API Key 로 이 메서드를 호출하면 서버가 403 을 반환합니다.</strong></p>\r\n *\r\n * <ul>\r\n * <li><strong>개발/프로토타입</strong>: Server Key 로 SDK 를 초기화한 뒤 이 메서드를 사용하면 빠르게 테스트 가능</li>\r\n * <li><strong>프로덕션</strong>: 고객사 backend 에서 Server Key 로 직접 {@code POST /api/v1/users/auth} 를 호출 →\r\n * JWT 를 받아 SDK 에 {@code setToken(jwt)} 으로 전달. 이 메서드를 브라우저에서 쓰지 마세요.</li>\r\n * </ul>\r\n *\r\n * <p>자세한 내용은 README 의 \"프로덕션 인증 플로우\" 섹션 참고.</p>\r\n *\r\n * @param {Object} userData\r\n * @param {string} userData.userId - 사용자 ID (필수, 고객사 회원 ID)\r\n * @param {string} userData.nickname - 닉네임 (필수, max 100자)\r\n * @param {string} [userData.profileImageUrl] - 프로필 이미지 URL (max 500자)\r\n * @param {Object} [userData.metadata] - 추가 메타데이터\r\n * @returns {Promise<Object>} 인증 결과 (토큰 포함)\r\n *\r\n * @example\r\n * // 개발 환경 — Server Key 로 빠른 테스트\r\n * const client = new TalkFlowClient({\r\n * apiKey: 'SERVER_KEY',\r\n * projectId: 'your-project-id',\r\n * env: 'development'\r\n * });\r\n * await client.registerUser({ userId: 'user-123', nickname: '홍길동' });\r\n * await client.connect();\r\n */\r\n async registerUser(userData) {\r\n // 다국어: navigator.language 자동 시드 — 호출자가 preferredLanguage 를 명시하지 않은 경우에만.\r\n // 서버 /users/auth 는 seed-if-absent (기존 값 비클로버) 라 매 인증마다 덮어쓰지 않아 안전.\r\n const payload = { ...userData };\r\n if (payload.preferredLanguage === undefined) {\r\n const detected = TalkFlowClient.detectBrowserLanguage();\r\n if (detected) {\r\n payload.preferredLanguage = detected;\r\n }\r\n }\r\n\r\n const response = await this.apiClient.post('/api/v1/users/auth', payload);\r\n\r\n if (response.data && response.data.accessToken) {\r\n await this.setToken(response.data.accessToken);\r\n this.logger.info('User authenticated, token auto-set');\r\n }\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * 사용자 정보 수정\r\n * @param {Object} userData\r\n * @param {string} [userData.nickname] - 닉네임 (max 100자)\r\n * @param {string} [userData.profileImageUrl] - 프로필 이미지 URL (max 500자)\r\n * @param {Object} [userData.metadata] - 메타데이터 (전체 교체)\r\n * @param {string|null} [userData.preferredLanguage] - 기본 선호 언어 (BCP-47 / 'OFF'(원문) / null). 다국어.\r\n * @returns {Promise<Object>}\r\n */\r\n async updateMyInfo(userData) {\r\n this._checkToken();\r\n return this.apiClient.put('/api/v1/users/update', userData);\r\n }\r\n\r\n /**\r\n * 내 기본 선호 언어 설정 (전 방 공통, 다국어). 명시 변경 — 방별 오버라이드는 {@link #setMyRoomLanguage}.\r\n * @param {string|null} language - BCP-47 코드(예 'ko','en') / 'OFF'(원문) / null·''(미설정)\r\n * @returns {Promise<Object>}\r\n */\r\n async setPreferredLanguage(language) {\r\n return this.updateMyInfo({ preferredLanguage: language });\r\n }\r\n\r\n /**\r\n * 브라우저 locale 감지 — {@code navigator.language} 를 BCP-47 소문자로 정규화. 비브라우저/미가용 시 null.\r\n * <p>{@link #registerUser} 자동 시드에 사용되며, 고객사 BFF 의 {@code /users/auth} 시드에도 쓸 수 있다.</p>\r\n * @returns {string|null}\r\n */\r\n static detectBrowserLanguage() {\r\n if (typeof navigator === 'undefined' || !navigator.language) {\r\n return null;\r\n }\r\n return navigator.language.trim().toLowerCase() || null;\r\n }\r\n\r\n /**\r\n * 표시 텍스트 헬퍼 (다국어) — 내 언어 번역이 있으면 그것, 없으면 원문.\r\n * <p>{@code myLang} 은 내 effectiveLanguage (방 participant.language ?? user.preferredLanguage).\r\n * {@code messageTranslated} 이벤트로 갱신된 message 에 적용.</p>\r\n * @param {Object} message - 메시지 (translations 맵 포함 가능)\r\n * @param {string} [myLang] - 내 언어 코드\r\n * @returns {string|null}\r\n */\r\n static displayText(message, myLang) {\r\n if (message && message.translations && myLang && message.translations[myLang]) {\r\n return message.translations[myLang];\r\n }\r\n return message ? message.content : null;\r\n }\r\n\r\n /**\r\n * 사용자 존재 여부 확인\r\n * @param {string} projectUserId - 프로젝트 사용자 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async checkUserExists(projectUserId) {\r\n return this.apiClient.get(`/api/v1/users/${projectUserId}/exists`);\r\n }\r\n\r\n /**\r\n * 사용자 목록 조회\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기 (1-100)\r\n * @param {string} [params.lastId] - 마지막 사용자 ID\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값\r\n * @returns {Promise<Object>}\r\n */\r\n async getUsers(params = {}) {\r\n this._checkToken();\r\n const { size = 50, lastId, lastSortValue } = params;\r\n return this.apiClient.get('/api/v1/users', { size, lastId, lastSortValue });\r\n }\r\n\r\n /**\r\n * 사용자 검색\r\n * @param {Object} params\r\n * @param {string} params.keyword - 검색 키워드 (필수, max 50자)\r\n * @param {number} [params.limit=20] - 결과 수 (1-50)\r\n * @returns {Promise<Object>}\r\n */\r\n async searchUsers(params) {\r\n this._checkToken();\r\n const { keyword, limit = 20 } = params;\r\n return this.apiClient.get('/api/v1/users/search', { keyword, limit });\r\n }\r\n\r\n /**\r\n * 토큰 필요 여부 확인\r\n * @private\r\n * @throws {Error} 토큰이 없는 경우\r\n */\r\n _checkToken() {\r\n if (!this.options.jwtToken) {\r\n throw new Error(\r\n 'JWT token is required for this operation. Obtain it from your backend ' +\r\n '(POST /api/v1/users/auth with Server API Key) and pass it via setToken(). ' +\r\n 'See README \"프로덕션 인증 플로우\".'\r\n );\r\n }\r\n }\r\n\r\n // ==================== 유틸리티 ====================\r\n\r\n /**\r\n * 현재 사용자 ID\r\n * @returns {string|null}\r\n */\r\n getUserId() {\r\n return this.userId;\r\n }\r\n\r\n /**\r\n * 초기화 상태 확인\r\n * @returns {boolean}\r\n */\r\n isInitialized() {\r\n return this._initialized;\r\n }\r\n\r\n /**\r\n * 서브 클라이언트 초기화 상태 확인\r\n * @returns {boolean}\r\n */\r\n isReady() {\r\n return this._initialized && !!this.connectionManager;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level - LogLevel 값\r\n */\r\n setLogLevel(level) {\r\n this.options.logLevel = level;\r\n this.logger.setLevel(level);\r\n this.apiClient.logger?.setLevel(level);\r\n\r\n if (this.connectionManager) {\r\n this.connectionManager.setLogLevel(level);\r\n }\r\n if (this.chat) {\r\n this.chat.setLogLevel(level);\r\n }\r\n if (this.webrtc) {\r\n this.webrtc.setLogLevel(level);\r\n }\r\n }\r\n\r\n /**\r\n * 전체 상태 요약\r\n * @returns {Object}\r\n */\r\n getStatus() {\r\n return {\r\n initialized: this._initialized,\r\n ready: this.isReady(),\r\n hasToken: this.hasToken(),\r\n connectionState: this._state,\r\n isConnected: this.isConnected(),\r\n userId: this.userId,\r\n chat: this.chat ? {\r\n subscribedRooms: this.chat.getSubscribedRooms()\r\n } : null,\r\n webrtc: this.webrtc ? {\r\n isInCall: this.webrtc.isInCall(),\r\n currentRoom: this.webrtc.getCurrentRoom(),\r\n participants: this.webrtc.getParticipants(),\r\n mediaState: this.webrtc.getMediaState()\r\n } : null\r\n };\r\n }\r\n\r\n /**\r\n * 리소스 정리\r\n */\r\n async destroy() {\r\n await this.disconnect();\r\n\r\n if (this.chat) {\r\n this.chat.destroy();\r\n }\r\n if (this.webrtc) {\r\n this.webrtc.destroy();\r\n }\r\n if (this.connectionManager) {\r\n await this.connectionManager.destroy();\r\n }\r\n\r\n this.removeAllListeners();\r\n\r\n this._initialized = false;\r\n this.logger.info('TalkFlowClient destroyed');\r\n }\r\n}\r\n\r\n// 정적 속성으로 상수 노출\r\nTalkFlowClient.ConnectionState = ConnectionState;\r\nTalkFlowClient.ErrorTypes = ErrorTypes;\r\nTalkFlowClient.LogLevel = LogLevel;\r\nTalkFlowClient.Environment = Environment;\r\n\r\napplySessionMethods(TalkFlowClient);\r\napplyDelegatedMethods(TalkFlowClient);\r\n\r\nexport default TalkFlowClient;\r\n","import { validateAndParseJWT, isJWTExpired } from '../utils/jwtUtils.js';\r\nimport ConnectionManager from '../core/ConnectionManager.js';\r\nimport ChatClient from '../chat/ChatClient.js';\r\nimport WebRTCClient from '../webrtc/WebRTCClient.js';\r\nimport PushManager from '../push/PushManager.js';\r\n\r\nexport function initializeSubClients(client) {\r\n if (client.connectionManager) {\r\n return;\r\n }\r\n\r\n if (!client.userId) {\r\n throw new Error('userId is required to initialize sub-clients. Please set JWT token first.');\r\n }\r\n\r\n client.connectionManager = new ConnectionManager({\r\n serverUrl: client.options.serverUrl,\r\n jwtToken: client.options.jwtToken,\r\n apiKey: client.options.apiKey,\r\n projectId: client.options.projectId,\r\n useSockJS: client.options.useSockJS,\r\n reconnectDelay: client.options.reconnectDelay,\r\n maxReconnectAttempts: client.options.maxReconnectAttempts,\r\n logLevel: client.options.logLevel\r\n });\r\n\r\n client._chat = new ChatClient({\r\n connectionManager: client.connectionManager,\r\n apiClient: client.apiClient,\r\n userId: client._userId,\r\n logLevel: client.options.logLevel\r\n });\r\n\r\n client._webrtc = new WebRTCClient({\r\n connectionManager: client.connectionManager,\r\n apiClient: client.apiClient,\r\n userId: client._userId,\r\n iceServers: client.options.iceServers,\r\n logLevel: client.options.logLevel\r\n });\r\n\r\n client._setupEventForwarding();\r\n client.logger.debug('Sub-clients initialized');\r\n}\r\n\r\nexport function applySessionMethods(TalkFlowClient) {\r\n TalkFlowClient.prototype.connect = async function connect(jwt, options = {}) {\r\n const { enablePush = false } = options;\r\n\r\n if (jwt) {\r\n await this.setToken(jwt);\r\n }\r\n\r\n if (!this.options.jwtToken) {\r\n throw new Error(\r\n 'JWT token is required. Obtain it from your backend (which calls ' +\r\n 'POST /api/v1/users/auth with a Server API Key) and pass it to the SDK ' +\r\n 'via the jwtToken option, setToken(), or connect(jwt). See README \"프로덕션 인증 플로우\".'\r\n );\r\n }\r\n\r\n if (isJWTExpired(this.options.jwtToken)) {\r\n throw new Error('JWT token has expired. Please update the token before connecting.');\r\n }\r\n\r\n if (!this.connectionManager) {\r\n this._initializeSubClients();\r\n }\r\n\r\n await this.connectionManager.connect();\r\n\r\n if (this.webrtc) {\r\n await this.webrtc.enableIncomingCalls();\r\n }\r\n\r\n if (this.chat && this.options.autoSubscribeRoomList) {\r\n try {\r\n await this.chat.subscribeRoomList();\r\n } catch (error) {\r\n this.logger.warn('Failed to auto-subscribe room list (non-fatal):', error);\r\n }\r\n }\r\n\r\n if (enablePush) {\r\n this.enablePushNotifications();\r\n }\r\n\r\n this.logger.info('Connected to server');\r\n };\r\n\r\n TalkFlowClient.prototype.disconnect = async function disconnect() {\r\n if (!this.connectionManager) {\r\n return;\r\n }\r\n\r\n if (this.webrtc && this.webrtc.isInCall()) {\r\n this.webrtc.endCall();\r\n }\r\n\r\n if (this.webrtc) {\r\n this.webrtc.disableIncomingCalls();\r\n }\r\n\r\n if (this.chat) {\r\n this.chat.unsubscribeAllRooms();\r\n this.chat.unsubscribeRoomList();\r\n }\r\n\r\n await this.connectionManager.disconnect();\r\n this.logger.info('Disconnected from server');\r\n };\r\n\r\n TalkFlowClient.prototype.logout = async function logout() {\r\n try {\r\n await this.apiClient.post('/v1/auth/signout');\r\n this.logger.info('Logged out from server');\r\n } catch (error) {\r\n this.logger.warn('Server logout failed (proceeding with local cleanup):', error.message);\r\n }\r\n\r\n await this.disconnect();\r\n\r\n if (this.pushManager) {\r\n this.pushManager.reset();\r\n this._pushEnablePromise = null;\r\n }\r\n\r\n this.options.jwtToken = null;\r\n this._userId = null;\r\n this.apiClient.setJwtToken(null);\r\n\r\n this.emit('loggedOut');\r\n };\r\n\r\n /**\r\n * 웹 푸시 알림을 활성화한다.\r\n *\r\n * <p>전체 흐름: Firebase 초기화 → 서비스 워커 등록 → 권한 요청 → FCM 토큰 발급 → 서버 등록 →\r\n * 포그라운드 메시지 핸들러 등록 → SW 에 projectId 등록.</p>\r\n *\r\n * <h3>호출 패턴</h3>\r\n * <ul>\r\n * <li><b>자동 활성화 (비차단)</b> — {@code client.enablePushNotifications();} 처럼\r\n * await 없이 호출하면 fire-and-forget 으로 동작한다. 메인 흐름 (UI 렌더링 등) 을 차단하지 않는다.\r\n * 이 경우 결과는 {@code 'pushEnabled'} / {@code 'pushFailed'} 이벤트로 받는다.\r\n * <b>중요</b>: 이 패턴을 쓸 때는 호출 <b>전에</b> 이벤트 리스너를 등록해야 한다 — 그렇지 않으면\r\n * 즉시 emit 되는 실패 이벤트를 놓칠 수 있다.</li>\r\n * <li><b>사용자 액션 (await)</b> — 버튼 클릭 등 사용자 명시적 액션에서는\r\n * {@code const result = await client.enablePushNotifications();} 으로 결과를 즉시 받아서\r\n * UI 분기에 사용한다. throw 하지 않으므로 try/catch 불필요.</li>\r\n * </ul>\r\n *\r\n * <p>이 메서드는 throw 하지 않는다. 실패는 {@code { ok: false, reason, error }} 형태의 객체로 반환된다.\r\n * 따라서 fire-and-forget 으로 호출해도 unhandled rejection 이 발생하지 않는다.</p>\r\n *\r\n * @param {Object} [options]\r\n * @param {Object} [options.firebaseConfig] - 커스텀 Firebase 설정 (테스트/검증용)\r\n * @param {string} [options.vapidKey] - 커스텀 VAPID 키\r\n * @param {string} [options.serviceWorkerPath] - 서비스 워커 경로\r\n * @returns {Promise<{ok: true, alreadyEnabled?: boolean} | {ok: false, reason: string, error: Error}>}\r\n * 성공 시 {@code { ok: true }}, 실패 시 {@code { ok: false, reason, error }}.\r\n * {@code reason} 은 {@link PushErrorCode} 값 또는 {@code 'NO_TOKEN'}.\r\n */\r\n TalkFlowClient.prototype.enablePushNotifications = async function enablePushNotifications(options = {}) {\r\n // JWT 토큰 미설정 시 명확한 분류 에러로 통보 (throw 하지 않고 결과 객체 반환).\r\n if (!this.options.jwtToken) {\r\n const error = new Error('JWT token is required to enable push notifications.');\r\n this.emit('pushFailed', { reason: 'NO_TOKEN', error });\r\n return { ok: false, reason: 'NO_TOKEN', error };\r\n }\r\n\r\n if (!this.pushManager) {\r\n this._pushManager = new PushManager({\r\n apiClient: this.apiClient,\r\n projectId: this.options.projectId,\r\n firebaseConfig: options.firebaseConfig,\r\n vapidKey: options.vapidKey,\r\n serviceWorkerPath: options.serviceWorkerPath,\r\n logLevel: this.options.logLevel\r\n });\r\n\r\n // 포그라운드 메시지 콜백 — projectId 필터링 + activeRoom suppress + messageId dedup 후 client emit.\r\n // PushManager 자체는 EventEmitter 가 아니므로 콜백 오버라이드로 우회한다.\r\n //\r\n // dedup: 같은 messageId 의 푸시가 서버측 멱등 race 복구 / FCM 재전송 등으로 중복 도착할 수 있어\r\n // 5분 TTL Map 으로 한 번만 emit. setTimeout 으로 자동 cleanup.\r\n const PUSH_DEDUP_TTL_MS = 5 * 60 * 1000;\r\n this.pushManager._onForegroundMessage = (payload) => {\r\n const data = payload.data || {};\r\n\r\n if (data.projectId && data.projectId !== this.options.projectId) {\r\n this.logger.debug('다른 프로젝트 푸시 무시:', data.projectId);\r\n return;\r\n }\r\n\r\n const activeRoomId = this.chat?.getActiveRoom();\r\n\r\n if (activeRoomId && activeRoomId === data.roomId) {\r\n this.logger.debug('포그라운드 푸시 suppress (현재 방):', data.roomId);\r\n return;\r\n }\r\n\r\n // messageId 기반 dedup — 키가 없는 시스템 푸시 등은 통과.\r\n if (data.messageId) {\r\n if (!this._recentPushMessageIds) {\r\n this._recentPushMessageIds = new Map();\r\n }\r\n if (this._recentPushMessageIds.has(data.messageId)) {\r\n this.logger.debug('포그라운드 푸시 dedup:', data.messageId);\r\n return;\r\n }\r\n const timer = setTimeout(() => {\r\n this._recentPushMessageIds.delete(data.messageId);\r\n }, PUSH_DEDUP_TTL_MS);\r\n this._recentPushMessageIds.set(data.messageId, timer);\r\n }\r\n\r\n this.emit('pushNotification', {\r\n title: payload.notification?.title || data.title,\r\n body: payload.notification?.body || data.body,\r\n data\r\n });\r\n };\r\n }\r\n\r\n // 가드 1: 이미 활성화됨 — 중복 호출은 성공으로 간주.\r\n if (this.pushManager.isEnabled()) {\r\n this.logger.debug('웹 푸시가 이미 활성화되어 있어 중복 호출을 건너뜁니다.');\r\n return { ok: true, alreadyEnabled: true };\r\n }\r\n\r\n // 가드 2: 진행 중 — 같은 Promise 를 공유해 두 호출자가 동일 결과를 받도록 한다.\r\n if (this._pushEnablePromise) {\r\n this.logger.debug('웹 푸시 활성화가 이미 진행 중입니다.');\r\n return this._pushEnablePromise;\r\n }\r\n\r\n // 본 활성화 흐름 — throw 하지 않고 결과 객체로 통일.\r\n // try/catch 안에서 emit + return 모두 처리하여 fire-and-forget 호출자도 안전하게 한다.\r\n const promise = (async () => {\r\n try {\r\n await this.pushManager.enable();\r\n this.emit('pushEnabled');\r\n return { ok: true };\r\n } catch (error) {\r\n // PushError 면 분류 코드 사용, 아니면 'UNKNOWN'.\r\n const reason = (error && error.code) ? error.code : 'UNKNOWN';\r\n this.logger.warn('Failed to enable push notifications:', reason, error?.message);\r\n this.emit('pushFailed', { reason, error });\r\n return { ok: false, reason, error };\r\n }\r\n })();\r\n\r\n this._pushEnablePromise = promise;\r\n\r\n // self-clear — 본인이 현재 진행 중인 Promise 일 때만 null 처리 (race 방지).\r\n promise.finally(() => {\r\n if (this._pushEnablePromise === promise) {\r\n this._pushEnablePromise = null;\r\n }\r\n });\r\n\r\n return promise;\r\n };\r\n\r\n TalkFlowClient.prototype.consumePendingRoom = async function consumePendingRoom() {\r\n return PushManager.consumePendingRoom(this.options.projectId);\r\n };\r\n\r\n TalkFlowClient.prototype.setCurrentDeviceEnabled = function setCurrentDeviceEnabled(enabled) {\r\n return this.pushManager?.setCurrentDeviceEnabled(enabled);\r\n };\r\n\r\n TalkFlowClient.prototype.setDeviceEnabled = function setDeviceEnabled(deviceId, enabled) {\r\n return this.pushManager?.setDeviceEnabled(deviceId, enabled);\r\n };\r\n\r\n TalkFlowClient.prototype.getMyDevices = function getMyDevices() {\r\n return this.pushManager?.getMyDevices();\r\n };\r\n\r\n /**\r\n * 현재 브라우저의 푸시 권한 상태를 조회한다.\r\n *\r\n * <p>{@code enablePushNotifications()} 호출 <b>전에도</b> 사용 가능 — pushManager 인스턴스 불필요.\r\n * UI 분기 (granted → 자동 enable / default → 버튼 노출 / denied → 설정 가이드 / unsupported → 숨김) 에 사용한다.</p>\r\n *\r\n * @returns {'granted'|'denied'|'default'|'unsupported'}\r\n */\r\n TalkFlowClient.prototype.getPushPermissionState = function getPushPermissionState() {\r\n return PushManager.getPermissionState();\r\n };\r\n\r\n /**\r\n * SDK 내부 푸시 활성화 상태 (FCM 토큰 발급 + 서버 등록까지 완료 여부).\r\n *\r\n * <p>주의: 이 값은 SDK 인스턴스의 메모리 상태일 뿐, 브라우저 권한 상태와 다르다.\r\n * 페이지 새로고침 후에는 enable 호출 전까지 false. 권한 상태는 {@link #getPushPermissionState} 사용.</p>\r\n *\r\n * @returns {boolean}\r\n */\r\n TalkFlowClient.prototype.isPushEnabled = function isPushEnabled() {\r\n return this.pushManager?.isEnabled() ?? false;\r\n };\r\n\r\n /**\r\n * 현재 발급된 FCM 토큰. enable 호출 후에만 유효, 그 외에는 null.\r\n * @returns {string|null}\r\n */\r\n TalkFlowClient.prototype.getPushToken = function getPushToken() {\r\n return this.pushManager?.getToken() ?? null;\r\n };\r\n\r\n /**\r\n * 푸시 상태 초기화 — 포그라운드 리스너 / FCM 토큰 / SW projectId 등록을 해제한다.\r\n * 로그아웃 또는 사용자 전환 시 호출. (logout() 내부에서도 자동 호출되므로 일반 흐름에선 불필요.)\r\n */\r\n TalkFlowClient.prototype.resetPush = function resetPush() {\r\n this.pushManager?.reset();\r\n this._pushEnablePromise = null;\r\n };\r\n\r\n TalkFlowClient.prototype.setToken = async function setToken(token) {\r\n const { userId } = validateAndParseJWT(token, { validateExpiry: false });\r\n const needReinitialize = this.userId && this.userId !== userId;\r\n\r\n this.options.jwtToken = token;\r\n this._userId = userId;\r\n this.apiClient.setJwtToken(token);\r\n\r\n if (needReinitialize && this.connectionManager) {\r\n await this.disconnect();\r\n if (this.pushManager) {\r\n this.pushManager.reset();\r\n this._pushEnablePromise = null;\r\n }\r\n this.connectionManager = null;\r\n this._chat = null;\r\n this._webrtc = null;\r\n\r\n this._initializeSubClients();\r\n } else if (this.connectionManager) {\r\n this.connectionManager.updateToken(token);\r\n } else {\r\n this._initializeSubClients();\r\n }\r\n\r\n this.logger.info('JWT token set', { userId });\r\n this.emit('tokenSet', { userId });\r\n };\r\n\r\n TalkFlowClient.prototype.updateToken = async function updateToken(newToken) {\r\n await this.setToken(newToken);\r\n };\r\n}\r\n"],"names":["EventEmitter","constructor","this","_events","Map","on","event","listener","has","set","Set","get","add","off","once","onceWrapper","args","apply","_originalListener","listeners","l","delete","size","emit","data","forEach","error","console","removeAllListeners","clear","listenerCount","eventNames","Array","from","keys","ConnectionState","DISCONNECTED","CONNECTING","CONNECTED","RECONNECTING","ERROR","ErrorTypes","CONNECTION_FAILED","CONNECTION_LOST","CONNECTION_TIMEOUT","JWT_INVALID","JWT_EXPIRED","JWT_PARSE_FAILED","UNAUTHORIZED","API_ERROR","API_TIMEOUT","CHAT_ROOM_NOT_FOUND","CHAT_MESSAGE_FAILED","CHAT_SUBSCRIPTION_FAILED","MEDIA_ACCESS_DENIED","SCREEN_SHARE_DENIED","PEER_CONNECTION_FAILED","ICE_CONNECTION_FAILED","SIGNALING_FAILED","DEVICE_SWITCH_FAILED","ENUMERATE_DEVICES_FAILED","CALL_ROOM_NOT_FOUND","INVALID_STATE","UNKNOWN_ERROR","SignalTypes","CALL_OFFER","CALL_ANSWER","ICE_CANDIDATE","JOIN_ROOM","LEAVE_ROOM","PEER_JOINED","PEER_LEFT","VIDEO_STATE_CHANGED","AUDIO_STATE_CHANGED","SCREEN_SHARE_STARTED","SCREEN_SHARE_ENDED","CALL_REQUEST","CALL_ACCEPT","CALL_REJECT","CALL_CANCEL","CALL_END","CALL_INVITATION","CALL_BUSY","ChatRoomType","DIRECT","GROUP","PRIVATE_GROUP","TEAM","RoomListEventType","MESSAGE_RECEIVED","MESSAGE_DELETED","MESSAGE_UPDATED","ROOM_CREATED","ROOM_JOINED","ROOM_LEFT","ROOM_UPDATED","ROOM_KICKED","ROOM_BANNED","MESSAGE_RETENTION_CLEANUP","WebSocketPaths","SOCKJS_ENDPOINT","NATIVE_ENDPOINT","APP_PREFIX","TOPIC_PREFIX","QUEUE_PREFIX","USER_PREFIX","getChatDestination","roomId","getChatReadDestination","getChatTypingDestination","ROOM_LIST_USER_DESTINATION","getWebRTCDestination","getWebRTCUserDestination","CHAT_SEND","CHAT_READ","CHAT_TYPING","WEBRTC_SIGNAL","Environment","DEVELOPMENT","STAGING","PRODUCTION","Endpoints","serverUrl","wsEndpoint","DefaultConfig","environment","reconnectDelay","maxReconnectAttempts","heartbeatIncoming","heartbeatOutgoing","apiTimeout","iceServers","urls","logLevel","getServerUrl","env","LogLevel","DEBUG","INFO","WARN","NONE","Logger","level","prefix","setLevel","setPrefix","_format","Date","toISOString","debug","info","warn","ApiClient","options","baseUrl","replace","apiKey","projectId","jwtToken","timeout","logger","setJwtToken","token","_getHeaders","headers","startsWith","request","method","path","body","params","url","searchParams","URLSearchParams","Object","entries","key","value","append","queryString","toString","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","JSON","stringify","undefined","signal","clearTimeout","name","timeoutError","Error","code","contentType","includes","json","text","ok","message","status","post","put","patch","upload","file","fieldName","onProgress","Promise","resolve","reject","xhr","XMLHttpRequest","form","FormData","open","setRequestHeader","onprogress","e","lengthComputable","loaded","total","percent","Math","round","onAbort","aborted","addEventListener","cleanupSignal","removeEventListener","onload","parsed","responseText","getResponseHeader","parse","onerror","err","ontimeout","onabort","send","stripBearer","base64UrlDecode","str","base64","padding","length","repeat","binaryString","atob","bytes","Uint8Array","c","charCodeAt","TextDecoder","decode","validateAndParseJWT","bufferSeconds","validateExpiry","parts","split","payload","originalError","exp","expiryTime","bufferTime","now","expiredAt","iat","clockSkew","userId","sub","user_id","extractUserIdFromJWT","isJWTExpired","BYTE","FrameImpl","_body","isBinaryBody","_binaryBody","binaryBody","TextEncoder","encode","command","escapeHeaderValues","skipContentLengthHeader","assign","fromRawFrame","rawFrame","trim","header","reverse","indexOf","hdrValueUnEscape","serializeCmdAndHeaders","serialize","cmdAndHeaders","toUnit8Array","buffer","lines","push","hdrValueEscape","isBodyEmpty","bodyLength","join","sizeOfUTF8","s","uint8CmdAndHeaders","nullTerminator","uint8Frame","marshall","Parser","onFrame","onIncomingPing","_encoder","_decoder","_token","_initState","parseChunk","segment","appendMissingNULLonIncoming","chunk","chunkWithNull","i","byte","_onByte","_collectFrame","_collectCommand","_reinjectByte","_results","_consumeTokenAsUTF8","_collectHeaders","_consumeByte","_collectHeaderKey","_setupCollectBody","_headerKey","_collectHeaderValue","contentLengthHeader","filter","_bodyBytesRemaining","parseInt","_collectBodyFixedSize","_collectBodyNullTerminated","_retrievedBody","_consumeTokenAsRaw","log","rawResult","StompSocketState","ActivationState","ReconnectionTimeMode","TickerStrategy","Ticker","_interval","_strategy","Interval","_debug","_workerScript","start","tick","stop","shouldUseWorker","runWorker","runInterval","disposeWorker","disposeInterval","Worker","_worker","URL","createObjectURL","Blob","type","onmessage","_timer","startTime","setInterval","terminate","clearInterval","Versions","versions","supportedVersions","protocolVersions","map","x","V1_0","V1_1","V1_2","default","StompHandler","connectedVersion","_connectedVersion","connected","_connected","_client","_webSocket","config","_serverFrameHandlers","frame","server","version","_escapeHeaderValues","_setupHeartbeat","onConnect","MESSAGE","subscription","onReceive","_subscriptions","onUnhandledMessage","client","messageId","ack","nack","RECEIPT","callback","_receiptWatchers","onUnhandledReceipt","onStompError","_counter","_partialData","_lastServerActivityTS","stompVersions","connectHeaders","disconnectHeaders","heartbeatToleranceMultiplier","heartbeatGracePeriods","splitLargeFrames","maxWebSocketChunkSize","forceBinaryWSFrames","logRawCommunication","discardWebsocketOnCommFailure","onDisconnect","onWebSocketClose","onWebSocketError","onUnhandledFrame","onHeartbeatReceived","onHeartbeatLost","parser","evt","rawChunkAsString","ArrayBuffer","onclose","closeEvent","_cleanUp","errorEvent","onopen","_transmit","serverOutgoing","serverIncoming","v","ttl","max","_pinger","heartbeatStrategy","readyState","OPEN","_ponger","delta","_closeOrDiscardWebsocket","discardWebsocket","_closeWebsocket","forceDisconnect","close","webSocket","msg","noOp","ts","id","random","substring","origOnClose","delay","getTime","reason","call","wasClean","rawChunk","out","dispose","receipt","watchForReceipt","publish","destination","hdrs","receiptId","subscribe","unsubscribe","begin","transactionId","txId","transaction","commit","subscriptionId","Client","_stompHandler","_disconnectHeaders","active","state","ACTIVE","_changeState","onChangeState","conf","connectionTimeout","_nextReconnectDelay","maxReconnectDelay","reconnectTimeMode","LINEAR","INACTIVE","beforeConnect","configure","activate","_activate","_connect","DEACTIVATING","deactivate","then","_connectionWatcher","_createWebSocket","_disposeStompHandler","_schedule_reconnect","webSocketFactory","brokerURL","WebSocket","binaryType","_reconnector","EXPONENTIAL","min","force","needToDispose","retPromise","CLOSED","origOnWebSocketClose","_checkConnection","TypeError","crypto","global","getRandomValues","browserCrypto","randomBytes","floor","_randomStringChars","string","ret","substr","number","numberString","t","slice","require$$0","onUnload","afterUnload","isChromePackagedApp","chrome","app","runtime","module","exports","attachEvent","document","detachEvent","unloadAdd","ref","triggerUnloadCallbacks","unloadDel","required","requiresPort","port","protocol","qs","prototype","hasOwnProperty","input","decodeURIComponent","encodeURIComponent","querystringify_1","obj","pairs","isNaN","query","part","result","exec","require$$1","controlOrWhitespace","CRHTLF","slashes","protocolre","windowsDriveLetter","trimLeft","rules","address","isSpecial","NaN","ignore","hash","lolcation","loc","location","window","self","finaldestination","Url","unescape","pathname","test","href","scheme","extractProtocol","rest","match","toLowerCase","forwardSlashes","otherSlashes","slashesCount","relative","extracted","instruction","index","instructions","lastIndexOf","charAt","base","concat","last","unshift","up","splice","host","hostname","username","password","auth","origin","fn","pop","char","ins","urlParse","getOrigin","p","isOriginEqual","a","b","isSchemeEqual","addPath","addQuery","q","isLoopbackAddr","addr","create","inherits_browserModule","ctor","superCtor","super_","enumerable","writable","configurable","TempCtor","EventTarget","_listeners","eventType","arr","idx","dispatchEvent","arguments","eventtarget","inherits","fired","g","removeListener","ai","addListener","emitter","utils","urlUtils","require$$2","require$$3","WebsocketDriver","Driver","MozWebSocket","websocketModule","require$$4","WebSocketTransport","transUrl","enabled","ws","unloadRef","_cleanup","transportName","roundTrips","websocket","BufferedSender","sender","sendBuffer","sendStop","sendSchedule","sendScheduleWait","tref","bufferedSender","Polling","Receiver","receiveUrl","AjaxObject","_scheduleReceiver","poll","pollIsClosing","polling","SenderReceiver","urlSuffix","senderFunc","pollUrl","senderReceiver","AjaxBasedTransport","opt","ajaxUrl","xo","createAjaxSender","ajaxBased","XhrReceiver","bufferPosition","_chunkHandler","bind","buf","XHR","AbstractXHRObject","opts","_start","noCredentials","supportsCORS","withCredentials","onreadystatechange","axo","cors","ignored","abstractXhr","XhrDriver","XHRCorsObject","xhrCors","XHRLocalObject","xhrLocal","browser","isOpera","navigator","userAgent","isKonqueror","hasDomain","domain","require$$5","XhrStreamingTransport","nullOrigin","needBody","xhrStreaming","eventUtils","XDRObject","xdr","XDomainRequest","_error","XdrStreamingTransport","cookie_needed","sameScheme","xdrStreaming","eventsource","EventSource","EventSourceReceiver","EventSourceDriver","es","decodeURI","_close","EventSourceTransport","WPrefix","currentWindowId","polluteGlobalNamespace","postMessage","parent","windowId","createIframe","iframeUrl","errorCallback","iframe","createElement","unattach","cleanup","parentNode","removeChild","src","style","display","position","appendChild","contentWindow","createHtmlfile","doc","CollectGarbage","r","write","parentWindow","iframeEnabled","iframeUtils","require$$6","IframeTransport","transport","iframeObj","onmessageCallback","_message","iframeMessage","cdata","object","isObject","extend","source","prop","objectUtils","iframeWrap","IframeWrapTransport","iframeInfo","sameOrigin","facadeTransport","HtmlfileReceiver","htmlfileEnabled","constructFunc","htmlfile","HtmlFileTransport","XhrPollingTransport","xhrPolling","XdrPollingTransport","xdrPolling","area","jsonp","enctype","acceptCharset","target","action","submit","completed","JsonpReceiver","urlWithId","_callback","_createScript","_abort","scriptErrorTimeout","aborting","script2","script","onclick","_scriptError","errorTimer","loadedOkay","charset","htmlFor","async","head","getElementsByTagName","insertBefore","firstChild","jsonpSender","JsonPTransport","jsonpPolling","transportList","require$$7","require$$8","defineProperty","ArrayPrototype","ObjectPrototype","FunctionPrototype","Function","StringPrototype","String","array_slice","_toString","isFunction","val","isString","supportsDescriptors","forceAssign","defineProperties","toObject","o","Empty","that","boundLength","boundArgs","bound","isArray","properlyBoxesNonStrict","properlyBoxesStrict","boxedString","splitString","fun","thisp","_","__","context","hasFirefox2IndexOfBug","sought","n","abs","compliantExecNpcg","string_split","separator","limit","separator2","lastIndex","lastLength","output","flags","ignoreCase","multiline","extended","sticky","lastLastIndex","RegExp","string_substr","hasNegativeSubstrBug","extraLookup","extraEscapable","_escape","quote","quoted","escapable","unrolled","fromCharCode","unrollLookup","availableTransports","filterToEnabled","transportsWhitelist","transports","main","facade","trans","logObject","levelExists","Event","initEvent","canBubble","cancelable","bubbles","timeStamp","stopPropagation","preventDefault","CAPTURING_PHASE","AT_TARGET","BUBBLING_PHASE","InfoAjax","t0","rtt","infoAjax","InfoReceiverIframe","ir","infoIframeReceiver","XDR","XHRCors","XHRLocal","XHRFake","to","xhrFake","InfoIframe","go","ifr","d","infoIframe","InfoReceiver","urlInfo","doXhr","_getReceiver","timeoutRef","infoReceiver","FacadeJS","_transport","_transportMessage","_transportClose","_send","InfoIframeReceiver","iframeBootstrap","SockJS","parentOrigin","transportMap","at","bootstrap_iframe","escape","require$$9","require$$10","require$$11","require$$12","require$$13","CloseEvent","require$$14","TransportMessageEvent","transMessage","require$$15","require$$16","protocols","extensions","protocols_whitelist","_transportsWhitelist","_transportOptions","transportOptions","_timeout","sessionId","_generateSessionId","_server","parsedUrl","SyntaxError","secure","sortedProtocols","sort","proto","_origin","_urlInfo","_ir","_receiveInfo","userSetCode","CLOSING","require$$17","_rto","countRTO","_transUrl","base_url","enabledTransports","_transports","Transport","shift","timeoutMs","_transportTimeoutId","_transportTimeout","transportUrl","transportObj","content","_open","forceFail","require$$18","entry","_sockjs_onload","getStompClient","StompJs","StompClient","getSockJS","ConnectionManager","super","useSockJS","stompClient","reconnectAttempts","subscriptions","pendingSubscriptions","_setState","prevState","_getWebSocketUrl","endpoint","_createStompClient","wsUrl","Authorization","_onConnect","_onDisconnect","_onStompError","_onWebSocketClose","_onWebSocketError","wsProtocol","wsHost","connect","_connectResolve","_connectReject","_drainPendingSubscriptions","_restoreSubscriptions","_processPendingSubscriptions","_handleReconnect","attempt","previousSubscriptions","_subscribeInternal","unsubscribeAll","updateToken","disconnect","pending","isConnected","getState","setLogLevel","destroy","ChatClient","connectionManager","apiClient","subscribedRooms","roomListSubscribed","_activeRoomId","_typingTimers","_assistantTypingTimers","_assistantTypingTimeoutMs","_assistantTypingUserId","_assistantTypingUserName","_seenChatMessageIdsByRoom","_seenRoomListMessageIdsByRoom","_maxSeenPerRoom","_shouldDedupMessage","bucket","seen","oldestKey","next","getRooms","lastId","lastSortValue","getRoom","enterRoom","wasAlreadySubscribed","subscribeRoom","setActiveRoom","getActiveRoom","clearActiveRoom","unsubscribeRoom","getRoomInfo","setMyRoomLanguage","language","createOneToOneRoom","friendId","createGroupRoom","roomType","roomName","description","invitedUserIds","messageRetentionHours","invitedAssistantPersonaIds","assistantMode","roomAiType","engagementIntensity","getAssistants","_unwrapSuccessResponse","getRoomAiMeta","rateAssistantMessage","rating","comment","Number","isInteger","summarizeWithAssistant","personaId","format","messageCount","translateWithAssistant","targetLang","sourceMessageId","getRoomPmPrompt","upsertRoomPmPrompt","activateRoomPmPrompt","deactivateRoomPmPrompt","getRoomPmPromptVersions","activateRoomPmPromptVersion","previewRoomPmPrompt","joinGroupRoom","leaveRoom","updateGroupRoom","inviteToGroupRoom","userIdsOrData","userIds","assistantPersonaIds","kickMember","banMember","unbanMember","getBannedMembers","pinMessage","unpinMessage","toggleReaction","emoji","getAvailableGroupRooms","getAllGroupRooms","getMessages","page","m","_normalizeMessageTimestamps","fetchLinkPreview","normalizedUrl","sendMessage","_generateMessageId","_sendMessageWithId","sendMessageOptimistic","baseMessageId","messageIds","_predictMessageIdsFromData","promise","_attachOptimisticMetadata","stopTyping","fileInfos","separateFiles","replyToMessageId","messages","sendTextMessage","sendTextMessageOptimistic","uploadFile","sendFileMessage","files","fileArray","_normalizeFileArray","metas","_uploadFiles","_applyFileMetadata","metadata","sendFileMessageOptimistic","_predictMessageIds","_predictGroupCount","_hasTextMessage","all","onUploadProgress","fileIndex","meta","itemMetadata","sendReply","sendReplyOptimistic","deleteMessage","deleteType","editMessage","_classifyFileType","mimeType","mime","typesSeen","fileType","groupCount","hasMessage","expectedCount","ids","separate","predictedMessageIds","actualMessageIds","predictionMatched","every","predicted","actual","optimistic","randomUUID","sentAt","_toEpochMillis","editedAt","replyTo","chatDestination","_handleChatMessage","readDestination","_handleReadEvent","typingDestination","_handleTypingEvent","memberJoinedHandler","members","participantCount","activeParticipantCount","timestamp","memberLeftHandler","subscribedAt","_clearTypingTimer","_clearAssistantTypingTimer","unsubscribeAllRooms","subscribeRoomList","_handleRoomListEvent","unsubscribeRoomList","isRoomListSubscribed","markAsRead","translations","sourceLang","translationsOf","senderType","userName","typing","actorId","_isSelfInMembers","cutoffTime","resolvedRoomId","events","remainingUnreadCount","startTyping","existingTimer","timer","some","_startAssistantTypingTimer","getSubscribedRooms","isSubscribed","MediaStreamManager","localStream","screenStream","videoEnabled","audioEnabled","_deviceChangeHandler","getUserMedia","constraints","video","audio","mediaDevices","stream","_getMediaErrorMessage","getDisplayMedia","getVideoTracks","onended","toggleVideo","videoTracks","track","toggleAudio","audioTracks","getAudioTracks","setVideoEnabled","setAudioEnabled","getLocalStream","getScreenStream","isVideoEnabled","isAudioEnabled","stopAll","getTracks","replaceTrack","oldTrack","newTrack","removeTrack","addTrack","kind","getTrack","tracks","getDevices","devices","enumerateDevices","videoInputs","audioInputs","audioOutputs","switchDevice","deviceId","exact","startDeviceChangeDetection","stopDeviceChangeDetection","getStreamInfo","hasStream","label","muted","applyVideoConstraints","videoTrack","applyConstraints","getVideoSettings","getSettings","getAudioSettings","audioTrack","PeerConnectionManager","peers","setLocalStream","peer","_addTracksToConnection","connection","setIceServers","servers","createPeerConnection","peerId","polite","iceCandidatePoolSize","RTCPeerConnection","peerState","makingOffer","ignoreOffer","isSettingRemoteAnswerPending","skipAutoNegotiation","_setupConnectionHandlers","onicecandidate","candidate","oniceconnectionstatechange","iceConnectionState","onconnectionstatechange","connectionState","onnegotiationneeded","setLocalDescription","localDescription","ontrack","streams","ondatachannel","channel","existingSenders","getSenders","find","createOffer","offer","createAnswer","answer","handleRemoteDescription","offerCollision","signalingState","setRemoteDescription","addIceCandidate","remoteDescription","getPeerConnection","getPeerIds","closePeerConnection","closeAllPeerConnections","promises","catch","getStats","getConnectionSummary","summary","WebRTCClient","mediaManager","peerManager","currentRoom","isGroupCall","participants","_iceServersFetched","_waitingForCallAccept","_pendingCallTarget","_pendingIceCandidates","_setupEventHandlers","initializeIceServers","getTurnCredentials","_setFallbackIceServers","_sendMediaState","_sendSignal","receiverId","sdp","createCallRoom","getCallRoom","joinCallRoomApi","leaveCallRoomApi","enableIncomingCalls","_incomingCallsEnabled","_subscribeToDirectSignaling","disableIncomingCalls","directDestination","isIncomingCallsEnabled","startCall","isGroup","mediaConstraints","_subscribeToSignaling","callUser","targetUserId","acceptCall","callerId","rejectCall","cancelCall","endCall","_unsubscribeFromSignaling","startScreenShare","localVideoTrack","currentVideoTrack","stopScreenShare","_handleSignal","mediaState","senderId","_handleUserJoined","_handleUserLeft","_handleOffer","_handleAnswer","_handleIceCandidate","_handleMediaState","isInCall","callRoomId","title","hostUserId","maxParticipants","createdAt","_handleCallAccepted","_handleCallEnded","joinedAt","_processPendingIceCandidates","_queueIceCandidate","RTCIceCandidate","candidates","getCurrentRoom","getParticipants","getMediaState","streamInfo","PushErrorCode","freeze","UNSUPPORTED_BROWSER","FIREBASE_NOT_INSTALLED","SW_REGISTER_FAILED","PERMISSION_DENIED","TOKEN_FAILED","SERVER_REGISTER_FAILED","PushError","cause","DEFAULT_FIREBASE_CONFIG","authDomain","storageBucket","messagingSenderId","appId","PENDING_NAVIGATION_STORE_NAME","PENDING_NAVIGATION_LEGACY_KEY","DEVICE_STORE_NAME","DEVICE_ID_KEY","LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY","openPendingNavigationDb","indexedDB","onupgradeneeded","db","objectStoreNames","contains","createObjectStore","keyPath","onsuccess","readDeviceIdFromIndexedDb","tx","objectStore","record","oncomplete","writeDeviceIdToIndexedDb","generateDeviceId","consumePendingRoomFromIndexedDB","primaryKey","PENDING_NAVIGATION_KEY_PREFIX","buildPendingNavigationKey","shouldTryLegacyFallback","store","pendingRoomId","primaryRequest","legacyRequest","legacyRecord","PushManager","firebaseConfig","vapidKey","serviceWorkerPath","_messaging","_currentToken","_enabled","_enablePromise","_foregroundMessageUnsubscribe","_swRegistration","_projectRegisteredToSW","_visibilityHandler","_deviceIdCache","consumePendingRoom","getPermissionState","Notification","permission","enable","_enableInternal","firebaseApp","firebaseMessaging","import","getApp","initializeApp","registration","_registerServiceWorker","requestPermission","getMessaging","tokenOptions","serviceWorkerRegistration","getToken","_registerTokenToServer","onMessage","_onForegroundMessage","_registerProjectToSW","_installVisibilityReRegister","update","swVersion","_getSWVersion","serviceWorker","register","updateViaCache","_getDeviceId","deviceToken","deviceType","localStorage","legacy","getItem","removeItem","fallbackId","reset","_uninstallVisibilityReRegister","_unregisterProjectFromSW","sw","ready","visibilityState","isEnabled","getMyDevices","setDeviceEnabled","setCurrentDeviceEnabled","readyRegistration","waiting","installing","MessageChannel","port1","port2","CONNECTION_EVENT_MAP","CHAT_EVENT_MAP","WEBRTC_EVENT_MAP","forwardMappedEvents","eventMap","sourceEvent","targetEvent","CHAT_DELEGATE_METHODS","WEBRTC_DELEGATE_METHODS","defineDelegates","targetKey","methods","TalkFlowClient","_validateOptions","autoSubscribeRoomList","_initialized","_state","_userId","_chat","_webrtc","_pushManager","_pushEnablePromise","_initializeSubClients","hasToken","chat","webrtc","pushManager","_setupEventForwarding","initializeSubClients","registerUser","userData","preferredLanguage","detected","detectBrowserLanguage","accessToken","setToken","updateMyInfo","_checkToken","setPreferredLanguage","displayText","myLang","checkUserExists","projectUserId","getUsers","searchUsers","keyword","getUserId","isInitialized","isReady","getStatus","initialized","jwt","enablePush","enablePushNotifications","logout","PUSH_DEDUP_TTL_MS","activeRoomId","_recentPushMessageIds","notification","alreadyEnabled","finally","getPushPermissionState","isPushEnabled","getPushToken","resetPush","needReinitialize","newToken","applySessionMethods","applyDelegatedMethods","GENERAL","PEOPLE_ONLY","CALL_ONLY","TEXT","IMAGE","FILE","VIDEO","AUDIO","SYSTEM","QUIET","NORMAL","LEGAL_ADVISOR","MARKETING","PRODUCT_PLANNING","HR","FINANCE","CUSTOMER_SUPPORT","SALES","ENGINEERING","DATA_ANALYST","PROJECT_MANAGEMENT","RESEARCH","TRANSLATION","DESIGN","PM","SUPER_ADMIN","PROJECT_ADMIN","ROOM_OWNER","PROJECT","ROOM","PERSONA_MULTI","PM_BACKSTAGE","USER","ASSISTANT","MINUTES","SHORT","TIMELINE","ACTIONS","OPTIONS","Infinity"],"mappings":"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e,eAChB,CAAC,qBAAsB,sBACvB,CAAC,mBAAoB,oBACrB,CAAC,eAAgB,gBACjB,CAAC,oBAAqB,qBACtB,CAAC,cAAe,eAChB,CAAC,YAAa,aACd,CAAC,gBAAiB,iBAClB,CAAC,eAAgB,gBACjB,CAAC,eAAgB,gBACjB,CAAC,gBAAiB,iBAClB,CAAC,WAAY,YACb,CAAC,eAAgB,gBACjB,CAAC,wBAAyB,yBAC1B,CAAC,iBAAkB,kBACnB,CAAC,aAAc,cACf,CAAC,WAAY,YACb,CAAC,kBAAmB,mBACpB,CAAC,wBAAyB,yBAC1B,CAAC,gBAAiB,iBAClB,CAAC,mBAAoB,oBACrB,CAAC,aAAc,cACf,CAAC,QAAS,gBAGd,SAASC,GAAoBpvB,EAAQqB,EAAQguB,GACzCA,EAASrgD,QAAQ,EAAEsgD,EAAaC,MAC5BvvB,EAAOpyB,GAAG0hD,EAAcvgD,IACpBsyB,EAAOvyB,KAAKygD,EAAaxgD,MAGrC,CCvEA,MAAMygD,GAAwB,CAC1B,WACA,UACA,cACA,oBACA,qBACA,kBACA,gBACA,YACA,kBACA,oBACA,aACA,YACA,cACA,mBACA,yBACA,mBACA,YACA,cACA,mBACA,cACA,wBACA,kBACA,4BACA,YACA,sBACA,aACA,kBACA,4BACA,cACA,gBACA,aACA,aACA,eACA,iBACA,gBACA,kBACA,sBACA,gBACA,kBACA,gBACA,oBACA,sBACA,uBACA,qBACA,eACA,cACA,aACA,gBACA,gBACA,uBACA,yBACA,yBACA,kBACA,qBACA,uBACA,yBACA,0BACA,8BACA,uBAGEC,GAA0B,CAC5B,uBACA,qBACA,iBACA,cACA,kBACA,mBACA,sBACA,uBACA,yBACA,YACA,WACA,aACA,aACA,aACA,UACA,cACA,cACA,kBACA,kBACA,mBACA,kBACA,iBACA,aACA,eACA,6BACA,4BACA,wBACA,mBACA,mBACA,WACA,iBACA,kBACA,gBACA,uBACA,YAGJ,SAASC,GAAgB1+B,EAAW2+B,EAAWC,GAC3CA,EAAQ5gD,QAAS8I,IACbkZ,EAAUlZ,GAAU,YAA4BvJ,GAC5C,OAAOd,KAAKkiD,GAAW73C,MAAWvJ,EACtC,GAER,CC3FA,MAAMshD,WAAuBtiD,EAwCzBC,WAAAA,CAAYwJ,GACRo3B,QAGA3gC,KAAKqiD,iBAAiB94C,GAItB,MAAMlB,EAAMkB,EAAQlB,KAAOX,EAAcC,YACnCH,GAAa+B,EAAQ/B,WAAaY,EAAaC,IAAMoB,QAAQ,MAAO,IAE1EzJ,KAAKuJ,QAAU,CACX/B,YACAa,MACAqB,OAAQH,EAAQG,OAChBC,UAAWJ,EAAQI,UACnBC,SAAUL,EAAQK,UAAY,KAC9Bg3B,WAAiC,IAAtBr3B,EAAQq3B,UACnBh5B,eAAgB2B,EAAQ3B,gBAAkBF,EAAcE,eACxDC,qBAAsB0B,EAAQ1B,sBAAwBH,EAAcG,qBACpEI,WAAYsB,EAAQtB,YAAcP,EAAcO,WAIhDq6C,uBAAyD,IAAlC/4C,EAAQ+4C,sBAC/Bn6C,cAA+ByD,IAArBrC,EAAQpB,SAAyBoB,EAAQpB,SAAWG,EAASG,MAI3EzI,KAAK8J,OAAS,IAAInB,EAAO3I,KAAKuJ,QAAQpB,SAAU,kBAGhDnI,KAAKuiD,cAAe,EACpBviD,KAAKwiD,OAASvgD,EAAgBC,aAM9BlC,KAAKyiD,QAAU,KAGXziD,KAAKuJ,QAAQK,WACb5J,KAAKyiD,QAAUxxC,EAAqBjR,KAAKuJ,QAAQK,WAIrD5J,KAAKmjC,UAAY,IAAI75B,EAAU,CAC3BE,QAASxJ,KAAKuJ,QAAQ/B,UACtBkC,OAAQ1J,KAAKuJ,QAAQG,OACrBC,UAAW3J,KAAKuJ,QAAQI,UACxBC,SAAU5J,KAAKuJ,QAAQK,SACvBzB,SAAUnI,KAAKuJ,QAAQpB,WAI3BnI,KAAKkjC,kBAAoB,KACzBljC,KAAK0iD,MAAQ,KACb1iD,KAAK2iD,QAAU,KACf3iD,KAAK4iD,aAAe,KACpB5iD,KAAK6iD,mBAAqB,KAGtB7iD,KAAKuJ,QAAQK,UAAY5J,KAAKyiD,SAC9BziD,KAAK8iD,wBAGT9iD,KAAKuiD,cAAe,EACpBviD,KAAK8J,OAAOV,KAAK,6BAA8B,CAC3C0H,OAAQ9Q,KAAKyiD,QACbM,WAAY/iD,KAAKuJ,QAAQK,UAEjC,CAKA,QAAIo5C,GAAS,OAAOhjD,KAAK0iD,KAAO,CAGhC,UAAIO,GAAW,OAAOjjD,KAAK2iD,OAAS,CAGpC,eAAIO,GAAgB,OAAOljD,KAAK4iD,YAAc,CAG9C,UAAI9xC,GAAW,OAAO9Q,KAAKyiD,OAAS,CAQpCJ,gBAAAA,CAAiB94C,GACb,IAAKA,EACD,MAAM,IAAI0C,MAAM,wBAEpB,IAAK1C,EAAQG,OACT,MAAM,IAAIuC,MAAM,sBAEpB,IAAK1C,EAAQI,UACT,MAAM,IAAIsC,MAAM,yBAMpB,GAAI1C,EAAQG,OAAOS,WAAW,OAAQ,CAClC,MAAM9B,EAAMkB,EAAQlB,KAAO,aACf,gBAARA,EACA5G,QAAQ4H,KACJ,8FAIJ5H,QAAQD,MACJ,4CAA8C6G,EAA9C,0JAMZ,CACJ,CAMAy6C,qBAAAA,ICnLG,SAA8BvpC,GACjC,IAAIA,EAAO2pB,kBAAX,CAIA,IAAK3pB,EAAOzI,OACR,MAAM,IAAI7E,MAAM,6EAGpBsN,EAAO2pB,kBAAoB,IAAIxC,GAAkB,CAC7Cl5B,UAAW+R,EAAOhQ,QAAQ/B,UAC1BoC,SAAU2P,EAAOhQ,QAAQK,SACzBF,OAAQ6P,EAAOhQ,QAAQG,OACvBC,UAAW4P,EAAOhQ,QAAQI,UAC1Bi3B,UAAWrnB,EAAOhQ,QAAQq3B,UAC1Bh5B,eAAgB2R,EAAOhQ,QAAQ3B,eAC/BC,qBAAsB0R,EAAOhQ,QAAQ1B,qBACrCM,SAAUoR,EAAOhQ,QAAQpB,WAG7BoR,EAAOmpC,MAAQ,IAAIzf,GAAW,CAC1BC,kBAAmB3pB,EAAO2pB,kBAC1BC,UAAW5pB,EAAO4pB,UAClBryB,OAAQyI,EAAOkpC,QACft6C,SAAUoR,EAAOhQ,QAAQpB,WAG7BoR,EAAOopC,QAAU,IAAIzM,GAAa,CAC9BhT,kBAAmB3pB,EAAO2pB,kBAC1BC,UAAW5pB,EAAO4pB,UAClBryB,OAAQyI,EAAOkpC,QACfx6C,WAAYsR,EAAOhQ,QAAQtB,WAC3BE,SAAUoR,EAAOhQ,QAAQpB,WAG7BoR,EAAO4pC,wBACP5pC,EAAOzP,OAAOX,MAAM,0BAjCpB,CAkCJ,CD+IQi6C,CAAqBpjD,KACzB,CAMAmjD,qBAAAA,GFxHG,IAA8B5pC,KEyHRvZ,MFxHbkjC,oBAIZ3pB,EAAO2pB,kBAAkB/iC,GAAG,cAAe,EAAG4e,QAAOmiB,gBACjD3nB,EAAOipC,OAASzjC,EAChBxF,EAAOlY,KAAK,cAAe,CAAE0d,QAAOmiB,gBAGxCygB,GAAoBpoC,EAAO2pB,kBAAmB3pB,EAAQioC,IAElDjoC,EAAOypC,MACPrB,GAAoBpoC,EAAOypC,KAAMzpC,EAAQkoC,IAGzCloC,EAAO0pC,QACPtB,GAAoBpoC,EAAO0pC,OAAQ1pC,EAAQmoC,IEyG/C,CAMA7e,WAAAA,GACI,QAAO7iC,KAAKkjC,mBAAoBljC,KAAKkjC,kBAAkBL,aAC3D,CAMAC,QAAAA,GACI,OAAO9iC,KAAKwiD,MAChB,CAMAO,QAAAA,GACI,QAAS/iD,KAAKuJ,QAAQK,QAC1B,CAqCA,kBAAMy5C,CAAaC,GAGf,MAAMjzC,EAAU,IAAKizC,GACrB,QAAkC13C,IAA9ByE,EAAQkzC,kBAAiC,CACzC,MAAMC,EAAWpB,GAAeqB,wBAC5BD,IACAnzC,EAAQkzC,kBAAoBC,EAEpC,CAEA,MAAMh4C,QAAiBxL,KAAKmjC,UAAUz2B,KAAK,qBAAsB2D,GAOjE,OALI7E,EAASlK,MAAQkK,EAASlK,KAAKoiD,oBACzB1jD,KAAK2jD,SAASn4C,EAASlK,KAAKoiD,aAClC1jD,KAAK8J,OAAOV,KAAK,uCAGdoC,CACX,CAWA,kBAAMo4C,CAAaN,GAEf,OADAtjD,KAAK6jD,cACE7jD,KAAKmjC,UAAUx2B,IAAI,uBAAwB22C,EACtD,CAOA,0BAAMQ,CAAqB7e,GACvB,OAAOjlC,KAAK4jD,aAAa,CAAEL,kBAAmBte,GAClD,CAOA,4BAAOwe,GACH,MAAyB,oBAAdr1B,WAA8BA,UAAU6W,UAG5C7W,UAAU6W,SAAShzB,OAAO+T,eAFtB,IAGf,CAUA,kBAAO+9B,CAAYv3C,EAASw3C,GACxB,OAAIx3C,GAAWA,EAAQqhC,cAAgBmW,GAAUx3C,EAAQqhC,aAAamW,GAC3Dx3C,EAAQqhC,aAAamW,GAEzBx3C,EAAUA,EAAQwzB,QAAU,IACvC,CAOA,qBAAMikB,CAAgBC,GAClB,OAAOlkD,KAAKmjC,UAAU1iC,IAAI,iBAAiByjD,WAC/C,CAUA,cAAMC,CAAS35C,EAAS,IACpBxK,KAAK6jD,cACL,MAAMziD,KAAEA,EAAO,GAAEijC,OAAEA,EAAMC,cAAEA,GAAkB95B,EAC7C,OAAOxK,KAAKmjC,UAAU1iC,IAAI,gBAAiB,CAAEW,OAAMijC,SAAQC,iBAC/D,CASA,iBAAM8f,CAAY55C,GACdxK,KAAK6jD,cACL,MAAMQ,QAAEA,EAAOpsB,MAAEA,EAAQ,IAAOztB,EAChC,OAAOxK,KAAKmjC,UAAU1iC,IAAI,uBAAwB,CAAE4jD,UAASpsB,SACjE,CAOA4rB,WAAAA,GACI,IAAK7jD,KAAKuJ,QAAQK,SACd,MAAM,IAAIqC,MACN,4KAKZ,CAQAq4C,SAAAA,GACI,OAAOtkD,KAAK8Q,MAChB,CAMAyzC,aAAAA,GACI,OAAOvkD,KAAKuiD,YAChB,CAMAiC,OAAAA,GACI,OAAOxkD,KAAKuiD,gBAAkBviD,KAAKkjC,iBACvC,CAMAH,WAAAA,CAAYn6B,GACR5I,KAAKuJ,QAAQpB,SAAWS,EACxB5I,KAAK8J,OAAOhB,SAASF,GACrB5I,KAAKmjC,UAAUr5B,QAAQhB,SAASF,GAE5B5I,KAAKkjC,mBACLljC,KAAKkjC,kBAAkBH,YAAYn6B,GAEnC5I,KAAKgjD,MACLhjD,KAAKgjD,KAAKjgB,YAAYn6B,GAEtB5I,KAAKijD,QACLjjD,KAAKijD,OAAOlgB,YAAYn6B,EAEhC,CAMA67C,SAAAA,GACI,MAAO,CACHC,YAAa1kD,KAAKuiD,aAClB3B,MAAO5gD,KAAKwkD,UACZzB,SAAU/iD,KAAK+iD,WACf3O,gBAAiBp0C,KAAKwiD,OACtB3f,YAAa7iC,KAAK6iC,cAClB/xB,OAAQ9Q,KAAK8Q,OACbkyC,KAAMhjD,KAAKgjD,KAAO,CACd5f,gBAAiBpjC,KAAKgjD,KAAKlU,sBAC3B,KACJmU,OAAQjjD,KAAKijD,OAAS,CAClB1J,SAAUv5C,KAAKijD,OAAO1J,WACtBlD,YAAar2C,KAAKijD,OAAO7I,iBACzB7D,aAAcv2C,KAAKijD,OAAO5I,kBAC1BtB,WAAY/4C,KAAKijD,OAAO3I,iBACxB,KAEZ,CAKA,aAAMtX,SACIhjC,KAAK2iC,aAEP3iC,KAAKgjD,MACLhjD,KAAKgjD,KAAKhgB,UAEVhjC,KAAKijD,QACLjjD,KAAKijD,OAAOjgB,UAEZhjC,KAAKkjC,yBACCljC,KAAKkjC,kBAAkBF,UAGjChjC,KAAK0B,qBAEL1B,KAAKuiD,cAAe,EACpBviD,KAAK8J,OAAOV,KAAK,2BACrB,EAIJg5C,GAAengD,gBAAkBA,EACjCmgD,GAAe7/C,WAAaA,EAC5B6/C,GAAe95C,SAAWA,EAC1B85C,GAAej7C,YAAcA,EC9atB,SAA6Bi7C,GAChCA,EAAe7+B,UAAUwe,QAAUhN,eAAuB4vB,EAAKp7C,EAAU,IACrE,MAAMq7C,WAAEA,GAAa,GAAUr7C,EAM/B,GAJIo7C,SACM3kD,KAAK2jD,SAASgB,IAGnB3kD,KAAKuJ,QAAQK,SACd,MAAM,IAAIqC,MACN,yNAMR,GAAIiF,EAAalR,KAAKuJ,QAAQK,UAC1B,MAAM,IAAIqC,MAAM,qEAapB,GAVKjM,KAAKkjC,mBACNljC,KAAK8iD,8BAGH9iD,KAAKkjC,kBAAkBnB,UAEzB/hC,KAAKijD,cACCjjD,KAAKijD,OAAOzL,sBAGlBx3C,KAAKgjD,MAAQhjD,KAAKuJ,QAAQ+4C,sBAC1B,UACUtiD,KAAKgjD,KAAKxV,mBACpB,CAAE,MAAOhsC,GACLxB,KAAK8J,OAAOT,KAAK,kDAAmD7H,EACxE,CAGAojD,GACA5kD,KAAK6kD,0BAGT7kD,KAAK8J,OAAOV,KAAK,sBACrB,EAEAg5C,EAAe7+B,UAAUof,WAAa5N,iBAC7B/0B,KAAKkjC,oBAINljC,KAAKijD,QAAUjjD,KAAKijD,OAAO1J,YAC3Bv5C,KAAKijD,OAAOzK,UAGZx4C,KAAKijD,QACLjjD,KAAKijD,OAAOtL,uBAGZ33C,KAAKgjD,OACLhjD,KAAKgjD,KAAKzV,sBACVvtC,KAAKgjD,KAAKtV,6BAGR1tC,KAAKkjC,kBAAkBP,aAC7B3iC,KAAK8J,OAAOV,KAAK,4BACrB,EAEAg5C,EAAe7+B,UAAUuhC,OAAS/vB,iBAC9B,UACU/0B,KAAKmjC,UAAUz2B,KAAK,oBAC1B1M,KAAK8J,OAAOV,KAAK,yBACrB,CAAE,MAAO5H,GACLxB,KAAK8J,OAAOT,KAAK,wDAAyD7H,EAAMgL,QACpF,OAEMxM,KAAK2iC,aAEP3iC,KAAKkjD,cACLljD,KAAKkjD,YAAY1C,QACjBxgD,KAAK6iD,mBAAqB,MAG9B7iD,KAAKuJ,QAAQK,SAAW,KACxB5J,KAAKyiD,QAAU,KACfziD,KAAKmjC,UAAUp5B,YAAY,MAE3B/J,KAAKqB,KAAK,YACd,EA+BA+gD,EAAe7+B,UAAUshC,wBAA0B9vB,eAAuCxrB,EAAU,CAAA,GAEhG,IAAKvJ,KAAKuJ,QAAQK,SAAU,CACxB,MAAMpI,EAAQ,IAAIyK,MAAM,uDAExB,OADAjM,KAAKqB,KAAK,aAAc,CAAEkc,OAAQ,WAAY/b,UACvC,CAAE+K,IAAI,EAAOgR,OAAQ,WAAY/b,QAC5C,CAEA,IAAKxB,KAAKkjD,YAAa,CACnBljD,KAAK4iD,aAAe,IAAItF,GAAY,CAChCna,UAAWnjC,KAAKmjC,UAChBx5B,UAAW3J,KAAKuJ,QAAQI,UACxB4zC,eAAgBh0C,EAAQg0C,eACxBC,SAAUj0C,EAAQi0C,SAClBC,kBAAmBl0C,EAAQk0C,kBAC3Bt1C,SAAUnI,KAAKuJ,QAAQpB,WAQ3B,MAAM48C,EAAoB,IAC1B/kD,KAAKkjD,YAAY3D,qBAAwBlvC,IACrC,MAAM/O,EAAO+O,EAAQ/O,MAAQ,CAAA,EAE7B,GAAIA,EAAKqI,WAAarI,EAAKqI,YAAc3J,KAAKuJ,QAAQI,UAElD,YADA3J,KAAK8J,OAAOX,MAAM,iBAAkB7H,EAAKqI,WAI7C,MAAMq7C,EAAehlD,KAAKgjD,MAAMpe,gBAEhC,GAAIogB,GAAgBA,IAAiB1jD,EAAKmF,OACtCzG,KAAK8J,OAAOX,MAAM,4BAA6B7H,EAAKmF,YADxD,CAMA,GAAInF,EAAKkY,UAAW,CAIhB,GAHKxZ,KAAKilD,wBACNjlD,KAAKilD,sBAAwB,IAAI/kD,KAEjCF,KAAKilD,sBAAsB3kD,IAAIgB,EAAKkY,WAEpC,YADAxZ,KAAK8J,OAAOX,MAAM,kBAAmB7H,EAAKkY,WAG9C,MAAMm1B,EAAQrjC,WAAW,KACrBtL,KAAKilD,sBAAsB9jD,OAAOG,EAAKkY,YACxCurC,GACH/kD,KAAKilD,sBAAsB1kD,IAAIe,EAAKkY,UAAWm1B,EACnD,CAEA3uC,KAAKqB,KAAK,mBAAoB,CAC1Bo4C,MAAOppC,EAAQ60C,cAAczL,OAASn4C,EAAKm4C,MAC3ClvC,KAAM8F,EAAQ60C,cAAc36C,MAAQjJ,EAAKiJ,KACzCjJ,QApBJ,EAuBR,CAGA,GAAItB,KAAKkjD,YAAYpC,YAEjB,OADA9gD,KAAK8J,OAAOX,MAAM,mCACX,CAAEoD,IAAI,EAAM44C,gBAAgB,GAIvC,GAAInlD,KAAK6iD,mBAEL,OADA7iD,KAAK8J,OAAOX,MAAM,yBACXnJ,KAAK6iD,mBAKhB,MAAM3Z,EAAU,WACZ,IAGI,aAFMlpC,KAAKkjD,YAAY3E,SACvBv+C,KAAKqB,KAAK,eACH,CAAEkL,IAAI,EACjB,CAAE,MAAO/K,GAEL,MAAM+b,EAAU/b,GAASA,EAAM0K,KAAQ1K,EAAM0K,KAAO,UAGpD,OAFAlM,KAAK8J,OAAOT,KAAK,uCAAwCkU,EAAQ/b,GAAOgL,SACxExM,KAAKqB,KAAK,aAAc,CAAEkc,SAAQ/b,UAC3B,CAAE+K,IAAI,EAAOgR,SAAQ/b,QAChC,CACH,EAZe,GAuBhB,OATAxB,KAAK6iD,mBAAqB3Z,EAG1BA,EAAQkc,QAAQ,KACRplD,KAAK6iD,qBAAuB3Z,IAC5BlpC,KAAK6iD,mBAAqB,QAI3B3Z,CACX,EAEAkZ,EAAe7+B,UAAU46B,mBAAqBppB,iBAC1C,OAAOuoB,GAAYa,mBAAmBn+C,KAAKuJ,QAAQI,UACvD,EAEAy4C,EAAe7+B,UAAU09B,wBAA0B,SAAiCv2B,GAChF,OAAO1qB,KAAKkjD,aAAajC,wBAAwBv2B,EACrD,EAEA03B,EAAe7+B,UAAUy9B,iBAAmB,SAA0BnP,EAAUnnB,GAC5E,OAAO1qB,KAAKkjD,aAAalC,iBAAiBnP,EAAUnnB,EACxD,EAEA03B,EAAe7+B,UAAUw9B,aAAe,WACpC,OAAO/gD,KAAKkjD,aAAanC,cAC7B,EAUAqB,EAAe7+B,UAAU8hC,uBAAyB,WAC9C,OAAO/H,GAAYc,oBACvB,EAUAgE,EAAe7+B,UAAU+hC,cAAgB,WACrC,OAAOtlD,KAAKkjD,aAAapC,cAAe,CAC5C,EAMAsB,EAAe7+B,UAAUgiC,aAAe,WACpC,OAAOvlD,KAAKkjD,aAAa9D,YAAc,IAC3C,EAMAgD,EAAe7+B,UAAUiiC,UAAY,WACjCxlD,KAAKkjD,aAAa1C,QAClBxgD,KAAK6iD,mBAAqB,IAC9B,EAEAT,EAAe7+B,UAAUogC,SAAW5uB,eAAwB/qB,GACxD,MAAM8G,OAAEA,GAAWd,EAAoBhG,EAAO,CAAEkG,gBAAgB,IAC1Du1C,EAAmBzlD,KAAK8Q,QAAU9Q,KAAK8Q,SAAWA,EAExD9Q,KAAKuJ,QAAQK,SAAWI,EACxBhK,KAAKyiD,QAAU3xC,EACf9Q,KAAKmjC,UAAUp5B,YAAYC,GAEvBy7C,GAAoBzlD,KAAKkjC,yBACnBljC,KAAK2iC,aACP3iC,KAAKkjD,cACLljD,KAAKkjD,YAAY1C,QACjBxgD,KAAK6iD,mBAAqB,MAE9B7iD,KAAKkjC,kBAAoB,KACzBljC,KAAK0iD,MAAQ,KACb1iD,KAAK2iD,QAAU,KAEf3iD,KAAK8iD,yBACE9iD,KAAKkjC,kBACZljC,KAAKkjC,kBAAkBR,YAAY14B,GAEnChK,KAAK8iD,wBAGT9iD,KAAK8J,OAAOV,KAAK,gBAAiB,CAAE0H,WACpC9Q,KAAKqB,KAAK,WAAY,CAAEyP,UAC5B,EAEAsxC,EAAe7+B,UAAUmf,YAAc3N,eAA2B2wB,SACxD1lD,KAAK2jD,SAAS+B,EACxB,CACJ,CD2HAC,CAAoBvD,IDjXb,SAA+BA,GAClCH,GAAgBG,EAAe7+B,UAAW,OAAQw+B,IAClDE,GAAgBG,EAAe7+B,UAAW,SAAUy+B,GACxD,CC+WA4D,CAAsBxD,oB9EjSO,CACzByD,QAAS,UACTC,YAAa,cACbC,UAAW,+CArGgB,CAC3BC,KAAM,OACNC,MAAO,QACPC,KAAM,OACNC,MAAO,QACPC,MAAO,QACPC,OAAQ,4HAmIuB,CAC/BC,MAAO,QACPC,OAAQ,SACRvnC,OAAQ,mJA9Ge,CACvBwnC,cAAe,gBACfC,UAAW,YACXC,iBAAkB,mBAClBC,GAAI,KACJC,QAAS,UACTC,iBAAkB,mBAClBC,MAAO,QACPC,YAAa,cACbC,aAAc,eACdC,mBAAoB,qBACpBC,SAAU,WACVC,YAAa,cACbC,OAAQ,SAERC,GAAI,gCAmH+B,CAEnCC,YAAa,cAEbC,cAAe,gBAEfC,WAAY,mCAhBkB,CAE9BC,QAAS,UAETC,KAAM,wEApCgB,CACtBh/C,KAAM,OACNi/C,cAAe,gBACfC,aAAc,mDAtGQ,CACtBC,KAAM,OACNC,UAAW,+CAyCgB,CAC3BC,QAAS,UACTC,MAAO,QACPC,SAAU,WACVC,QAAS,UACTC,QAAS,mEGwCN,SAA0Bv+C,GAC7B,IACI,MACMuG,EADqBlB,EAAYrF,GACNwG,MAAM,KAEvC,OAAqB,IAAjBD,EAAMb,OACC,KAGJ5D,KAAKiD,MAAMO,EAAgBiB,EAAM,IAC5C,CAAE,MAAO3O,GAEL,OADAC,QAAQD,MAAM,gCAAiCA,GACxC,IACX,CACJ,8DAzCO,SAA6BoI,GAChC,IACI,MACMuG,EADqBlB,EAAYrF,GACNwG,MAAM,KAEvC,GAAqB,IAAjBD,EAAMb,OACN,OAAQ,EAGZ,MAAMe,EAAU3E,KAAKiD,MAAMO,EAAgBiB,EAAM,KAEjD,OAAKE,EAAQE,IAIS,IAAdF,EAAQE,IAActH,KAAKyH,MAHxB03C,GAIf,CAAE,MAAO5mD,GAEL,OADAC,QAAQD,MAAM,oCAAqCA,IAC5C,CACX,CACJ","x_google_ignoreList":[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]}
|
|
1
|
+
{"version":3,"file":"talkflow-sdk.standalone.js","sources":["../src/utils/EventEmitter.js","../src/constants.js","../src/utils/Logger.js","../src/utils/ApiClient.js","../src/utils/jwtUtils.js","../node_modules/@stomp/stompjs/esm6/byte.js","../node_modules/@stomp/stompjs/esm6/frame-impl.js","../node_modules/@stomp/stompjs/esm6/parser.js","../node_modules/@stomp/stompjs/esm6/types.js","../node_modules/@stomp/stompjs/esm6/ticker.js","../node_modules/@stomp/stompjs/esm6/versions.js","../node_modules/@stomp/stompjs/esm6/stomp-handler.js","../node_modules/@stomp/stompjs/esm6/augment-websocket.js","../node_modules/@stomp/stompjs/esm6/client.js","../node_modules/sockjs-client/lib/utils/random.js","../node_modules/sockjs-client/lib/utils/browser-crypto.js","../node_modules/sockjs-client/lib/utils/event.js","../node_modules/url-parse/index.js","../node_modules/requires-port/index.js","../node_modules/querystringify/index.js","../node_modules/sockjs-client/lib/utils/url.js","../node_modules/inherits/inherits_browser.js","../node_modules/sockjs-client/lib/event/eventtarget.js","../node_modules/sockjs-client/lib/event/emitter.js","../node_modules/sockjs-client/lib/transport/websocket.js","../node_modules/sockjs-client/lib/transport/browser/websocket.js","../node_modules/sockjs-client/lib/transport/lib/sender-receiver.js","../node_modules/sockjs-client/lib/transport/lib/buffered-sender.js","../node_modules/sockjs-client/lib/transport/lib/polling.js","../node_modules/sockjs-client/lib/transport/lib/ajax-based.js","../node_modules/sockjs-client/lib/transport/receiver/xhr.js","../node_modules/sockjs-client/lib/transport/browser/abstract-xhr.js","../node_modules/sockjs-client/lib/transport/sender/xhr-cors.js","../node_modules/sockjs-client/lib/transport/sender/xhr-local.js","../node_modules/sockjs-client/lib/utils/browser.js","../node_modules/sockjs-client/lib/transport/xhr-streaming.js","../node_modules/sockjs-client/lib/transport/sender/xdr.js","../node_modules/sockjs-client/lib/transport/xdr-streaming.js","../node_modules/sockjs-client/lib/transport/browser/eventsource.js","../node_modules/sockjs-client/lib/transport/eventsource.js","../node_modules/sockjs-client/lib/transport/receiver/eventsource.js","../node_modules/sockjs-client/lib/version.js","../node_modules/sockjs-client/lib/utils/iframe.js","../node_modules/sockjs-client/lib/transport/iframe.js","../node_modules/sockjs-client/lib/utils/object.js","../node_modules/sockjs-client/lib/transport/lib/iframe-wrap.js","../node_modules/sockjs-client/lib/transport/htmlfile.js","../node_modules/sockjs-client/lib/transport/receiver/htmlfile.js","../node_modules/sockjs-client/lib/transport/xhr-polling.js","../node_modules/sockjs-client/lib/transport/xdr-polling.js","../node_modules/sockjs-client/lib/transport/sender/jsonp.js","../node_modules/sockjs-client/lib/transport/jsonp-polling.js","../node_modules/sockjs-client/lib/transport/receiver/jsonp.js","../node_modules/sockjs-client/lib/transport-list.js","../node_modules/sockjs-client/lib/shims.js","../node_modules/sockjs-client/lib/utils/escape.js","../node_modules/sockjs-client/lib/utils/transport.js","../node_modules/sockjs-client/lib/utils/log.js","../node_modules/sockjs-client/lib/event/event.js","../node_modules/sockjs-client/lib/location.js","../node_modules/sockjs-client/lib/info-ajax.js","../node_modules/sockjs-client/lib/info-iframe-receiver.js","../node_modules/sockjs-client/lib/info-receiver.js","../node_modules/sockjs-client/lib/transport/sender/xhr-fake.js","../node_modules/sockjs-client/lib/info-iframe.js","../node_modules/sockjs-client/lib/iframe-bootstrap.js","../node_modules/sockjs-client/lib/facade.js","../node_modules/sockjs-client/lib/main.js","../node_modules/sockjs-client/lib/event/close.js","../node_modules/sockjs-client/lib/event/trans-message.js","../node_modules/sockjs-client/lib/entry.js","../src/core/ConnectionManager.js","../src/chat/ChatClient.js","../src/webrtc/MediaStreamManager.js","../src/webrtc/PeerConnectionManager.js","../src/webrtc/WebRTCClient.js","../src/push/PushManager.js","../src/talkflow/eventForwarding.js","../src/talkflow/delegates.js","../src/TalkFlowClient.js","../src/talkflow/session.js"],"sourcesContent":["/**\r\n * EventEmitter 유틸리티\r\n * 이벤트 기반 통신을 위한 간단한 구현\r\n */\r\n\r\nclass EventEmitter {\r\n constructor() {\r\n this._events = new Map();\r\n }\r\n\r\n /**\r\n * 이벤트 리스너 등록\r\n * @param {string} event - 이벤트 이름\r\n * @param {Function} listener - 콜백 함수\r\n * @returns {Function} - 구독 해제 함수\r\n */\r\n on(event, listener) {\r\n if (!this._events.has(event)) {\r\n this._events.set(event, new Set());\r\n }\r\n this._events.get(event).add(listener);\r\n\r\n // 구독 해제 함수 반환\r\n return () => this.off(event, listener);\r\n }\r\n\r\n /**\r\n * 일회성 이벤트 리스너 등록\r\n * @param {string} event - 이벤트 이름\r\n * @param {Function} listener - 콜백 함수\r\n * @returns {Function} - 구독 해제 함수\r\n */\r\n once(event, listener) {\r\n const onceWrapper = (...args) => {\r\n this.off(event, onceWrapper);\r\n listener.apply(this, args);\r\n };\r\n onceWrapper._originalListener = listener;\r\n return this.on(event, onceWrapper);\r\n }\r\n\r\n /**\r\n * 이벤트 리스너 제거\r\n * @param {string} event - 이벤트 이름\r\n * @param {Function} listener - 콜백 함수\r\n */\r\n off(event, listener) {\r\n const listeners = this._events.get(event);\r\n if (listeners) {\r\n // once로 등록된 리스너도 제거 가능하도록\r\n for (const l of listeners) {\r\n if (l === listener || l._originalListener === listener) {\r\n listeners.delete(l);\r\n break;\r\n }\r\n }\r\n if (listeners.size === 0) {\r\n this._events.delete(event);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 이벤트 발생.\r\n * payload 가 없는 이벤트({@code loggedOut}, {@code pushEnabled} 등) 는 {@code data} 생략 가능.\r\n * @param {string} event - 이벤트 이름\r\n * @param {*} [data] - 이벤트 데이터 (선택)\r\n */\r\n emit(event, data) {\r\n const listeners = this._events.get(event);\r\n if (listeners) {\r\n listeners.forEach(listener => {\r\n try {\r\n listener(data);\r\n } catch (error) {\r\n console.error(`Error in event listener for '${event}':`, error);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * 특정 이벤트의 모든 리스너 제거 (이벤트명 생략 시 전체 제거)\r\n * @param {string} [event] - 이벤트 이름 (선택)\r\n */\r\n removeAllListeners(event) {\r\n if (event) {\r\n this._events.delete(event);\r\n } else {\r\n this._events.clear();\r\n }\r\n }\r\n\r\n /**\r\n * 이벤트 리스너 수 반환\r\n * @param {string} event - 이벤트 이름\r\n * @returns {number}\r\n */\r\n listenerCount(event) {\r\n const listeners = this._events.get(event);\r\n return listeners ? listeners.size : 0;\r\n }\r\n\r\n /**\r\n * 등록된 이벤트 이름 목록\r\n * @returns {string[]}\r\n */\r\n eventNames() {\r\n return Array.from(this._events.keys());\r\n }\r\n}\r\n\r\nexport default EventEmitter;\r\n","/**\r\n * TalkFlow SDK Constants\r\n * 클라이언트 상태, 에러 타입, 시그널 타입 등 상수 정의\r\n */\r\n\r\n/**\r\n * 클라이언트 연결 상태\r\n */\r\nexport const ConnectionState = {\r\n DISCONNECTED: 'disconnected',\r\n CONNECTING: 'connecting',\r\n CONNECTED: 'connected',\r\n RECONNECTING: 'reconnecting',\r\n ERROR: 'error'\r\n};\r\n\r\n/**\r\n * 에러 타입\r\n */\r\nexport const ErrorTypes = {\r\n // Connection errors\r\n CONNECTION_FAILED: 'CONNECTION_FAILED',\r\n CONNECTION_LOST: 'CONNECTION_LOST',\r\n CONNECTION_TIMEOUT: 'CONNECTION_TIMEOUT',\r\n\r\n // Authentication errors\r\n JWT_INVALID: 'JWT_INVALID',\r\n JWT_EXPIRED: 'JWT_EXPIRED',\r\n JWT_PARSE_FAILED: 'JWT_PARSE_FAILED',\r\n UNAUTHORIZED: 'UNAUTHORIZED',\r\n\r\n // API errors\r\n API_ERROR: 'API_ERROR',\r\n API_TIMEOUT: 'API_TIMEOUT',\r\n\r\n // Chat errors\r\n CHAT_ROOM_NOT_FOUND: 'CHAT_ROOM_NOT_FOUND',\r\n CHAT_MESSAGE_FAILED: 'CHAT_MESSAGE_FAILED',\r\n CHAT_SUBSCRIPTION_FAILED: 'CHAT_SUBSCRIPTION_FAILED',\r\n\r\n // WebRTC errors\r\n MEDIA_ACCESS_DENIED: 'MEDIA_ACCESS_DENIED',\r\n SCREEN_SHARE_DENIED: 'SCREEN_SHARE_DENIED',\r\n PEER_CONNECTION_FAILED: 'PEER_CONNECTION_FAILED',\r\n ICE_CONNECTION_FAILED: 'ICE_CONNECTION_FAILED',\r\n SIGNALING_FAILED: 'SIGNALING_FAILED',\r\n DEVICE_SWITCH_FAILED: 'DEVICE_SWITCH_FAILED',\r\n ENUMERATE_DEVICES_FAILED: 'ENUMERATE_DEVICES_FAILED',\r\n CALL_ROOM_NOT_FOUND: 'CALL_ROOM_NOT_FOUND',\r\n\r\n // General errors\r\n INVALID_STATE: 'INVALID_STATE',\r\n UNKNOWN_ERROR: 'UNKNOWN_ERROR'\r\n};\r\n\r\n/**\r\n * WebRTC 시그널 타입 (서버 WebRTCMessageType과 일치)\r\n */\r\nexport const SignalTypes = {\r\n // Offer/Answer (WebRTC SDP)\r\n CALL_OFFER: 'call_offer',\r\n CALL_ANSWER: 'call_answer',\r\n\r\n // ICE\r\n ICE_CANDIDATE: 'ice_candidate',\r\n\r\n // Room events\r\n JOIN_ROOM: 'join_room',\r\n LEAVE_ROOM: 'leave_room',\r\n PEER_JOINED: 'peer_joined',\r\n PEER_LEFT: 'peer_left',\r\n\r\n // Media state\r\n VIDEO_STATE_CHANGED: 'video_state_changed',\r\n AUDIO_STATE_CHANGED: 'audio_state_changed',\r\n SCREEN_SHARE_STARTED: 'screen_share_started',\r\n SCREEN_SHARE_ENDED: 'screen_share_ended',\r\n\r\n // Call events (UI 레벨)\r\n CALL_REQUEST: 'call_request',\r\n CALL_ACCEPT: 'call_accept',\r\n CALL_REJECT: 'call_reject',\r\n CALL_CANCEL: 'call_cancel',\r\n CALL_END: 'call_end',\r\n CALL_INVITATION: 'call_invitation',\r\n CALL_BUSY: 'call_busy'\r\n};\r\n\r\n/**\r\n * 채팅 메시지 타입\r\n */\r\nexport const ChatMessageType = {\r\n TEXT: 'TEXT',\r\n IMAGE: 'IMAGE',\r\n FILE: 'FILE',\r\n VIDEO: 'VIDEO',\r\n AUDIO: 'AUDIO',\r\n SYSTEM: 'SYSTEM'\r\n};\r\n\r\n/**\r\n * 메시지 발신자 타입 — 서버 {@code SenderType} 과 일치.\r\n *\r\n * <ul>\r\n * <li>{@code USER} — 사람 사용자가 보낸 메시지 (기존 모든 메시지의 default)</li>\r\n * <li>{@code ASSISTANT} — AI 어시스턴트 페르소나가 생성한 메시지. {@code userId} 자리는 페르소나 ID</li>\r\n * </ul>\r\n *\r\n * <p>서버 응답에서 {@code senderType} 필드가 없으면 {@code USER} 로 간주 (기존 데이터 graceful).</p>\r\n */\r\nexport const SenderType = {\r\n USER: 'USER',\r\n ASSISTANT: 'ASSISTANT'\r\n};\r\n\r\n/**\r\n * 어시스턴트 페르소나 분류 — 서버 {@code PersonaRole} enum 과 일치 (14개).\r\n *\r\n * <p>{@code PM} 은 PM_BACKSTAGE 방의 단일 PM Front 전용 — 기존 14인 PERSONA_MULTI 라인업과 별개 구성이며\r\n * {@code PROJECT_MANAGEMENT}(PM 비서)와도 다르다.</p>\r\n */\r\nexport const PersonaRole = {\r\n LEGAL_ADVISOR: 'LEGAL_ADVISOR',\r\n MARKETING: 'MARKETING',\r\n PRODUCT_PLANNING: 'PRODUCT_PLANNING',\r\n HR: 'HR',\r\n FINANCE: 'FINANCE',\r\n CUSTOMER_SUPPORT: 'CUSTOMER_SUPPORT',\r\n SALES: 'SALES',\r\n ENGINEERING: 'ENGINEERING',\r\n DATA_ANALYST: 'DATA_ANALYST',\r\n PROJECT_MANAGEMENT: 'PROJECT_MANAGEMENT',\r\n RESEARCH: 'RESEARCH',\r\n TRANSLATION: 'TRANSLATION',\r\n DESIGN: 'DESIGN',\r\n // PM_BACKSTAGE 전용 — PM Front 단일 응답 (PERSONA_MULTI 14인과 별개)\r\n PM: 'PM'\r\n};\r\n\r\n/**\r\n * 채팅 요약 포맷 — 서버 {@code SummarizeFormat} enum 과 일치 (5개).\r\n *\r\n * <p>{@link ChatClient#summarizeWithAssistant} 의 {@code format} 인자.\r\n * 서버가 대소문자 무관하게 매핑하며, 미지정/알 수 없는 값이면 {@code SHORT} 로 처리.</p>\r\n *\r\n * <ul>\r\n * <li>{@code MINUTES} — 회의록 (참석/안건/핵심 논의/결정사항)</li>\r\n * <li>{@code SHORT} — 짧은 요약 (몇 문장 핵심). 기본값.</li>\r\n * <li>{@code TIMELINE} — 시간순 타임라인</li>\r\n * <li>{@code ACTIONS} — 액션 아이템 (task/owner/기한)</li>\r\n * <li>{@code OPTIONS} — 옵션 비교 (논의된 선택지별 장단점)</li>\r\n * </ul>\r\n */\r\nexport const SummarizeFormat = {\r\n MINUTES: 'MINUTES',\r\n SHORT: 'SHORT',\r\n TIMELINE: 'TIMELINE',\r\n ACTIONS: 'ACTIONS',\r\n OPTIONS: 'OPTIONS'\r\n};\r\n\r\n\r\n/**\r\n * 채팅방 타입 (서버 {@code ChatRoomType} enum 과 일치).\r\n *\r\n * <ul>\r\n * <li>{@code DIRECT} — 1:1 채팅방</li>\r\n * <li>{@code GROUP} — 공개 그룹 채팅방</li>\r\n * <li>{@code PRIVATE_GROUP} — 비밀 그룹 채팅방 (입장 시 비밀번호 필요)</li>\r\n * <li>{@code TEAM} — 팀 채팅방 (초대 전용 — 직접 입장 불가, 멤버는 입장 시점과 무관하게\r\n * 방 전체 히스토리 열람. 비참여자에게는 탐색/전체 목록에서도 숨김. 비밀번호 없음)</li>\r\n * </ul>\r\n */\r\nexport const ChatRoomType = {\r\n DIRECT: 'DIRECT',\r\n GROUP: 'GROUP',\r\n PRIVATE_GROUP: 'PRIVATE_GROUP',\r\n TEAM: 'TEAM'\r\n};\r\n\r\n/**\r\n * 어시스턴트 참여 모드 (서버 {@code AssistantMode} enum 과 일치).\r\n *\r\n * <ul>\r\n * <li>{@code GENERAL}: 자동 라우팅 + @mention 모두 활성</li>\r\n * <li>{@code PEOPLE_ONLY}: AI 응답 전면 차단</li>\r\n * <li>{@code CALL_ONLY}: @mention / 명시 호출 시만 응답</li>\r\n * </ul>\r\n */\r\nexport const AssistantMode = {\r\n GENERAL: 'GENERAL',\r\n PEOPLE_ONLY: 'PEOPLE_ONLY',\r\n CALL_ONLY: 'CALL_ONLY'\r\n};\r\n\r\n/**\r\n * 방의 AI 사용 방식 (상위 SSOT) — 서버 {@code RoomAiType} enum 과 일치.\r\n *\r\n * <p>{@link ChatClient#createGroupRoom} 의 {@code roomAiType} 으로 방 생성 시 의도를 명시한다.\r\n * 미지정 시 서버가 API 키 권한(capability) + 첨부 페르소나로 파생한다 (레거시 호환).\r\n * <b>단, PERSONA_MULTI 권한이 없는 PM 전용 키는 미지정 시 {@code NONE}(사람 방)으로 파생되므로</b>\r\n * {@link ChatClient#getRoomAiMeta} 로 확인 후 고른 타입을 명시 전송할 것.</p>\r\n *\r\n * <ul>\r\n * <li>{@code NONE}: AI 미사용 (순수 사람 채팅). {@code invitedAssistantPersonaIds}/{@code assistantMode} 동시 지정 불가.</li>\r\n * <li>{@code PERSONA_MULTI}: 다중 페르소나, LLM 이 판단해 호출. {@code assistantMode} 로 세부 제어. (현재 유일 활성)</li>\r\n * <li>{@code PM_BACKSTAGE}: PM + 백스테이지. PM 단일 응답 구현됨 — {@code engagementIntensity} 로 개입 강도 제어. 백스테이지 위임은 후속.</li>\r\n * </ul>\r\n */\r\nexport const RoomAiType = {\r\n NONE: 'NONE',\r\n PERSONA_MULTI: 'PERSONA_MULTI',\r\n PM_BACKSTAGE: 'PM_BACKSTAGE'\r\n};\r\n\r\n/**\r\n * PM_BACKSTAGE 방의 PM 개입 강도 (서버 {@code EngagementIntensity} enum 과 일치).\r\n *\r\n * <p>{@link ChatClient#createGroupRoom} / {@code updateGroupRoom} 의 {@code engagementIntensity}.\r\n * <b>PM_BACKSTAGE 방 전용</b> — 그 외 방 타입에 지정 시 서버가 거절한다. 14인 PERSONA_MULTI 는\r\n * 쿨다운 + Planner 자동 판단이라 무관하며, 이 값은 PM 단일 응답의 개입 빈도만 제어한다.</p>\r\n *\r\n * <ul>\r\n * <li>{@code QUIET}: 보수적 — 명확한 질문/도움 요청에만 PM 응답</li>\r\n * <li>{@code NORMAL}: 기본 — 업무 신호에 응답, 잡담엔 침묵</li>\r\n * <li>{@code ACTIVE}: 적극 — 가벼운 신호에도 응답</li>\r\n * </ul>\r\n */\r\nexport const EngagementIntensity = {\r\n QUIET: 'QUIET',\r\n NORMAL: 'NORMAL',\r\n ACTIVE: 'ACTIVE'\r\n};\r\n\r\n/**\r\n * PM 프롬프트 레이어 차원 — 서버 {@code PmPromptLayerScope} enum 과 일치.\r\n *\r\n * <p>전역 PM 프롬프트 위에 합성되는 추가 지시문 2종. 합성 순서: 전역 → PROJECT → ROOM.\r\n * SDK 가 다루는 것은 ROOM(방 레이어 — {@link ChatClient#getRoomPmPrompt} 등)이며,\r\n * PROJECT(회사 레이어)는 어드민 콘솔 영역이다.</p>\r\n */\r\nexport const PmPromptLayerScope = {\r\n /** 회사(프로젝트) 레이어 — 어드민 콘솔(PROJECT_ADMIN) 관리. */\r\n PROJECT: 'PROJECT',\r\n /** 방 레이어 — 방 주인(OWNER) 관리. PM_BACKSTAGE 방 전용. */\r\n ROOM: 'ROOM'\r\n};\r\n\r\n/**\r\n * PM 프롬프트 레이어 수정 주체 (감사 추적) — 서버 {@code PmPromptLayerEditorType} enum 과 일치.\r\n */\r\nexport const PmPromptLayerEditorType = {\r\n /** 플랫폼 운영자 (대리 수정). */\r\n SUPER_ADMIN: 'SUPER_ADMIN',\r\n /** 회사 관리자 — PROJECT 레이어. */\r\n PROJECT_ADMIN: 'PROJECT_ADMIN',\r\n /** 방 주인(OWNER) — ROOM 레이어. */\r\n ROOM_OWNER: 'ROOM_OWNER'\r\n};\r\n\r\n/**\r\n * 채팅방 리스트 실시간 업데이트 이벤트 타입 (카톡 스타일).\r\n *\r\n * 서버 RoomListEventType enum 과 일치해야 함.\r\n *\r\n * @example\r\n * client.on('roomListUpdate', (event) => {\r\n * switch (event.eventType) {\r\n * case RoomListEventType.MESSAGE_RECEIVED:\r\n * // 해당 방을 최상단으로, unread +1 (본인 메시지는 제외)\r\n * break;\r\n * case RoomListEventType.ROOM_LEFT:\r\n * // actorId === currentUserId 이면 리스트에서 제거\r\n * break;\r\n * }\r\n * });\r\n */\r\nexport const RoomListEventType = {\r\n /** 새 메시지 수신 → 리스트 최상단 이동, unread 카운트 +1 (본인 메시지 제외). */\r\n MESSAGE_RECEIVED: 'MESSAGE_RECEIVED',\r\n /** 현재 lastMessage 삭제 → 리스트 preview 갱신, unread 카운트 변경 없음. */\r\n MESSAGE_DELETED: 'MESSAGE_DELETED',\r\n /** 현재 lastMessage 편집 → 리스트 preview 새 content 로 갱신, unread 카운트 변경 없음. */\r\n MESSAGE_UPDATED: 'MESSAGE_UPDATED',\r\n /** 방 생성 (Direct/Group) → 참가자 리스트에 신규 item 추가. */\r\n ROOM_CREATED: 'ROOM_CREATED',\r\n /** 기존 방에 누군가 입장 → 참가자 수 갱신. */\r\n ROOM_JOINED: 'ROOM_JOINED',\r\n /** 방 퇴장 → 나간 본인 리스트에서는 제거, 남은 참가자는 참가자 수 감소. */\r\n ROOM_LEFT: 'ROOM_LEFT',\r\n /** 방 정보 변경 (이름, 설명 등). 미래 확장용. */\r\n ROOM_UPDATED: 'ROOM_UPDATED',\r\n /**\r\n * 방장에 의한 단순 추방 → 대상은 리스트에서 방 제거, 남은 참가자는 참가자 수 감소.\r\n * 재입장 가능 (ban 아님). actorId 는 추방한 방장, members 에 추방된 사용자 정보.\r\n */\r\n ROOM_KICKED: 'ROOM_KICKED',\r\n /** 방장에 의한 추방 + 영구 차단 → payload 는 ROOM_KICKED 와 동일, 재입장 거부됨. */\r\n ROOM_BANNED: 'ROOM_BANNED',\r\n /** 메시지 보관 기간 정리 → cutoffTime 이전 메시지를 UI에서 제거. */\r\n MESSAGE_RETENTION_CLEANUP: 'MESSAGE_RETENTION_CLEANUP'\r\n};\r\n\r\n/**\r\n * WebSocket 경로\r\n */\r\nexport const WebSocketPaths = {\r\n // Endpoints\r\n SOCKJS_ENDPOINT: '/ws-chat',\r\n NATIVE_ENDPOINT: '/ws-chat-native',\r\n\r\n // Prefixes\r\n APP_PREFIX: '/app',\r\n TOPIC_PREFIX: '/topic',\r\n QUEUE_PREFIX: '/queue',\r\n USER_PREFIX: '/user',\r\n\r\n // Chat destinations\r\n getChatDestination: (roomId) => `/topic/chat/${roomId}`,\r\n getChatReadDestination: (roomId) => `/topic/chat/${roomId}/read`,\r\n getChatTypingDestination: (roomId) => `/topic/chat/${roomId}/typing`,\r\n // AI 진행 표시(생각중/검색중/작성중) + 토큰 스트리밍 — typing 과 별개 채널(가산 UX 레이어).\r\n getChatAssistantStreamDestination: (roomId) => `/topic/chat/${roomId}/assistant-stream`,\r\n\r\n // Room list (카톡 스타일 리스트 실시간 업데이트)\r\n // 서버가 convertAndSendToUser(userId, \"/queue/rooms\", event) 로 전송\r\n // 클라이언트는 \"/user/queue/rooms\" 구독 (Spring STOMP 가 세션별 자동 격리)\r\n ROOM_LIST_USER_DESTINATION: '/user/queue/rooms',\r\n\r\n // WebRTC destinations\r\n getWebRTCDestination: (roomId) => `/topic/webrtc/${roomId}`,\r\n getWebRTCUserDestination: () => `/user/queue/webrtc`,\r\n\r\n // Send destinations\r\n CHAT_SEND: '/app/chat/send',\r\n CHAT_READ: '/app/chat/read',\r\n CHAT_TYPING: '/app/chat/typing',\r\n WEBRTC_SIGNAL: '/app/webrtc/signal'\r\n};\r\n\r\n/**\r\n * 환경 타입\r\n */\r\nexport const Environment = {\r\n DEVELOPMENT: 'development',\r\n STAGING: 'staging',\r\n PRODUCTION: 'production'\r\n};\r\n\r\n/**\r\n * 환경별 서버 설정\r\n */\r\nexport const Endpoints = {\r\n [Environment.DEVELOPMENT]: {\r\n serverUrl: 'https://dev-chat.apiorbit.net',\r\n wsEndpoint: '/ws-chat'\r\n },\r\n [Environment.STAGING]: {\r\n serverUrl: 'https://stg-api.talk-x.app',\r\n wsEndpoint: '/ws-chat'\r\n },\r\n [Environment.PRODUCTION]: {\r\n serverUrl: 'https://prod-api.talk-x.app',\r\n wsEndpoint: '/ws-chat'\r\n }\r\n};\r\n\r\n/**\r\n * 기본 설정값\r\n */\r\nexport const DefaultConfig = {\r\n // 기본 환경 (production)\r\n environment: Environment.PRODUCTION,\r\n\r\n // Connection\r\n reconnectDelay: 5000,\r\n maxReconnectAttempts: 10,\r\n heartbeatIncoming: 10000,\r\n heartbeatOutgoing: 10000,\r\n\r\n // API\r\n apiTimeout: 30000,\r\n\r\n // WebRTC\r\n iceServers: [\r\n { urls: 'stun:stun.l.google.com:19302' },\r\n { urls: 'stun:stun1.l.google.com:19302' }\r\n ],\r\n\r\n // Logging\r\n logLevel: 2 // WARN\r\n};\r\n\r\n/**\r\n * 환경에 따른 서버 URL 반환\r\n * @param {string} env - Environment 값\r\n * @returns {string} 서버 URL\r\n */\r\nexport function getServerUrl(env) {\r\n const endpoint = Endpoints[env] || Endpoints[Environment.PRODUCTION];\r\n return endpoint.serverUrl;\r\n}\r\n\r\n/**\r\n * 로그 레벨\r\n */\r\nexport const LogLevel = {\r\n DEBUG: 0,\r\n INFO: 1,\r\n WARN: 2,\r\n ERROR: 3,\r\n NONE: 4\r\n};\r\n","/**\r\n * Logger 유틸리티\r\n * 로그 레벨에 따른 콘솔 출력 관리\r\n */\r\n\r\nimport { LogLevel } from '../constants.js';\r\n\r\nclass Logger {\r\n /**\r\n * @param {number} [level] - 로그 레벨\r\n * @param {string} [prefix] - 로그 접두사\r\n */\r\n constructor(level = LogLevel.WARN, prefix = 'TalkFlow') {\r\n this.level = level;\r\n this.prefix = prefix;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level - 로그 레벨\r\n */\r\n setLevel(level) {\r\n this.level = level;\r\n }\r\n\r\n /**\r\n * 로그 접두사 설정\r\n * @param {string} prefix - 로그 접두사\r\n */\r\n setPrefix(prefix) {\r\n this.prefix = prefix;\r\n }\r\n\r\n /**\r\n * 로그 포맷 생성\r\n * @private\r\n * @param {string} level - 로그 레벨 문자열\r\n * @param {...*} args - 로그 인자들\r\n * @returns {Array} 포맷된 로그 배열\r\n */\r\n _format(level, ...args) {\r\n const timestamp = new Date().toISOString();\r\n return [`[${timestamp}] [${level}] [${this.prefix}]`, ...args];\r\n }\r\n\r\n /**\r\n * 디버그 로그 출력\r\n * @param {...*} args - 로그 인자들\r\n */\r\n debug(...args) {\r\n if (this.level <= LogLevel.DEBUG) {\r\n console.debug(...this._format('DEBUG', ...args));\r\n }\r\n }\r\n\r\n /**\r\n * 정보 로그 출력\r\n * @param {...*} args - 로그 인자들\r\n */\r\n info(...args) {\r\n if (this.level <= LogLevel.INFO) {\r\n console.info(...this._format('INFO', ...args));\r\n }\r\n }\r\n\r\n /**\r\n * 경고 로그 출력\r\n * @param {...*} args - 로그 인자들\r\n */\r\n warn(...args) {\r\n if (this.level <= LogLevel.WARN) {\r\n console.warn(...this._format('WARN', ...args));\r\n }\r\n }\r\n\r\n /**\r\n * 에러 로그 출력\r\n * @param {...*} args - 로그 인자들\r\n */\r\n error(...args) {\r\n if (this.level <= LogLevel.ERROR) {\r\n console.error(...this._format('ERROR', ...args));\r\n }\r\n }\r\n}\r\n\r\nexport default Logger;\r\nexport { LogLevel };\r\n","/**\r\n * API Client\r\n * REST API 호출을 위한 HTTP 클라이언트\r\n */\r\n\r\nimport { ErrorTypes, DefaultConfig } from '../constants.js';\r\nimport Logger from './Logger.js';\r\n\r\nclass ApiClient {\r\n /**\r\n * @param {Object} options\r\n * @param {string} options.baseUrl - API 기본 URL\r\n * @param {string} options.apiKey - API 키\r\n * @param {string} options.projectId - 프로젝트 ID\r\n * @param {string} options.jwtToken - JWT 토큰\r\n * @param {number} [options.timeout] - 요청 타임아웃 (ms)\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\r\n this.apiKey = options.apiKey;\r\n this.projectId = options.projectId;\r\n this.jwtToken = options.jwtToken;\r\n this.timeout = options.timeout || DefaultConfig.apiTimeout;\r\n\r\n this.logger = new Logger(options.logLevel, 'ApiClient');\r\n }\r\n\r\n /**\r\n * JWT 토큰 업데이트.\r\n * logout 경로에서 토큰 제거를 위해 {@code null} 도 전달 가능.\r\n * @param {string|null} token\r\n */\r\n setJwtToken(token) {\r\n this.jwtToken = token;\r\n }\r\n\r\n /**\r\n * 공통 헤더 생성\r\n * @returns {Object}\r\n */\r\n _getHeaders() {\r\n const headers = {\r\n 'Content-Type': 'application/json',\r\n 'X-API-KEY': this.apiKey,\r\n 'X-PROJECT-ID': this.projectId\r\n };\r\n\r\n if (this.jwtToken) {\r\n headers['Authorization'] = this.jwtToken.startsWith('Bearer ')\r\n ? this.jwtToken\r\n : `Bearer ${this.jwtToken}`;\r\n }\r\n\r\n return headers;\r\n }\r\n\r\n /**\r\n * HTTP 요청 실행\r\n * @param {string} method - HTTP 메서드\r\n * @param {string} path - API 경로\r\n * @param {Object} [options] - 옵션\r\n * @param {Object} [options.body] - 요청 본문\r\n * @param {Object} [options.params] - URL 파라미터\r\n * @returns {Promise<Object>}\r\n */\r\n async request(method, path, options = {}) {\r\n const { body, params } = options;\r\n\r\n // URL 빌드\r\n let url = `${this.baseUrl}${path}`;\r\n if (params) {\r\n const searchParams = new URLSearchParams();\r\n Object.entries(params).forEach(([key, value]) => {\r\n if (value !== undefined && value !== null) {\r\n searchParams.append(key, value);\r\n }\r\n });\r\n const queryString = searchParams.toString();\r\n if (queryString) {\r\n url += `?${queryString}`;\r\n }\r\n }\r\n\r\n // AbortController for timeout\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\r\n\r\n let response;\r\n try {\r\n this.logger.debug(`${method} ${url}`, body ? { body } : '');\r\n\r\n response = await fetch(url, {\r\n method,\r\n headers: this._getHeaders(),\r\n body: body ? JSON.stringify(body) : undefined,\r\n signal: controller.signal\r\n });\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n\r\n if (error.name === 'AbortError') {\r\n const timeoutError = new Error('Request timeout');\r\n timeoutError.code = ErrorTypes.API_TIMEOUT;\r\n throw timeoutError;\r\n }\r\n\r\n this.logger.error(`API Error: ${method} ${url}`, error);\r\n throw error;\r\n }\r\n\r\n clearTimeout(timeoutId);\r\n\r\n // 응답 파싱\r\n const contentType = response.headers.get('content-type');\r\n let data;\r\n\r\n if (contentType && contentType.includes('application/json')) {\r\n data = await response.json();\r\n } else {\r\n data = await response.text();\r\n }\r\n\r\n // 에러 처리\r\n if (!response.ok) {\r\n const error = new Error(data?.message || `HTTP ${response.status}`);\r\n error.code = ErrorTypes.API_ERROR;\r\n error.status = response.status;\r\n error.response = data;\r\n this.logger.error(`API Error: ${method} ${url}`, error);\r\n throw error;\r\n }\r\n\r\n this.logger.debug(`Response ${response.status}:`, data);\r\n return data;\r\n }\r\n\r\n // HTTP 메서드 헬퍼\r\n\r\n /**\r\n * GET 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [params] - URL 파라미터\r\n * @returns {Promise<Object>}\r\n */\r\n get(path, params) {\r\n return this.request('GET', path, { params });\r\n }\r\n\r\n /**\r\n * POST 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [body] - 요청 본문\r\n * @param {Object} [params] - URL 파라미터\r\n * @returns {Promise<Object>}\r\n */\r\n post(path, body, params) {\r\n return this.request('POST', path, { body, params });\r\n }\r\n\r\n /**\r\n * PUT 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [body] - 요청 본문\r\n * @returns {Promise<Object>}\r\n */\r\n put(path, body) {\r\n return this.request('PUT', path, { body });\r\n }\r\n\r\n /**\r\n * PATCH 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [body] - 요청 본문\r\n * @returns {Promise<Object>}\r\n */\r\n patch(path, body) {\r\n return this.request('PATCH', path, { body });\r\n }\r\n\r\n /**\r\n * DELETE 요청\r\n * @param {string} path - API 경로\r\n * @param {Object} [params] - URL 파라미터\r\n * @returns {Promise<Object>}\r\n */\r\n delete(path, params) {\r\n return this.request('DELETE', path, { params });\r\n }\r\n\r\n /**\r\n * multipart/form-data 파일 업로드.\r\n *\r\n * <p>{@code fetch} 가 업로드 진행률을 지원하지 않아 {@code XMLHttpRequest} 사용.\r\n * Content-Type 은 브라우저가 boundary 를 포함해 자동 세팅하므로 명시하지 않는다.</p>\r\n *\r\n * @param {string} path - API 경로 (baseUrl 뒤에 붙음)\r\n * @param {File|Blob} file - 업로드할 파일\r\n * @param {Object} [options]\r\n * @param {string} [options.fieldName='file'] - multipart field 이름 (서버 @RequestParam 과 일치)\r\n * @param {Function} [options.onProgress] - {@code ({loaded, total, percent}) => void} — 업로드 진행률 콜백\r\n * @param {AbortSignal} [options.signal] - 업로드 취소용 AbortSignal\r\n * @param {number} [options.timeout=600000] - 타임아웃 (ms). 기본 10분.\r\n * @returns {Promise<Object>} 서버 응답 JSON (SuccessResponse 래퍼 포함)\r\n */\r\n upload(path, file, options = {}) {\r\n const {\r\n fieldName = 'file',\r\n onProgress,\r\n signal,\r\n timeout = 600_000\r\n } = options;\r\n\r\n return new Promise((resolve, reject) => {\r\n const url = `${this.baseUrl}${path}`;\r\n const xhr = new XMLHttpRequest();\r\n const form = new FormData();\r\n form.append(fieldName, file);\r\n\r\n xhr.open('POST', url);\r\n xhr.timeout = timeout;\r\n\r\n // Content-Type 은 XHR 이 multipart boundary 포함해 자동 세팅 — 명시 금지\r\n xhr.setRequestHeader('X-API-KEY', this.apiKey);\r\n xhr.setRequestHeader('X-PROJECT-ID', this.projectId);\r\n if (this.jwtToken) {\r\n xhr.setRequestHeader(\r\n 'Authorization',\r\n this.jwtToken.startsWith('Bearer ') ? this.jwtToken : `Bearer ${this.jwtToken}`\r\n );\r\n }\r\n\r\n if (typeof onProgress === 'function') {\r\n xhr.upload.onprogress = (e) => {\r\n if (e.lengthComputable) {\r\n onProgress({\r\n loaded: e.loaded,\r\n total: e.total,\r\n percent: Math.round((e.loaded / e.total) * 100)\r\n });\r\n }\r\n };\r\n }\r\n\r\n // 외부 AbortSignal 과 연동 (취소 지원)\r\n const onAbort = () => xhr.abort();\r\n if (signal) {\r\n if (signal.aborted) {\r\n reject(new Error('Upload cancelled'));\r\n return;\r\n }\r\n signal.addEventListener('abort', onAbort);\r\n }\r\n const cleanupSignal = () => {\r\n if (signal) signal.removeEventListener('abort', onAbort);\r\n };\r\n\r\n xhr.onload = () => {\r\n cleanupSignal();\r\n let parsed = xhr.responseText;\r\n const contentType = xhr.getResponseHeader('content-type') || '';\r\n if (contentType.includes('application/json')) {\r\n try { parsed = JSON.parse(xhr.responseText); } catch { /* keep text */ }\r\n }\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n this.logger.debug(`Upload ${xhr.status}:`, parsed);\r\n resolve(parsed);\r\n } else {\r\n const error = new Error(parsed?.message || `HTTP ${xhr.status}`);\r\n error.code = ErrorTypes.API_ERROR;\r\n error.status = xhr.status;\r\n error.response = parsed;\r\n this.logger.error(`Upload Error: POST ${url}`, error);\r\n reject(error);\r\n }\r\n };\r\n\r\n xhr.onerror = () => {\r\n cleanupSignal();\r\n const err = new Error('Network error during upload');\r\n err.code = ErrorTypes.API_ERROR;\r\n this.logger.error(`Upload Network Error: POST ${url}`, err);\r\n reject(err);\r\n };\r\n\r\n xhr.ontimeout = () => {\r\n cleanupSignal();\r\n const err = new Error('Upload timeout');\r\n err.code = ErrorTypes.API_TIMEOUT;\r\n this.logger.error(`Upload Timeout: POST ${url}`, err);\r\n reject(err);\r\n };\r\n\r\n xhr.onabort = () => {\r\n cleanupSignal();\r\n reject(new Error('Upload cancelled'));\r\n };\r\n\r\n this.logger.debug(`POST ${url} (multipart, ${file.size} bytes)`);\r\n xhr.send(form);\r\n });\r\n }\r\n}\r\n\r\nexport default ApiClient;\r\n","/**\r\n * JWT 유틸리티 함수\r\n * JWT 토큰 파싱 및 검증\r\n */\r\n\r\nimport { ErrorTypes } from '../constants.js';\r\n\r\n/**\r\n * Bearer 접두사 제거\r\n * @param {string} token - JWT 토큰\r\n * @returns {string}\r\n */\r\nfunction stripBearer(token) {\r\n if (!token || typeof token !== 'string') {\r\n return '';\r\n }\r\n return token.replace(/^Bearer\\s+/i, '');\r\n}\r\n\r\n/**\r\n * Base64URL 디코딩 (UTF-8 안전)\r\n * @param {string} str - Base64URL 인코딩된 문자열\r\n * @returns {string}\r\n */\r\nfunction base64UrlDecode(str) {\r\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\r\n const padding = base64.length % 4;\r\n if (padding) {\r\n base64 += '='.repeat(4 - padding);\r\n }\r\n // atob()은 Latin-1 기반이라 멀티바이트 UTF-8(한국어 등)이 깨짐\r\n // → Uint8Array 변환 후 TextDecoder로 UTF-8 디코딩\r\n const binaryString = atob(base64);\r\n const bytes = Uint8Array.from(binaryString, c => c.charCodeAt(0));\r\n return new TextDecoder().decode(bytes);\r\n}\r\n\r\n/**\r\n * JWT 토큰 검증 및 파싱\r\n * @param {string} jwtToken - JWT 토큰 (Bearer 접두사 포함 가능)\r\n * @param {Object} [options] - 옵션\r\n * @param {number} [options.bufferSeconds=30] - 만료 버퍼 시간 (초)\r\n * @param {boolean} [options.validateExpiry=true] - 만료 시간 검증 여부\r\n * @returns {{userId: string, payload: Object}}\r\n * @throws {Error}\r\n */\r\nexport function validateAndParseJWT(jwtToken, options = {}) {\r\n const { bufferSeconds = 30, validateExpiry = true } = options;\r\n\r\n if (!jwtToken || typeof jwtToken !== 'string') {\r\n const error = new Error('JWT token is required');\r\n error.code = ErrorTypes.JWT_INVALID;\r\n throw error;\r\n }\r\n\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n const error = new Error('Invalid JWT format: expected 3 parts separated by dots');\r\n error.code = ErrorTypes.JWT_INVALID;\r\n throw error;\r\n }\r\n\r\n let payload;\r\n try {\r\n payload = JSON.parse(base64UrlDecode(parts[1]));\r\n } catch (e) {\r\n const error = new Error('Invalid JWT: failed to decode payload');\r\n error.code = ErrorTypes.JWT_PARSE_FAILED;\r\n error.originalError = e;\r\n throw error;\r\n }\r\n\r\n if (validateExpiry && payload.exp) {\r\n const expiryTime = payload.exp * 1000;\r\n const bufferTime = bufferSeconds * 1000;\r\n const now = Date.now();\r\n\r\n if (expiryTime < now + bufferTime) {\r\n const error = new Error(\r\n expiryTime < now\r\n ? 'JWT token expired'\r\n : `JWT token expires within ${bufferSeconds} seconds`\r\n );\r\n error.code = ErrorTypes.JWT_EXPIRED;\r\n error.expiredAt = new Date(expiryTime).toISOString();\r\n throw error;\r\n }\r\n }\r\n\r\n if (payload.iat) {\r\n const issuedAt = payload.iat * 1000;\r\n const clockSkew = 60 * 1000;\r\n\r\n if (issuedAt > Date.now() + clockSkew) {\r\n const error = new Error('JWT token issued in the future');\r\n error.code = ErrorTypes.JWT_INVALID;\r\n throw error;\r\n }\r\n }\r\n\r\n const userId = payload.userId || payload.sub || payload.user_id;\r\n\r\n if (!userId) {\r\n const error = new Error('Cannot extract userId from JWT: missing userId, sub, or user_id claim');\r\n error.code = ErrorTypes.JWT_INVALID;\r\n throw error;\r\n }\r\n\r\n return { userId, payload };\r\n}\r\n\r\n/**\r\n * JWT에서 userId만 추출\r\n * @param {string} jwtToken - JWT 토큰\r\n * @returns {string|null}\r\n */\r\nexport function extractUserIdFromJWT(jwtToken) {\r\n try {\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n return null;\r\n }\r\n\r\n const payload = JSON.parse(base64UrlDecode(parts[1]));\r\n return payload.userId || payload.sub || payload.user_id || null;\r\n } catch (error) {\r\n console.error('Failed to extract userId from JWT:', error);\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * JWT 만료 여부 확인\r\n * @param {string} jwtToken - JWT 토큰\r\n * @param {number} [bufferSeconds=0] - 버퍼 시간 (초)\r\n * @returns {boolean}\r\n */\r\nexport function isJWTExpired(jwtToken, bufferSeconds = 0) {\r\n try {\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n return true;\r\n }\r\n\r\n const payload = JSON.parse(base64UrlDecode(parts[1]));\r\n\r\n if (!payload.exp) {\r\n return false;\r\n }\r\n\r\n const expiryTime = payload.exp * 1000;\r\n const bufferTime = bufferSeconds * 1000;\r\n\r\n return expiryTime < Date.now() + bufferTime;\r\n } catch (error) {\r\n console.error('Failed to check JWT expiration:', error);\r\n return true;\r\n }\r\n}\r\n\r\n/**\r\n * JWT 남은 유효 시간 반환\r\n * @param {string} jwtToken - JWT 토큰\r\n * @returns {number} - 밀리초, 만료 시 음수\r\n */\r\nexport function getJWTRemainingTime(jwtToken) {\r\n try {\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n return -1;\r\n }\r\n\r\n const payload = JSON.parse(base64UrlDecode(parts[1]));\r\n\r\n if (!payload.exp) {\r\n return Infinity;\r\n }\r\n\r\n return (payload.exp * 1000) - Date.now();\r\n } catch (error) {\r\n console.error('Failed to get JWT remaining time:', error);\r\n return -1;\r\n }\r\n}\r\n\r\n/**\r\n * JWT payload 디코딩 (검증 없이)\r\n * @param {string} jwtToken - JWT 토큰\r\n * @returns {Object|null}\r\n */\r\nexport function decodeJWTPayload(jwtToken) {\r\n try {\r\n const tokenWithoutBearer = stripBearer(jwtToken);\r\n const parts = tokenWithoutBearer.split('.');\r\n\r\n if (parts.length !== 3) {\r\n return null;\r\n }\r\n\r\n return JSON.parse(base64UrlDecode(parts[1]));\r\n } catch (error) {\r\n console.error('Failed to decode JWT payload:', error);\r\n return null;\r\n }\r\n}\r\n","/**\n * Some byte values, used as per STOMP specifications.\n *\n * Part of `@stomp/stompjs`.\n *\n * @internal\n */\nexport const BYTE = {\n // LINEFEED byte (octet 10)\n LF: '\\x0A',\n // NULL byte (octet 0)\n NULL: '\\x00',\n};\n//# sourceMappingURL=byte.js.map","import { BYTE } from './byte.js';\n/**\n * Frame class represents a STOMP frame.\n *\n * @internal\n */\nexport class FrameImpl {\n /**\n * body of the frame\n */\n get body() {\n if (!this._body && this.isBinaryBody) {\n this._body = new TextDecoder().decode(this._binaryBody);\n }\n return this._body || '';\n }\n /**\n * body as Uint8Array\n */\n get binaryBody() {\n if (!this._binaryBody && !this.isBinaryBody) {\n this._binaryBody = new TextEncoder().encode(this._body);\n }\n // At this stage it will definitely have a valid value\n return this._binaryBody;\n }\n /**\n * Frame constructor. `command`, `headers` and `body` are available as properties.\n *\n * @internal\n */\n constructor(params) {\n const { command, headers, body, binaryBody, escapeHeaderValues, skipContentLengthHeader, } = params;\n this.command = command;\n this.headers = Object.assign({}, headers || {});\n if (binaryBody) {\n this._binaryBody = binaryBody;\n this.isBinaryBody = true;\n }\n else {\n this._body = body || '';\n this.isBinaryBody = false;\n }\n this.escapeHeaderValues = escapeHeaderValues || false;\n this.skipContentLengthHeader = skipContentLengthHeader || false;\n }\n /**\n * deserialize a STOMP Frame from raw data.\n *\n * @internal\n */\n static fromRawFrame(rawFrame, escapeHeaderValues) {\n const headers = {};\n const trim = (str) => str.replace(/^\\s+|\\s+$/g, '');\n // In case of repeated headers, as per standards, first value need to be used\n for (const header of rawFrame.headers.reverse()) {\n const idx = header.indexOf(':');\n const key = trim(header[0]);\n let value = trim(header[1]);\n if (escapeHeaderValues &&\n rawFrame.command !== 'CONNECT' &&\n rawFrame.command !== 'CONNECTED') {\n value = FrameImpl.hdrValueUnEscape(value);\n }\n headers[key] = value;\n }\n return new FrameImpl({\n command: rawFrame.command,\n headers,\n binaryBody: rawFrame.binaryBody,\n escapeHeaderValues,\n });\n }\n /**\n * @internal\n */\n toString() {\n return this.serializeCmdAndHeaders();\n }\n /**\n * serialize this Frame in a format suitable to be passed to WebSocket.\n * If the body is string the output will be string.\n * If the body is binary (i.e. of type Unit8Array) it will be serialized to ArrayBuffer.\n *\n * @internal\n */\n serialize() {\n const cmdAndHeaders = this.serializeCmdAndHeaders();\n if (this.isBinaryBody) {\n return FrameImpl.toUnit8Array(cmdAndHeaders, this._binaryBody).buffer;\n }\n else {\n return cmdAndHeaders + this._body + BYTE.NULL;\n }\n }\n serializeCmdAndHeaders() {\n const lines = [this.command];\n if (this.skipContentLengthHeader) {\n delete this.headers['content-length'];\n }\n for (const name of Object.keys(this.headers || {})) {\n const value = this.headers[name];\n if (this.escapeHeaderValues &&\n this.command !== 'CONNECT' &&\n this.command !== 'CONNECTED') {\n lines.push(`${name}:${FrameImpl.hdrValueEscape(`${value}`)}`);\n }\n else {\n lines.push(`${name}:${value}`);\n }\n }\n if (this.isBinaryBody ||\n (!this.isBodyEmpty() && !this.skipContentLengthHeader)) {\n lines.push(`content-length:${this.bodyLength()}`);\n }\n return lines.join(BYTE.LF) + BYTE.LF + BYTE.LF;\n }\n isBodyEmpty() {\n return this.bodyLength() === 0;\n }\n bodyLength() {\n const binaryBody = this.binaryBody;\n return binaryBody ? binaryBody.length : 0;\n }\n /**\n * Compute the size of a UTF-8 string by counting its number of bytes\n * (and not the number of characters composing the string)\n */\n static sizeOfUTF8(s) {\n return s ? new TextEncoder().encode(s).length : 0;\n }\n static toUnit8Array(cmdAndHeaders, binaryBody) {\n const uint8CmdAndHeaders = new TextEncoder().encode(cmdAndHeaders);\n const nullTerminator = new Uint8Array([0]);\n const uint8Frame = new Uint8Array(uint8CmdAndHeaders.length + binaryBody.length + nullTerminator.length);\n uint8Frame.set(uint8CmdAndHeaders);\n uint8Frame.set(binaryBody, uint8CmdAndHeaders.length);\n uint8Frame.set(nullTerminator, uint8CmdAndHeaders.length + binaryBody.length);\n return uint8Frame;\n }\n /**\n * Serialize a STOMP frame as per STOMP standards, suitable to be sent to the STOMP broker.\n *\n * @internal\n */\n static marshall(params) {\n const frame = new FrameImpl(params);\n return frame.serialize();\n }\n /**\n * Escape header values\n */\n static hdrValueEscape(str) {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\n/g, '\\\\n')\n .replace(/:/g, '\\\\c');\n }\n /**\n * UnEscape header values\n */\n static hdrValueUnEscape(str) {\n return str\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\c/g, ':')\n .replace(/\\\\\\\\/g, '\\\\');\n }\n}\n//# sourceMappingURL=frame-impl.js.map","/**\n * @internal\n */\nconst NULL = 0;\n/**\n * @internal\n */\nconst LF = 10;\n/**\n * @internal\n */\nconst CR = 13;\n/**\n * @internal\n */\nconst COLON = 58;\n/**\n * This is an evented, rec descent parser.\n * A stream of Octets can be passed and whenever it recognizes\n * a complete Frame or an incoming ping it will invoke the registered callbacks.\n *\n * All incoming Octets are fed into _onByte function.\n * Depending on current state the _onByte function keeps changing.\n * Depending on the state it keeps accumulating into _token and _results.\n * State is indicated by current value of _onByte, all states are named as _collect.\n *\n * STOMP standards https://stomp.github.io/stomp-specification-1.2.html\n * imply that all lengths are considered in bytes (instead of string lengths).\n * So, before actual parsing, if the incoming data is String it is converted to Octets.\n * This allows faithful implementation of the protocol and allows NULL Octets to be present in the body.\n *\n * There is no peek function on the incoming data.\n * When a state change occurs based on an Octet without consuming the Octet,\n * the Octet, after state change, is fed again (_reinjectByte).\n * This became possible as the state change can be determined by inspecting just one Octet.\n *\n * There are two modes to collect the body, if content-length header is there then it by counting Octets\n * otherwise it is determined by NULL terminator.\n *\n * Following the standards, the command and headers are converted to Strings\n * and the body is returned as Octets.\n * Headers are returned as an array and not as Hash - to allow multiple occurrence of an header.\n *\n * This parser does not use Regular Expressions as that can only operate on Strings.\n *\n * It handles if multiple STOMP frames are given as one chunk, a frame is split into multiple chunks, or\n * any combination there of. The parser remembers its state (any partial frame) and continues when a new chunk\n * is pushed.\n *\n * Typically the higher level function will convert headers to Hash, handle unescaping of header values\n * (which is protocol version specific), and convert body to text.\n *\n * Check the parser.spec.js to understand cases that this parser is supposed to handle.\n *\n * Part of `@stomp/stompjs`.\n *\n * @internal\n */\nexport class Parser {\n constructor(onFrame, onIncomingPing) {\n this.onFrame = onFrame;\n this.onIncomingPing = onIncomingPing;\n this._encoder = new TextEncoder();\n this._decoder = new TextDecoder();\n this._token = [];\n this._initState();\n }\n parseChunk(segment, appendMissingNULLonIncoming = false) {\n let chunk;\n if (typeof segment === 'string') {\n chunk = this._encoder.encode(segment);\n }\n else {\n chunk = new Uint8Array(segment);\n }\n // See https://github.com/stomp-js/stompjs/issues/89\n // Remove when underlying issue is fixed.\n //\n // Send a NULL byte, if the last byte of a Text frame was not NULL.F\n if (appendMissingNULLonIncoming && chunk[chunk.length - 1] !== 0) {\n const chunkWithNull = new Uint8Array(chunk.length + 1);\n chunkWithNull.set(chunk, 0);\n chunkWithNull[chunk.length] = 0;\n chunk = chunkWithNull;\n }\n // tslint:disable-next-line:prefer-for-of\n for (let i = 0; i < chunk.length; i++) {\n const byte = chunk[i];\n this._onByte(byte);\n }\n }\n // The following implements a simple Rec Descent Parser.\n // The grammar is simple and just one byte tells what should be the next state\n _collectFrame(byte) {\n if (byte === NULL) {\n // Ignore\n return;\n }\n if (byte === CR) {\n // Ignore CR\n return;\n }\n if (byte === LF) {\n // Incoming Ping\n this.onIncomingPing();\n return;\n }\n this._onByte = this._collectCommand;\n this._reinjectByte(byte);\n }\n _collectCommand(byte) {\n if (byte === CR) {\n // Ignore CR\n return;\n }\n if (byte === LF) {\n this._results.command = this._consumeTokenAsUTF8();\n this._onByte = this._collectHeaders;\n return;\n }\n this._consumeByte(byte);\n }\n _collectHeaders(byte) {\n if (byte === CR) {\n // Ignore CR\n return;\n }\n if (byte === LF) {\n this._setupCollectBody();\n return;\n }\n this._onByte = this._collectHeaderKey;\n this._reinjectByte(byte);\n }\n _reinjectByte(byte) {\n this._onByte(byte);\n }\n _collectHeaderKey(byte) {\n if (byte === COLON) {\n this._headerKey = this._consumeTokenAsUTF8();\n this._onByte = this._collectHeaderValue;\n return;\n }\n this._consumeByte(byte);\n }\n _collectHeaderValue(byte) {\n if (byte === CR) {\n // Ignore CR\n return;\n }\n if (byte === LF) {\n this._results.headers.push([\n this._headerKey,\n this._consumeTokenAsUTF8(),\n ]);\n this._headerKey = undefined;\n this._onByte = this._collectHeaders;\n return;\n }\n this._consumeByte(byte);\n }\n _setupCollectBody() {\n const contentLengthHeader = this._results.headers.filter((header) => {\n return header[0] === 'content-length';\n })[0];\n if (contentLengthHeader) {\n this._bodyBytesRemaining = parseInt(contentLengthHeader[1], 10);\n this._onByte = this._collectBodyFixedSize;\n }\n else {\n this._onByte = this._collectBodyNullTerminated;\n }\n }\n _collectBodyNullTerminated(byte) {\n if (byte === NULL) {\n this._retrievedBody();\n return;\n }\n this._consumeByte(byte);\n }\n _collectBodyFixedSize(byte) {\n // It is post decrement, so that we discard the trailing NULL octet\n if (this._bodyBytesRemaining-- === 0) {\n this._retrievedBody();\n return;\n }\n this._consumeByte(byte);\n }\n _retrievedBody() {\n this._results.binaryBody = this._consumeTokenAsRaw();\n try {\n this.onFrame(this._results);\n }\n catch (e) {\n console.log(`Ignoring an exception thrown by a frame handler. Original exception: `, e);\n }\n this._initState();\n }\n // Rec Descent Parser helpers\n _consumeByte(byte) {\n this._token.push(byte);\n }\n _consumeTokenAsUTF8() {\n return this._decoder.decode(this._consumeTokenAsRaw());\n }\n _consumeTokenAsRaw() {\n const rawResult = new Uint8Array(this._token);\n this._token = [];\n return rawResult;\n }\n _initState() {\n this._results = {\n command: undefined,\n headers: [],\n binaryBody: undefined,\n };\n this._token = [];\n this._headerKey = undefined;\n this._onByte = this._collectFrame;\n }\n}\n//# sourceMappingURL=parser.js.map","/**\n * Possible states for the IStompSocket\n */\nexport var StompSocketState;\n(function (StompSocketState) {\n StompSocketState[StompSocketState[\"CONNECTING\"] = 0] = \"CONNECTING\";\n StompSocketState[StompSocketState[\"OPEN\"] = 1] = \"OPEN\";\n StompSocketState[StompSocketState[\"CLOSING\"] = 2] = \"CLOSING\";\n StompSocketState[StompSocketState[\"CLOSED\"] = 3] = \"CLOSED\";\n})(StompSocketState || (StompSocketState = {}));\n/**\n * Possible activation state\n */\nexport var ActivationState;\n(function (ActivationState) {\n ActivationState[ActivationState[\"ACTIVE\"] = 0] = \"ACTIVE\";\n ActivationState[ActivationState[\"DEACTIVATING\"] = 1] = \"DEACTIVATING\";\n ActivationState[ActivationState[\"INACTIVE\"] = 2] = \"INACTIVE\";\n})(ActivationState || (ActivationState = {}));\n/**\n * Possible reconnection wait time modes\n */\nexport var ReconnectionTimeMode;\n(function (ReconnectionTimeMode) {\n ReconnectionTimeMode[ReconnectionTimeMode[\"LINEAR\"] = 0] = \"LINEAR\";\n ReconnectionTimeMode[ReconnectionTimeMode[\"EXPONENTIAL\"] = 1] = \"EXPONENTIAL\";\n})(ReconnectionTimeMode || (ReconnectionTimeMode = {}));\n/**\n * Possible ticker strategies for outgoing heartbeat ping\n */\nexport var TickerStrategy;\n(function (TickerStrategy) {\n TickerStrategy[\"Interval\"] = \"interval\";\n TickerStrategy[\"Worker\"] = \"worker\";\n})(TickerStrategy || (TickerStrategy = {}));\n//# sourceMappingURL=types.js.map","import { TickerStrategy } from './types.js';\nexport class Ticker {\n constructor(_interval, _strategy = TickerStrategy.Interval, _debug) {\n this._interval = _interval;\n this._strategy = _strategy;\n this._debug = _debug;\n this._workerScript = `\n var startTime = Date.now();\n setInterval(function() {\n self.postMessage(Date.now() - startTime);\n }, ${this._interval});\n `;\n }\n start(tick) {\n this.stop();\n if (this.shouldUseWorker()) {\n this.runWorker(tick);\n }\n else {\n this.runInterval(tick);\n }\n }\n stop() {\n this.disposeWorker();\n this.disposeInterval();\n }\n shouldUseWorker() {\n return (typeof Worker !== 'undefined' && this._strategy === TickerStrategy.Worker);\n }\n runWorker(tick) {\n this._debug('Using runWorker for outgoing pings');\n if (!this._worker) {\n this._worker = new Worker(URL.createObjectURL(new Blob([this._workerScript], { type: 'text/javascript' })));\n this._worker.onmessage = message => tick(message.data);\n }\n }\n runInterval(tick) {\n this._debug('Using runInterval for outgoing pings');\n if (!this._timer) {\n const startTime = Date.now();\n this._timer = setInterval(() => {\n tick(Date.now() - startTime);\n }, this._interval);\n }\n }\n disposeWorker() {\n if (this._worker) {\n this._worker.terminate();\n delete this._worker;\n this._debug('Outgoing ping disposeWorker');\n }\n }\n disposeInterval() {\n if (this._timer) {\n clearInterval(this._timer);\n delete this._timer;\n this._debug('Outgoing ping disposeInterval');\n }\n }\n}\n//# sourceMappingURL=ticker.js.map","/**\n * Supported STOMP versions\n *\n * Part of `@stomp/stompjs`.\n */\nexport class Versions {\n /**\n * Takes an array of versions, typical elements '1.2', '1.1', or '1.0'\n *\n * You will be creating an instance of this class if you want to override\n * supported versions to be declared during STOMP handshake.\n */\n constructor(versions) {\n this.versions = versions;\n }\n /**\n * Used as part of CONNECT STOMP Frame\n */\n supportedVersions() {\n return this.versions.join(',');\n }\n /**\n * Used while creating a WebSocket\n */\n protocolVersions() {\n return this.versions.map(x => `v${x.replace('.', '')}.stomp`);\n }\n}\n/**\n * Indicates protocol version 1.0\n */\nVersions.V1_0 = '1.0';\n/**\n * Indicates protocol version 1.1\n */\nVersions.V1_1 = '1.1';\n/**\n * Indicates protocol version 1.2\n */\nVersions.V1_2 = '1.2';\n/**\n * @internal\n */\nVersions.default = new Versions([\n Versions.V1_2,\n Versions.V1_1,\n Versions.V1_0,\n]);\n//# sourceMappingURL=versions.js.map","import { augmentWebsocket } from './augment-websocket.js';\nimport { BYTE } from './byte.js';\nimport { FrameImpl } from './frame-impl.js';\nimport { Parser } from './parser.js';\nimport { Ticker } from './ticker.js';\nimport { StompSocketState, } from './types.js';\nimport { Versions } from './versions.js';\n/**\n * The STOMP protocol handler\n *\n * Part of `@stomp/stompjs`.\n *\n * @internal\n */\nexport class StompHandler {\n get connectedVersion() {\n return this._connectedVersion;\n }\n get connected() {\n return this._connected;\n }\n constructor(_client, _webSocket, config) {\n this._client = _client;\n this._webSocket = _webSocket;\n this._connected = false;\n this._serverFrameHandlers = {\n // [CONNECTED Frame](https://stomp.github.com/stomp-specification-1.2.html#CONNECTED_Frame)\n CONNECTED: frame => {\n this.debug(`connected to server ${frame.headers.server}`);\n this._connected = true;\n this._connectedVersion = frame.headers.version;\n // STOMP version 1.2 needs header values to be escaped\n if (this._connectedVersion === Versions.V1_2) {\n this._escapeHeaderValues = true;\n }\n this._setupHeartbeat(frame.headers);\n this.onConnect(frame);\n },\n // [MESSAGE Frame](https://stomp.github.com/stomp-specification-1.2.html#MESSAGE)\n MESSAGE: frame => {\n // the callback is registered when the client calls\n // `subscribe()`.\n // If there is no registered subscription for the received message,\n // the default `onUnhandledMessage` callback is used that the client can set.\n // This is useful for subscriptions that are automatically created\n // on the browser side (e.g. [RabbitMQ's temporary\n // queues](https://www.rabbitmq.com/stomp.html)).\n const subscription = frame.headers.subscription;\n const onReceive = this._subscriptions[subscription] || this.onUnhandledMessage;\n // bless the frame to be a Message\n const message = frame;\n const client = this;\n const messageId = this._connectedVersion === Versions.V1_2\n ? message.headers.ack\n : message.headers['message-id'];\n // add `ack()` and `nack()` methods directly to the returned frame\n // so that a simple call to `message.ack()` can acknowledge the message.\n message.ack = (headers = {}) => {\n return client.ack(messageId, subscription, headers);\n };\n message.nack = (headers = {}) => {\n return client.nack(messageId, subscription, headers);\n };\n onReceive(message);\n },\n // [RECEIPT Frame](https://stomp.github.com/stomp-specification-1.2.html#RECEIPT)\n RECEIPT: frame => {\n const callback = this._receiptWatchers[frame.headers['receipt-id']];\n if (callback) {\n callback(frame);\n // Server will acknowledge only once, remove the callback\n delete this._receiptWatchers[frame.headers['receipt-id']];\n }\n else {\n this.onUnhandledReceipt(frame);\n }\n },\n // [ERROR Frame](https://stomp.github.com/stomp-specification-1.2.html#ERROR)\n ERROR: frame => {\n this.onStompError(frame);\n },\n };\n // used to index subscribers\n this._counter = 0;\n // subscription callbacks indexed by subscriber's ID\n this._subscriptions = {};\n // receipt-watchers indexed by receipts-ids\n this._receiptWatchers = {};\n this._partialData = '';\n this._escapeHeaderValues = false;\n this._lastServerActivityTS = Date.now();\n this.debug = config.debug;\n this.stompVersions = config.stompVersions;\n this.connectHeaders = config.connectHeaders;\n this.disconnectHeaders = config.disconnectHeaders;\n this.heartbeatIncoming = config.heartbeatIncoming;\n this.heartbeatToleranceMultiplier = config.heartbeatGracePeriods;\n this.heartbeatOutgoing = config.heartbeatOutgoing;\n this.splitLargeFrames = config.splitLargeFrames;\n this.maxWebSocketChunkSize = config.maxWebSocketChunkSize;\n this.forceBinaryWSFrames = config.forceBinaryWSFrames;\n this.logRawCommunication = config.logRawCommunication;\n this.appendMissingNULLonIncoming = config.appendMissingNULLonIncoming;\n this.discardWebsocketOnCommFailure = config.discardWebsocketOnCommFailure;\n this.onConnect = config.onConnect;\n this.onDisconnect = config.onDisconnect;\n this.onStompError = config.onStompError;\n this.onWebSocketClose = config.onWebSocketClose;\n this.onWebSocketError = config.onWebSocketError;\n this.onUnhandledMessage = config.onUnhandledMessage;\n this.onUnhandledReceipt = config.onUnhandledReceipt;\n this.onUnhandledFrame = config.onUnhandledFrame;\n this.onHeartbeatReceived = config.onHeartbeatReceived;\n this.onHeartbeatLost = config.onHeartbeatLost;\n }\n start() {\n const parser = new Parser(\n // On Frame\n rawFrame => {\n const frame = FrameImpl.fromRawFrame(rawFrame, this._escapeHeaderValues);\n // if this.logRawCommunication is set, the rawChunk is logged at this._webSocket.onmessage\n if (!this.logRawCommunication) {\n this.debug(`<<< ${frame}`);\n }\n const serverFrameHandler = this._serverFrameHandlers[frame.command] || this.onUnhandledFrame;\n serverFrameHandler(frame);\n }, \n // On Incoming Ping\n () => {\n this.debug('<<< PONG');\n this.onHeartbeatReceived();\n });\n this._webSocket.onmessage = (evt) => {\n this.debug('Received data');\n this._lastServerActivityTS = Date.now();\n if (this.logRawCommunication) {\n const rawChunkAsString = evt.data instanceof ArrayBuffer\n ? new TextDecoder().decode(evt.data)\n : evt.data;\n this.debug(`<<< ${rawChunkAsString}`);\n }\n parser.parseChunk(evt.data, this.appendMissingNULLonIncoming);\n };\n this._webSocket.onclose = (closeEvent) => {\n this.debug(`Connection closed to ${this._webSocket.url}`);\n this._cleanUp();\n this.onWebSocketClose(closeEvent);\n };\n this._webSocket.onerror = (errorEvent) => {\n this.onWebSocketError(errorEvent);\n };\n this._webSocket.onopen = () => {\n // Clone before updating\n const connectHeaders = Object.assign({}, this.connectHeaders);\n this.debug('Web Socket Opened...');\n connectHeaders['accept-version'] = this.stompVersions.supportedVersions();\n connectHeaders['heart-beat'] = [\n this.heartbeatOutgoing,\n this.heartbeatIncoming,\n ].join(',');\n this._transmit({ command: 'CONNECT', headers: connectHeaders });\n };\n }\n _setupHeartbeat(headers) {\n if (headers.version !== Versions.V1_1 &&\n headers.version !== Versions.V1_2) {\n return;\n }\n // It is valid for the server to not send this header\n // https://stomp.github.io/stomp-specification-1.2.html#Heart-beating\n if (!headers['heart-beat']) {\n return;\n }\n // heart-beat header received from the server looks like:\n //\n // heart-beat: sx, sy\n const [serverOutgoing, serverIncoming] = headers['heart-beat']\n .split(',')\n .map((v) => parseInt(v, 10));\n if (this.heartbeatOutgoing !== 0 && serverIncoming !== 0) {\n const ttl = Math.max(this.heartbeatOutgoing, serverIncoming);\n this.debug(`send PING every ${ttl}ms`);\n this._pinger = new Ticker(ttl, this._client.heartbeatStrategy, this.debug);\n this._pinger.start(() => {\n if (this._webSocket.readyState === StompSocketState.OPEN) {\n this._webSocket.send(BYTE.LF);\n this.debug('>>> PING');\n }\n });\n }\n if (this.heartbeatIncoming !== 0 && serverOutgoing !== 0) {\n const ttl = Math.max(this.heartbeatIncoming, serverOutgoing);\n this.debug(`check PONG every ${ttl}ms`);\n this._ponger = setInterval(() => {\n const delta = Date.now() - this._lastServerActivityTS;\n // We wait multiple grace periods to be flexible on window's setInterval calls\n if (delta > ttl * this.heartbeatToleranceMultiplier) {\n this.debug(`did not receive server activity for the last ${delta}ms`);\n this.onHeartbeatLost();\n this._closeOrDiscardWebsocket();\n }\n }, ttl);\n }\n }\n _closeOrDiscardWebsocket() {\n if (this.discardWebsocketOnCommFailure) {\n this.debug('Discarding websocket, the underlying socket may linger for a while');\n this.discardWebsocket();\n }\n else {\n this.debug('Issuing close on the websocket');\n this._closeWebsocket();\n }\n }\n forceDisconnect() {\n if (this._webSocket) {\n if (this._webSocket.readyState === StompSocketState.CONNECTING ||\n this._webSocket.readyState === StompSocketState.OPEN) {\n this._closeOrDiscardWebsocket();\n }\n }\n }\n _closeWebsocket() {\n this._webSocket.onmessage = () => { }; // ignore messages\n this._webSocket.close();\n }\n discardWebsocket() {\n if (typeof this._webSocket.terminate !== 'function') {\n augmentWebsocket(this._webSocket, (msg) => this.debug(msg));\n }\n // @ts-ignore - this method will be there at this stage\n this._webSocket.terminate();\n }\n _transmit(params) {\n const { command, headers, body, binaryBody, skipContentLengthHeader } = params;\n const frame = new FrameImpl({\n command,\n headers,\n body,\n binaryBody,\n escapeHeaderValues: this._escapeHeaderValues,\n skipContentLengthHeader,\n });\n let rawChunk = frame.serialize();\n if (this.logRawCommunication) {\n this.debug(`>>> ${rawChunk}`);\n }\n else {\n this.debug(`>>> ${frame}`);\n }\n if (this.forceBinaryWSFrames && typeof rawChunk === 'string') {\n rawChunk = new TextEncoder().encode(rawChunk);\n }\n if (typeof rawChunk !== 'string' || !this.splitLargeFrames) {\n this._webSocket.send(rawChunk);\n }\n else {\n let out = rawChunk;\n while (out.length > 0) {\n const chunk = out.substring(0, this.maxWebSocketChunkSize);\n out = out.substring(this.maxWebSocketChunkSize);\n this._webSocket.send(chunk);\n this.debug(`chunk sent = ${chunk.length}, remaining = ${out.length}`);\n }\n }\n }\n dispose() {\n if (this.connected) {\n try {\n // clone before updating\n const disconnectHeaders = Object.assign({}, this.disconnectHeaders);\n if (!disconnectHeaders.receipt) {\n disconnectHeaders.receipt = `close-${this._counter++}`;\n }\n this.watchForReceipt(disconnectHeaders.receipt, frame => {\n this._closeWebsocket();\n this._cleanUp();\n this.onDisconnect(frame);\n });\n this._transmit({ command: 'DISCONNECT', headers: disconnectHeaders });\n }\n catch (error) {\n this.debug(`Ignoring error during disconnect ${error}`);\n }\n }\n else {\n if (this._webSocket.readyState === StompSocketState.CONNECTING ||\n this._webSocket.readyState === StompSocketState.OPEN) {\n this._closeWebsocket();\n }\n }\n }\n _cleanUp() {\n this._connected = false;\n if (this._pinger) {\n this._pinger.stop();\n this._pinger = undefined;\n }\n if (this._ponger) {\n clearInterval(this._ponger);\n this._ponger = undefined;\n }\n }\n publish(params) {\n const { destination, headers, body, binaryBody, skipContentLengthHeader } = params;\n const hdrs = Object.assign({ destination }, headers);\n this._transmit({\n command: 'SEND',\n headers: hdrs,\n body,\n binaryBody,\n skipContentLengthHeader,\n });\n }\n watchForReceipt(receiptId, callback) {\n this._receiptWatchers[receiptId] = callback;\n }\n subscribe(destination, callback, headers = {}) {\n headers = Object.assign({}, headers);\n if (!headers.id) {\n headers.id = `sub-${this._counter++}`;\n }\n headers.destination = destination;\n this._subscriptions[headers.id] = callback;\n this._transmit({ command: 'SUBSCRIBE', headers });\n const client = this;\n return {\n id: headers.id,\n unsubscribe(hdrs) {\n return client.unsubscribe(headers.id, hdrs);\n },\n };\n }\n unsubscribe(id, headers = {}) {\n headers = Object.assign({}, headers);\n delete this._subscriptions[id];\n headers.id = id;\n this._transmit({ command: 'UNSUBSCRIBE', headers });\n }\n begin(transactionId) {\n const txId = transactionId || `tx-${this._counter++}`;\n this._transmit({\n command: 'BEGIN',\n headers: {\n transaction: txId,\n },\n });\n const client = this;\n return {\n id: txId,\n commit() {\n client.commit(txId);\n },\n abort() {\n client.abort(txId);\n },\n };\n }\n commit(transactionId) {\n this._transmit({\n command: 'COMMIT',\n headers: {\n transaction: transactionId,\n },\n });\n }\n abort(transactionId) {\n this._transmit({\n command: 'ABORT',\n headers: {\n transaction: transactionId,\n },\n });\n }\n ack(messageId, subscriptionId, headers = {}) {\n headers = Object.assign({}, headers);\n if (this._connectedVersion === Versions.V1_2) {\n headers.id = messageId;\n }\n else {\n headers['message-id'] = messageId;\n }\n headers.subscription = subscriptionId;\n this._transmit({ command: 'ACK', headers });\n }\n nack(messageId, subscriptionId, headers = {}) {\n headers = Object.assign({}, headers);\n if (this._connectedVersion === Versions.V1_2) {\n headers.id = messageId;\n }\n else {\n headers['message-id'] = messageId;\n }\n headers.subscription = subscriptionId;\n return this._transmit({ command: 'NACK', headers });\n }\n}\n//# sourceMappingURL=stomp-handler.js.map","/**\n * @internal\n */\nexport function augmentWebsocket(webSocket, debug) {\n webSocket.terminate = function () {\n const noOp = () => { };\n // set all callbacks to no op\n this.onerror = noOp;\n this.onmessage = noOp;\n this.onopen = noOp;\n const ts = new Date();\n const id = Math.random().toString().substring(2, 8); // A simulated id\n const origOnClose = this.onclose;\n // Track delay in actual closure of the socket\n this.onclose = closeEvent => {\n const delay = new Date().getTime() - ts.getTime();\n debug(`Discarded socket (#${id}) closed after ${delay}ms, with code/reason: ${closeEvent.code}/${closeEvent.reason}`);\n };\n this.close();\n origOnClose?.call(webSocket, {\n code: 4001,\n reason: `Quick discarding socket (#${id}) without waiting for the shutdown sequence.`,\n wasClean: false,\n });\n };\n}\n//# sourceMappingURL=augment-websocket.js.map","import { StompHandler } from './stomp-handler.js';\nimport { ActivationState, ReconnectionTimeMode, StompSocketState, TickerStrategy, } from './types.js';\nimport { Versions } from './versions.js';\n/**\n * STOMP Client Class.\n *\n * Part of `@stomp/stompjs`.\n *\n * This class provides a robust implementation for connecting to and interacting with a\n * STOMP-compliant messaging broker over WebSocket. It supports STOMP versions 1.2, 1.1, and 1.0.\n *\n * Features:\n * - Handles automatic reconnections.\n * - Supports heartbeat mechanisms to detect and report communication failures.\n * - Allows customization of connection and WebSocket behaviors through configurations.\n * - Compatible with both browser environments and Node.js with polyfill support for WebSocket.\n */\nexport class Client {\n /**\n * Provides access to the underlying WebSocket instance.\n * This property is **read-only**.\n *\n * Example:\n * ```javascript\n * const webSocket = client.webSocket;\n * if (webSocket) {\n * console.log('WebSocket is connected:', webSocket.readyState === WebSocket.OPEN);\n * }\n * ```\n *\n * **Caution:**\n * Directly interacting with the WebSocket instance (e.g., sending or receiving frames)\n * can interfere with the proper functioning of this library. Such actions may cause\n * unexpected behavior, disconnections, or invalid state in the library's internal mechanisms.\n *\n * Instead, use the library's provided methods to manage STOMP communication.\n *\n * @returns The WebSocket instance used by the STOMP handler, or `undefined` if not connected.\n */\n get webSocket() {\n return this._stompHandler?._webSocket;\n }\n /**\n * Allows customization of the disconnection headers.\n *\n * Any changes made during an active session will also be applied immediately.\n *\n * Example:\n * ```javascript\n * client.disconnectHeaders = {\n * receipt: 'custom-receipt-id'\n * };\n * ```\n */\n get disconnectHeaders() {\n return this._disconnectHeaders;\n }\n set disconnectHeaders(value) {\n this._disconnectHeaders = value;\n if (this._stompHandler) {\n this._stompHandler.disconnectHeaders = this._disconnectHeaders;\n }\n }\n /**\n * Indicates whether there is an active connection to the STOMP broker.\n *\n * Usage:\n * ```javascript\n * if (client.connected) {\n * console.log('Client is connected to the broker.');\n * } else {\n * console.log('No connection to the broker.');\n * }\n * ```\n *\n * @returns `true` if the client is currently connected, `false` otherwise.\n */\n get connected() {\n return !!this._stompHandler && this._stompHandler.connected;\n }\n /**\n * The version of the STOMP protocol negotiated with the server during connection.\n *\n * This is a **read-only** property and reflects the negotiated protocol version after\n * a successful connection.\n *\n * Example:\n * ```javascript\n * console.log('Connected STOMP version:', client.connectedVersion);\n * ```\n *\n * @returns The negotiated STOMP protocol version or `undefined` if not connected.\n */\n get connectedVersion() {\n return this._stompHandler ? this._stompHandler.connectedVersion : undefined;\n }\n /**\n * Indicates whether the client is currently active.\n *\n * A client is considered active if it is connected or actively attempting to reconnect.\n *\n * Example:\n * ```javascript\n * if (client.active) {\n * console.log('The client is active.');\n * } else {\n * console.log('The client is inactive.');\n * }\n * ```\n *\n * @returns `true` if the client is active, otherwise `false`.\n */\n get active() {\n return this.state === ActivationState.ACTIVE;\n }\n _changeState(state) {\n this.state = state;\n this.onChangeState(state);\n }\n /**\n * Constructs a new STOMP client instance.\n *\n * The constructor initializes default values and sets up no-op callbacks for all events.\n * Configuration can be passed during construction, or updated later using `configure`.\n *\n * Example:\n * ```javascript\n * const client = new Client({\n * brokerURL: 'wss://broker.example.com',\n * reconnectDelay: 5000\n * });\n * ```\n *\n * @param conf Optional configuration object to initialize the client with.\n */\n constructor(conf = {}) {\n /**\n * STOMP protocol versions to use during the handshake. By default, the client will attempt\n * versions `1.2`, `1.1`, and `1.0` in descending order of preference.\n *\n * Example:\n * ```javascript\n * // Configure the client to only use versions 1.1 and 1.0\n * client.stompVersions = new Versions(['1.1', '1.0']);\n * ```\n */\n this.stompVersions = Versions.default;\n /**\n * Timeout for establishing STOMP connection, in milliseconds.\n *\n * If the connection is not established within this period, the attempt will fail.\n * The default is `0`, meaning no timeout is set for connection attempts.\n *\n * Example:\n * ```javascript\n * client.connectionTimeout = 5000; // Fail connection if not established in 5 seconds\n * ```\n */\n this.connectionTimeout = 0;\n /**\n * Delay (in milliseconds) between reconnection attempts if the connection drops.\n *\n * Set to `0` to disable automatic reconnections. The default value is `5000` ms (5 seconds).\n *\n * Example:\n * ```javascript\n * client.reconnectDelay = 3000; // Attempt reconnection every 3 seconds\n * client.reconnectDelay = 0; // Disable automatic reconnection\n * ```\n */\n this.reconnectDelay = 5000;\n /**\n * The next reconnection delay, used internally.\n * Initialized to the value of [Client#reconnectDelay]{@link Client#reconnectDelay}, and it may\n * dynamically change based on [Client#reconnectTimeMode]{@link Client#reconnectTimeMode}.\n */\n this._nextReconnectDelay = 0;\n /**\n * Maximum delay (in milliseconds) between reconnection attempts when using exponential backoff.\n *\n * Default is 15 minutes (`15 * 60 * 1000` milliseconds). If `0`, there will be no upper limit.\n *\n * Example:\n * ```javascript\n * client.maxReconnectDelay = 10000; // Maximum wait time is 10 seconds\n * ```\n */\n this.maxReconnectDelay = 15 * 60 * 1000;\n /**\n * Mode for determining the time interval between reconnection attempts.\n *\n * Available modes:\n * - `ReconnectionTimeMode.LINEAR` (default): Fixed delays between reconnection attempts.\n * - `ReconnectionTimeMode.EXPONENTIAL`: Delay doubles after each attempt, capped by [maxReconnectDelay]{@link Client#maxReconnectDelay}.\n *\n * Example:\n * ```javascript\n * client.reconnectTimeMode = ReconnectionTimeMode.EXPONENTIAL;\n * client.reconnectDelay = 200; // Initial delay of 200 ms, doubles with each attempt\n * client.maxReconnectDelay = 2 * 60 * 1000; // Cap delay at 10 minutes\n * ```\n */\n this.reconnectTimeMode = ReconnectionTimeMode.LINEAR;\n /**\n * Interval (in milliseconds) for receiving heartbeat signals from the server.\n *\n * Specifies the expected frequency of heartbeats sent by the server. Set to `0` to disable.\n *\n * Example:\n * ```javascript\n * client.heartbeatIncoming = 10000; // Expect a heartbeat every 10 seconds\n * ```\n */\n this.heartbeatIncoming = 10000;\n /**\n * Multiplier for adjusting tolerance when processing heartbeat signals.\n *\n * Tolerance level is calculated using the multiplier:\n * `tolerance = heartbeatIncoming * heartbeatToleranceMultiplier`.\n * This helps account for delays in network communication or variations in timings.\n *\n * Default value is `2`.\n *\n * Example:\n * ```javascript\n * client.heartbeatToleranceMultiplier = 2.5; // Tolerates longer delays\n * ```\n */\n this.heartbeatToleranceMultiplier = 2;\n /**\n * Interval (in milliseconds) for sending heartbeat signals to the server.\n *\n * Specifies how frequently heartbeats should be sent to the server. Set to `0` to disable.\n *\n * Example:\n * ```javascript\n * client.heartbeatOutgoing = 5000; // Send a heartbeat every 5 seconds\n * ```\n */\n this.heartbeatOutgoing = 10000;\n /**\n * Strategy for sending outgoing heartbeats.\n *\n * Options:\n * - `TickerStrategy.Worker`: Uses Web Workers for sending heartbeats (recommended for long-running or background sessions).\n * - `TickerStrategy.Interval`: Uses standard JavaScript `setInterval` (default).\n *\n * Note:\n * - If Web Workers are unavailable (e.g., in Node.js), the `Interval` strategy is used automatically.\n * - Web Workers are preferable in browsers for reducing disconnects when tabs are in the background.\n *\n * Example:\n * ```javascript\n * client.heartbeatStrategy = TickerStrategy.Worker;\n * ```\n */\n this.heartbeatStrategy = TickerStrategy.Interval;\n /**\n * Enables splitting of large text WebSocket frames into smaller chunks.\n *\n * This setting is enabled for brokers that support only chunked messages (e.g., Java Spring-based brokers).\n * Default is `false`.\n *\n * Warning:\n * - Should not be used with WebSocket-compliant brokers, as chunking may cause large message failures.\n * - Binary WebSocket frames are never split.\n *\n * Example:\n * ```javascript\n * client.splitLargeFrames = true;\n * client.maxWebSocketChunkSize = 4096; // Allow chunks of 4 KB\n * ```\n */\n this.splitLargeFrames = false;\n /**\n * Maximum size (in bytes) for individual WebSocket chunks if [splitLargeFrames]{@link Client#splitLargeFrames} is enabled.\n *\n * Default is 8 KB (`8 * 1024` bytes). This value has no effect if [splitLargeFrames]{@link Client#splitLargeFrames} is `false`.\n */\n this.maxWebSocketChunkSize = 8 * 1024;\n /**\n * Forces all WebSocket frames to use binary transport, irrespective of payload type.\n *\n * Default behavior determines frame type based on payload (e.g., binary data for ArrayBuffers).\n *\n * Example:\n * ```javascript\n * client.forceBinaryWSFrames = true;\n * ```\n */\n this.forceBinaryWSFrames = false;\n /**\n * Workaround for a React Native WebSocket bug, where messages containing `NULL` are chopped.\n *\n * Enabling this appends a `NULL` character to incoming frames to ensure they remain valid STOMP packets.\n *\n * Warning:\n * - For brokers that split large messages, this may cause data loss or connection termination.\n *\n * Example:\n * ```javascript\n * client.appendMissingNULLonIncoming = true;\n * ```\n */\n this.appendMissingNULLonIncoming = false;\n /**\n * Instruct the library to immediately terminate the socket on communication failures, even\n * before the WebSocket is completely closed.\n *\n * This is particularly useful in browser environments where WebSocket closure may get delayed,\n * causing prolonged reconnection intervals under certain failure conditions.\n *\n *\n * Example:\n * ```javascript\n * client.discardWebsocketOnCommFailure = true; // Enable aggressive closing of WebSocket\n * ```\n *\n * Default value: `false`.\n */\n this.discardWebsocketOnCommFailure = false;\n /**\n * Current activation state of the client.\n *\n * Possible states:\n * - `ActivationState.ACTIVE`: Client is connected or actively attempting to connect.\n * - `ActivationState.INACTIVE`: Client is disconnected and not attempting to reconnect.\n * - `ActivationState.DEACTIVATING`: Client is in the process of disconnecting.\n *\n * Note: The client may transition directly from `ACTIVE` to `INACTIVE` without entering\n * the `DEACTIVATING` state.\n */\n this.state = ActivationState.INACTIVE;\n // No op callbacks\n const noOp = () => { };\n this.debug = noOp;\n this.beforeConnect = noOp;\n this.onConnect = noOp;\n this.onDisconnect = noOp;\n this.onUnhandledMessage = noOp;\n this.onUnhandledReceipt = noOp;\n this.onUnhandledFrame = noOp;\n this.onHeartbeatReceived = noOp;\n this.onHeartbeatLost = noOp;\n this.onStompError = noOp;\n this.onWebSocketClose = noOp;\n this.onWebSocketError = noOp;\n this.logRawCommunication = false;\n this.onChangeState = noOp;\n // These parameters would typically get proper values before connect is called\n this.connectHeaders = {};\n this._disconnectHeaders = {};\n // Apply configuration\n this.configure(conf);\n }\n /**\n * Updates the client's configuration.\n *\n * All properties in the provided configuration object will override the current settings.\n *\n * Additionally, a warning is logged if `maxReconnectDelay` is configured to a\n * value lower than `reconnectDelay`, and `maxReconnectDelay` is adjusted to match `reconnectDelay`.\n *\n * Example:\n * ```javascript\n * client.configure({\n * reconnectDelay: 3000,\n * maxReconnectDelay: 10000\n * });\n * ```\n *\n * @param conf Configuration object containing the new settings.\n */\n configure(conf) {\n // bulk assign all properties to this\n Object.assign(this, conf);\n // Warn on incorrect maxReconnectDelay settings\n if (this.maxReconnectDelay > 0 &&\n this.maxReconnectDelay < this.reconnectDelay) {\n this.debug(`Warning: maxReconnectDelay (${this.maxReconnectDelay}ms) is less than reconnectDelay (${this.reconnectDelay}ms). Using reconnectDelay as the maxReconnectDelay delay.`);\n this.maxReconnectDelay = this.reconnectDelay;\n }\n }\n /**\n * Activates the client, initiating a connection to the STOMP broker.\n *\n * On activation, the client attempts to connect and sets its state to `ACTIVE`. If the connection\n * is lost, it will automatically retry based on `reconnectDelay` or `maxReconnectDelay`. If\n * `reconnectTimeMode` is set to `EXPONENTIAL`, the reconnect delay increases exponentially.\n *\n * To stop reconnection attempts and disconnect, call [Client#deactivate]{@link Client#deactivate}.\n *\n * Example:\n * ```javascript\n * client.activate(); // Connect to the broker\n * ```\n *\n * If the client is currently `DEACTIVATING`, connection is delayed until the deactivation process completes.\n */\n activate() {\n const _activate = () => {\n if (this.active) {\n this.debug('Already ACTIVE, ignoring request to activate');\n return;\n }\n this._changeState(ActivationState.ACTIVE);\n this._nextReconnectDelay = this.reconnectDelay;\n this._connect();\n };\n // if it is deactivating, wait for it to complete before activating.\n if (this.state === ActivationState.DEACTIVATING) {\n this.debug('Waiting for deactivation to finish before activating');\n this.deactivate().then(() => {\n _activate();\n });\n }\n else {\n _activate();\n }\n }\n async _connect() {\n await this.beforeConnect(this);\n if (this._stompHandler) {\n this.debug('There is already a stompHandler, skipping the call to connect');\n return;\n }\n if (!this.active) {\n this.debug('Client has been marked inactive, will not attempt to connect');\n return;\n }\n // setup connection watcher\n if (this.connectionTimeout > 0) {\n // clear first\n if (this._connectionWatcher) {\n clearTimeout(this._connectionWatcher);\n }\n this._connectionWatcher = setTimeout(() => {\n if (this.connected) {\n return;\n }\n // Connection not established, close the underlying socket\n // a reconnection will be attempted\n this.debug(`Connection not established in ${this.connectionTimeout}ms, closing socket`);\n this.forceDisconnect();\n }, this.connectionTimeout);\n }\n this.debug('Opening Web Socket...');\n // Get the actual WebSocket (or a similar object)\n const webSocket = this._createWebSocket();\n this._stompHandler = new StompHandler(this, webSocket, {\n debug: this.debug,\n stompVersions: this.stompVersions,\n connectHeaders: this.connectHeaders,\n disconnectHeaders: this._disconnectHeaders,\n heartbeatIncoming: this.heartbeatIncoming,\n heartbeatGracePeriods: this.heartbeatToleranceMultiplier,\n heartbeatOutgoing: this.heartbeatOutgoing,\n heartbeatStrategy: this.heartbeatStrategy,\n splitLargeFrames: this.splitLargeFrames,\n maxWebSocketChunkSize: this.maxWebSocketChunkSize,\n forceBinaryWSFrames: this.forceBinaryWSFrames,\n logRawCommunication: this.logRawCommunication,\n appendMissingNULLonIncoming: this.appendMissingNULLonIncoming,\n discardWebsocketOnCommFailure: this.discardWebsocketOnCommFailure,\n onConnect: frame => {\n // Successfully connected, stop the connection watcher\n if (this._connectionWatcher) {\n clearTimeout(this._connectionWatcher);\n this._connectionWatcher = undefined;\n }\n // Reset reconnect delay after successful connection\n this._nextReconnectDelay = this.reconnectDelay;\n if (!this.active) {\n this.debug('STOMP got connected while deactivate was issued, will disconnect now');\n this._disposeStompHandler();\n return;\n }\n this.onConnect(frame);\n },\n onDisconnect: frame => {\n this.onDisconnect(frame);\n },\n onStompError: frame => {\n this.onStompError(frame);\n },\n onWebSocketClose: evt => {\n this._stompHandler = undefined; // a new one will be created in case of a reconnect\n if (this.state === ActivationState.DEACTIVATING) {\n // Mark deactivation complete\n this._changeState(ActivationState.INACTIVE);\n }\n // The callback is called before attempting to reconnect, this would allow the client\n // to be `deactivated` in the callback.\n this.onWebSocketClose(evt);\n if (this.active) {\n this._schedule_reconnect();\n }\n },\n onWebSocketError: evt => {\n this.onWebSocketError(evt);\n },\n onUnhandledMessage: message => {\n this.onUnhandledMessage(message);\n },\n onUnhandledReceipt: frame => {\n this.onUnhandledReceipt(frame);\n },\n onUnhandledFrame: frame => {\n this.onUnhandledFrame(frame);\n },\n onHeartbeatReceived: () => {\n this.onHeartbeatReceived();\n },\n onHeartbeatLost: () => {\n this.onHeartbeatLost();\n },\n });\n this._stompHandler.start();\n }\n _createWebSocket() {\n let webSocket;\n if (this.webSocketFactory) {\n webSocket = this.webSocketFactory();\n }\n else if (this.brokerURL) {\n webSocket = new WebSocket(this.brokerURL, this.stompVersions.protocolVersions());\n }\n else {\n throw new Error('Either brokerURL or webSocketFactory must be provided');\n }\n webSocket.binaryType = 'arraybuffer';\n return webSocket;\n }\n _schedule_reconnect() {\n if (this._nextReconnectDelay > 0) {\n this.debug(`STOMP: scheduling reconnection in ${this._nextReconnectDelay}ms`);\n this._reconnector = setTimeout(() => {\n if (this.reconnectTimeMode === ReconnectionTimeMode.EXPONENTIAL) {\n this._nextReconnectDelay = this._nextReconnectDelay * 2;\n // Truncated exponential backoff with a set limit unless disabled\n if (this.maxReconnectDelay !== 0) {\n this._nextReconnectDelay = Math.min(this._nextReconnectDelay, this.maxReconnectDelay);\n }\n }\n this._connect();\n }, this._nextReconnectDelay);\n }\n }\n /**\n * Disconnects the client and stops the automatic reconnection loop.\n *\n * If there is an active STOMP connection at the time of invocation, the appropriate callbacks\n * will be triggered during the shutdown sequence. Once deactivated, the client will enter the\n * `INACTIVE` state, and no further reconnection attempts will be made.\n *\n * **Behavior**:\n * - If there is no active WebSocket connection, this method resolves immediately.\n * - If there is an active connection, the method waits for the underlying WebSocket\n * to properly close before resolving.\n * - Multiple calls to this method are safe. Each invocation resolves upon completion.\n * - To reactivate, call [Client#activate]{@link Client#activate}.\n *\n * **Experimental Option:**\n * - By specifying the `force: true` option, the WebSocket connection is discarded immediately,\n * bypassing both the STOMP and WebSocket shutdown sequences.\n * - **Caution:** Using `force: true` may leave the WebSocket in an inconsistent state,\n * and brokers may not immediately detect the termination.\n *\n * Example:\n * ```javascript\n * // Graceful disconnect\n * await client.deactivate();\n *\n * // Forced disconnect to speed up shutdown when the connection is stale\n * await client.deactivate({ force: true });\n * ```\n *\n * @param options Configuration options for deactivation. Use `force: true` for immediate shutdown.\n * @returns A Promise that resolves when the deactivation process completes.\n */\n async deactivate(options = {}) {\n const force = options.force || false;\n const needToDispose = this.active;\n let retPromise;\n if (this.state === ActivationState.INACTIVE) {\n this.debug(`Already INACTIVE, nothing more to do`);\n return Promise.resolve();\n }\n this._changeState(ActivationState.DEACTIVATING);\n // Clear reconnection timer just to be safe\n this._nextReconnectDelay = 0;\n // Clear if a reconnection was scheduled\n if (this._reconnector) {\n clearTimeout(this._reconnector);\n this._reconnector = undefined;\n }\n if (this._stompHandler &&\n // @ts-ignore - if there is a _stompHandler, there is the webSocket\n this.webSocket.readyState !== StompSocketState.CLOSED) {\n const origOnWebSocketClose = this._stompHandler.onWebSocketClose;\n // we need to wait for the underlying websocket to close\n retPromise = new Promise((resolve, reject) => {\n // @ts-ignore - there is a _stompHandler\n this._stompHandler.onWebSocketClose = evt => {\n origOnWebSocketClose(evt);\n resolve();\n };\n });\n }\n else {\n // indicate that auto reconnect loop should terminate\n this._changeState(ActivationState.INACTIVE);\n return Promise.resolve();\n }\n if (force) {\n this._stompHandler?.discardWebsocket();\n }\n else if (needToDispose) {\n this._disposeStompHandler();\n }\n return retPromise;\n }\n /**\n * Forces a disconnect by directly closing the WebSocket.\n *\n * Unlike a normal disconnect, this does not send a DISCONNECT sequence to the broker but\n * instead closes the WebSocket connection directly. After forcing a disconnect, the client\n * will automatically attempt to reconnect based on its `reconnectDelay` configuration.\n *\n * **Note:** To prevent further reconnect attempts, call [Client#deactivate]{@link Client#deactivate}.\n *\n * Example:\n * ```javascript\n * client.forceDisconnect();\n * ```\n */\n forceDisconnect() {\n if (this._stompHandler) {\n this._stompHandler.forceDisconnect();\n }\n }\n _disposeStompHandler() {\n // Dispose STOMP Handler\n if (this._stompHandler) {\n this._stompHandler.dispose();\n }\n }\n /**\n * Sends a message to the specified destination on the STOMP broker.\n *\n * The `body` must be a `string`. For non-string payloads (e.g., JSON), encode it as a string before sending.\n * If sending binary data, use the `binaryBody` parameter as a [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array).\n *\n * **Content-Length Behavior**:\n * - For non-binary messages, the `content-length` header is added by default.\n * - The `content-length` header can be skipped for text frames by setting `skipContentLengthHeader: true` in the parameters.\n * - For binary messages, the `content-length` header is always included.\n *\n * **Notes**:\n * - Ensure that brokers support binary frames before using `binaryBody`.\n * - Sending messages with NULL octets and missing `content-length` headers can cause brokers to disconnect and throw errors.\n *\n * Example:\n * ```javascript\n * // Basic text message\n * client.publish({ destination: \"/queue/test\", body: \"Hello, STOMP\" });\n *\n * // Text message with additional headers\n * client.publish({ destination: \"/queue/test\", headers: { priority: 9 }, body: \"Hello, STOMP\" });\n *\n * // Skip content-length header\n * client.publish({ destination: \"/queue/test\", body: \"Hello, STOMP\", skipContentLengthHeader: true });\n *\n * // Binary message\n * const binaryData = new Uint8Array([1, 2, 3, 4]);\n * client.publish({\n * destination: '/topic/special',\n * binaryBody: binaryData,\n * headers: { 'content-type': 'application/octet-stream' }\n * });\n * ```\n */\n publish(params) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.publish(params);\n }\n _checkConnection() {\n if (!this.connected) {\n throw new TypeError('There is no underlying STOMP connection');\n }\n }\n /**\n * Monitors for a receipt acknowledgment from the broker for specific operations.\n *\n * Add a `receipt` header to the operation (like subscribe or publish), and use this method with\n * the same receipt ID to detect when the broker has acknowledged the operation's completion.\n *\n * The callback is invoked with the corresponding {@link IFrame} when the receipt is received.\n *\n * Example:\n * ```javascript\n * const receiptId = \"unique-receipt-id\";\n *\n * client.watchForReceipt(receiptId, (frame) => {\n * console.log(\"Operation acknowledged by the broker:\", frame);\n * });\n *\n * // Attach the receipt header to an operation\n * client.publish({ destination: \"/queue/test\", headers: { receipt: receiptId }, body: \"Hello\" });\n * ```\n *\n * @param receiptId Unique identifier for the receipt.\n * @param callback Callback function invoked on receiving the RECEIPT frame.\n */\n watchForReceipt(receiptId, callback) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.watchForReceipt(receiptId, callback);\n }\n /**\n * Subscribes to a destination on the STOMP broker.\n *\n * The callback is triggered for each message received from the subscribed destination. The message\n * is passed as an {@link IMessage} instance.\n *\n * **Subscription ID**:\n * - If no `id` is provided in `headers`, the library generates a unique subscription ID automatically.\n * - Provide an explicit `id` in `headers` if you wish to manage the subscription ID manually.\n *\n * Example:\n * ```javascript\n * const callback = (message) => {\n * console.log(\"Received message:\", message.body);\n * };\n *\n * // Auto-generated subscription ID\n * const subscription = client.subscribe(\"/queue/test\", callback);\n *\n * // Explicit subscription ID\n * const mySubId = \"my-subscription-id\";\n * const subscription = client.subscribe(\"/queue/test\", callback, { id: mySubId });\n * ```\n *\n * @param destination Destination to subscribe to.\n * @param callback Function invoked for each received message.\n * @param headers Optional headers for subscription, such as `id`.\n * @returns A {@link StompSubscription} which can be used to manage the subscription.\n */\n subscribe(destination, callback, headers = {}) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n return this._stompHandler.subscribe(destination, callback, headers);\n }\n /**\n * Unsubscribes from a subscription on the STOMP broker.\n *\n * Prefer using the `unsubscribe` method directly on the {@link StompSubscription} returned from `subscribe` for cleaner management:\n * ```javascript\n * const subscription = client.subscribe(\"/queue/test\", callback);\n * // Unsubscribe using the subscription object\n * subscription.unsubscribe();\n * ```\n *\n * This method can also be used directly with the subscription ID.\n *\n * Example:\n * ```javascript\n * client.unsubscribe(\"my-subscription-id\");\n * ```\n *\n * @param id Subscription ID to unsubscribe.\n * @param headers Optional headers to pass for the UNSUBSCRIBE frame.\n */\n unsubscribe(id, headers = {}) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.unsubscribe(id, headers);\n }\n /**\n * Starts a new transaction. The returned {@link ITransaction} object provides\n * methods for [commit]{@link ITransaction#commit} and [abort]{@link ITransaction#abort}.\n *\n * If `transactionId` is not provided, the library generates a unique ID internally.\n *\n * Example:\n * ```javascript\n * const tx = client.begin(); // Auto-generated ID\n *\n * // Or explicitly specify a transaction ID\n * const tx = client.begin(\"my-transaction-id\");\n * ```\n *\n * @param transactionId Optional transaction ID.\n * @returns An instance of {@link ITransaction}.\n */\n begin(transactionId) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n return this._stompHandler.begin(transactionId);\n }\n /**\n * Commits a transaction.\n *\n * It is strongly recommended to call [commit]{@link ITransaction#commit} on\n * the transaction object returned by [client#begin]{@link Client#begin}.\n *\n * Example:\n * ```javascript\n * const tx = client.begin();\n * // Perform operations under this transaction\n * tx.commit();\n * ```\n *\n * @param transactionId The ID of the transaction to commit.\n */\n commit(transactionId) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.commit(transactionId);\n }\n /**\n * Aborts a transaction.\n *\n * It is strongly recommended to call [abort]{@link ITransaction#abort} directly\n * on the transaction object returned by [client#begin]{@link Client#begin}.\n *\n * Example:\n * ```javascript\n * const tx = client.begin();\n * // Perform operations under this transaction\n * tx.abort(); // Abort the transaction\n * ```\n *\n * @param transactionId The ID of the transaction to abort.\n */\n abort(transactionId) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.abort(transactionId);\n }\n /**\n * Acknowledges receipt of a message. Typically, this should be done by calling\n * [ack]{@link IMessage#ack} directly on the {@link IMessage} instance passed\n * to the subscription callback.\n *\n * Example:\n * ```javascript\n * const callback = (message) => {\n * // Process the message\n * message.ack(); // Acknowledge the message\n * };\n *\n * client.subscribe(\"/queue/example\", callback, { ack: \"client\" });\n * ```\n *\n * @param messageId The ID of the message to acknowledge.\n * @param subscriptionId The ID of the subscription.\n * @param headers Optional headers for the acknowledgment frame.\n */\n ack(messageId, subscriptionId, headers = {}) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.ack(messageId, subscriptionId, headers);\n }\n /**\n * Rejects a message (negative acknowledgment). Like acknowledgments, this should\n * typically be done by calling [nack]{@link IMessage#nack} directly on the {@link IMessage}\n * instance passed to the subscription callback.\n *\n * Example:\n * ```javascript\n * const callback = (message) => {\n * // Process the message\n * if (isError(message)) {\n * message.nack(); // Reject the message\n * }\n * };\n *\n * client.subscribe(\"/queue/example\", callback, { ack: \"client\" });\n * ```\n *\n * @param messageId The ID of the message to negatively acknowledge.\n * @param subscriptionId The ID of the subscription.\n * @param headers Optional headers for the NACK frame.\n */\n nack(messageId, subscriptionId, headers = {}) {\n this._checkConnection();\n // @ts-ignore - we already checked that there is a _stompHandler, and it is connected\n this._stompHandler.nack(messageId, subscriptionId, headers);\n }\n}\n//# sourceMappingURL=client.js.map","'use strict';\n\nvar crypto = require('crypto');\n\n// This string has length 32, a power of 2, so the modulus doesn't introduce a\n// bias.\nvar _randomStringChars = 'abcdefghijklmnopqrstuvwxyz012345';\nmodule.exports = {\n string: function(length) {\n var max = _randomStringChars.length;\n var bytes = crypto.randomBytes(length);\n var ret = [];\n for (var i = 0; i < length; i++) {\n ret.push(_randomStringChars.substr(bytes[i] % max, 1));\n }\n return ret.join('');\n }\n\n, number: function(max) {\n return Math.floor(Math.random() * max);\n }\n\n, numberString: function(max) {\n var t = ('' + (max - 1)).length;\n var p = new Array(t + 1).join('0');\n return (p + this.number(max)).slice(-t);\n }\n};\n","'use strict';\n\nif (global.crypto && global.crypto.getRandomValues) {\n module.exports.randomBytes = function(length) {\n var bytes = new Uint8Array(length);\n global.crypto.getRandomValues(bytes);\n return bytes;\n };\n} else {\n module.exports.randomBytes = function(length) {\n var bytes = new Array(length);\n for (var i = 0; i < length; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n return bytes;\n };\n}\n","'use strict';\n\nvar random = require('./random');\n\nvar onUnload = {}\n , afterUnload = false\n // detect google chrome packaged apps because they don't allow the 'unload' event\n , isChromePackagedApp = global.chrome && global.chrome.app && global.chrome.app.runtime\n ;\n\nmodule.exports = {\n attachEvent: function(event, listener) {\n if (typeof global.addEventListener !== 'undefined') {\n global.addEventListener(event, listener, false);\n } else if (global.document && global.attachEvent) {\n // IE quirks.\n // According to: http://stevesouders.com/misc/test-postmessage.php\n // the message gets delivered only to 'document', not 'window'.\n global.document.attachEvent('on' + event, listener);\n // I get 'window' for ie8.\n global.attachEvent('on' + event, listener);\n }\n }\n\n, detachEvent: function(event, listener) {\n if (typeof global.addEventListener !== 'undefined') {\n global.removeEventListener(event, listener, false);\n } else if (global.document && global.detachEvent) {\n global.document.detachEvent('on' + event, listener);\n global.detachEvent('on' + event, listener);\n }\n }\n\n, unloadAdd: function(listener) {\n if (isChromePackagedApp) {\n return null;\n }\n\n var ref = random.string(8);\n onUnload[ref] = listener;\n if (afterUnload) {\n setTimeout(this.triggerUnloadCallbacks, 0);\n }\n return ref;\n }\n\n, unloadDel: function(ref) {\n if (ref in onUnload) {\n delete onUnload[ref];\n }\n }\n\n, triggerUnloadCallbacks: function() {\n for (var ref in onUnload) {\n onUnload[ref]();\n delete onUnload[ref];\n }\n }\n};\n\nvar unloadTriggered = function() {\n if (afterUnload) {\n return;\n }\n afterUnload = true;\n module.exports.triggerUnloadCallbacks();\n};\n\n// 'unload' alone is not reliable in opera within an iframe, but we\n// can't use `beforeunload` as IE fires it on javascript: links.\nif (!isChromePackagedApp) {\n module.exports.attachEvent('unload', unloadTriggered);\n}\n","'use strict';\n\nvar required = require('requires-port')\n , qs = require('querystringify')\n , controlOrWhitespace = /^[\\x00-\\x20\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]+/\n , CRHTLF = /[\\n\\r\\t]/g\n , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\\/\\//\n , port = /:\\d+$/\n , protocolre = /^([a-z][a-z0-9.+-]*:)?(\\/\\/)?([\\\\/]+)?([\\S\\s]*)/i\n , windowsDriveLetter = /^[a-zA-Z]:/;\n\n/**\n * Remove control characters and whitespace from the beginning of a string.\n *\n * @param {Object|String} str String to trim.\n * @returns {String} A new string representing `str` stripped of control\n * characters and whitespace from its beginning.\n * @public\n */\nfunction trimLeft(str) {\n return (str ? str : '').toString().replace(controlOrWhitespace, '');\n}\n\n/**\n * These are the parse rules for the URL parser, it informs the parser\n * about:\n *\n * 0. The char it Needs to parse, if it's a string it should be done using\n * indexOf, RegExp using exec and NaN means set as current value.\n * 1. The property we should set when parsing this value.\n * 2. Indication if it's backwards or forward parsing, when set as number it's\n * the value of extra chars that should be split off.\n * 3. Inherit from location if non existing in the parser.\n * 4. `toLowerCase` the resulting value.\n */\nvar rules = [\n ['#', 'hash'], // Extract from the back.\n ['?', 'query'], // Extract from the back.\n function sanitize(address, url) { // Sanitize what is left of the address\n return isSpecial(url.protocol) ? address.replace(/\\\\/g, '/') : address;\n },\n ['/', 'pathname'], // Extract from the back.\n ['@', 'auth', 1], // Extract from the front.\n [NaN, 'host', undefined, 1, 1], // Set left over value.\n [/:(\\d*)$/, 'port', undefined, 1], // RegExp the back.\n [NaN, 'hostname', undefined, 1, 1] // Set left over.\n];\n\n/**\n * These properties should not be copied or inherited from. This is only needed\n * for all non blob URL's as a blob URL does not include a hash, only the\n * origin.\n *\n * @type {Object}\n * @private\n */\nvar ignore = { hash: 1, query: 1 };\n\n/**\n * The location object differs when your code is loaded through a normal page,\n * Worker or through a worker using a blob. And with the blobble begins the\n * trouble as the location object will contain the URL of the blob, not the\n * location of the page where our code is loaded in. The actual origin is\n * encoded in the `pathname` so we can thankfully generate a good \"default\"\n * location from it so we can generate proper relative URL's again.\n *\n * @param {Object|String} loc Optional default location object.\n * @returns {Object} lolcation object.\n * @public\n */\nfunction lolcation(loc) {\n var globalVar;\n\n if (typeof window !== 'undefined') globalVar = window;\n else if (typeof global !== 'undefined') globalVar = global;\n else if (typeof self !== 'undefined') globalVar = self;\n else globalVar = {};\n\n var location = globalVar.location || {};\n loc = loc || location;\n\n var finaldestination = {}\n , type = typeof loc\n , key;\n\n if ('blob:' === loc.protocol) {\n finaldestination = new Url(unescape(loc.pathname), {});\n } else if ('string' === type) {\n finaldestination = new Url(loc, {});\n for (key in ignore) delete finaldestination[key];\n } else if ('object' === type) {\n for (key in loc) {\n if (key in ignore) continue;\n finaldestination[key] = loc[key];\n }\n\n if (finaldestination.slashes === undefined) {\n finaldestination.slashes = slashes.test(loc.href);\n }\n }\n\n return finaldestination;\n}\n\n/**\n * Check whether a protocol scheme is special.\n *\n * @param {String} The protocol scheme of the URL\n * @return {Boolean} `true` if the protocol scheme is special, else `false`\n * @private\n */\nfunction isSpecial(scheme) {\n return (\n scheme === 'file:' ||\n scheme === 'ftp:' ||\n scheme === 'http:' ||\n scheme === 'https:' ||\n scheme === 'ws:' ||\n scheme === 'wss:'\n );\n}\n\n/**\n * @typedef ProtocolExtract\n * @type Object\n * @property {String} protocol Protocol matched in the URL, in lowercase.\n * @property {Boolean} slashes `true` if protocol is followed by \"//\", else `false`.\n * @property {String} rest Rest of the URL that is not part of the protocol.\n */\n\n/**\n * Extract protocol information from a URL with/without double slash (\"//\").\n *\n * @param {String} address URL we want to extract from.\n * @param {Object} location\n * @return {ProtocolExtract} Extracted information.\n * @private\n */\nfunction extractProtocol(address, location) {\n address = trimLeft(address);\n address = address.replace(CRHTLF, '');\n location = location || {};\n\n var match = protocolre.exec(address);\n var protocol = match[1] ? match[1].toLowerCase() : '';\n var forwardSlashes = !!match[2];\n var otherSlashes = !!match[3];\n var slashesCount = 0;\n var rest;\n\n if (forwardSlashes) {\n if (otherSlashes) {\n rest = match[2] + match[3] + match[4];\n slashesCount = match[2].length + match[3].length;\n } else {\n rest = match[2] + match[4];\n slashesCount = match[2].length;\n }\n } else {\n if (otherSlashes) {\n rest = match[3] + match[4];\n slashesCount = match[3].length;\n } else {\n rest = match[4]\n }\n }\n\n if (protocol === 'file:') {\n if (slashesCount >= 2) {\n rest = rest.slice(2);\n }\n } else if (isSpecial(protocol)) {\n rest = match[4];\n } else if (protocol) {\n if (forwardSlashes) {\n rest = rest.slice(2);\n }\n } else if (slashesCount >= 2 && isSpecial(location.protocol)) {\n rest = match[4];\n }\n\n return {\n protocol: protocol,\n slashes: forwardSlashes || isSpecial(protocol),\n slashesCount: slashesCount,\n rest: rest\n };\n}\n\n/**\n * Resolve a relative URL pathname against a base URL pathname.\n *\n * @param {String} relative Pathname of the relative URL.\n * @param {String} base Pathname of the base URL.\n * @return {String} Resolved pathname.\n * @private\n */\nfunction resolve(relative, base) {\n if (relative === '') return base;\n\n var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/'))\n , i = path.length\n , last = path[i - 1]\n , unshift = false\n , up = 0;\n\n while (i--) {\n if (path[i] === '.') {\n path.splice(i, 1);\n } else if (path[i] === '..') {\n path.splice(i, 1);\n up++;\n } else if (up) {\n if (i === 0) unshift = true;\n path.splice(i, 1);\n up--;\n }\n }\n\n if (unshift) path.unshift('');\n if (last === '.' || last === '..') path.push('');\n\n return path.join('/');\n}\n\n/**\n * The actual URL instance. Instead of returning an object we've opted-in to\n * create an actual constructor as it's much more memory efficient and\n * faster and it pleases my OCD.\n *\n * It is worth noting that we should not use `URL` as class name to prevent\n * clashes with the global URL instance that got introduced in browsers.\n *\n * @constructor\n * @param {String} address URL we want to parse.\n * @param {Object|String} [location] Location defaults for relative paths.\n * @param {Boolean|Function} [parser] Parser for the query string.\n * @private\n */\nfunction Url(address, location, parser) {\n address = trimLeft(address);\n address = address.replace(CRHTLF, '');\n\n if (!(this instanceof Url)) {\n return new Url(address, location, parser);\n }\n\n var relative, extracted, parse, instruction, index, key\n , instructions = rules.slice()\n , type = typeof location\n , url = this\n , i = 0;\n\n //\n // The following if statements allows this module two have compatibility with\n // 2 different API:\n //\n // 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments\n // where the boolean indicates that the query string should also be parsed.\n //\n // 2. The `URL` interface of the browser which accepts a URL, object as\n // arguments. The supplied object will be used as default values / fall-back\n // for relative paths.\n //\n if ('object' !== type && 'string' !== type) {\n parser = location;\n location = null;\n }\n\n if (parser && 'function' !== typeof parser) parser = qs.parse;\n\n location = lolcation(location);\n\n //\n // Extract protocol information before running the instructions.\n //\n extracted = extractProtocol(address || '', location);\n relative = !extracted.protocol && !extracted.slashes;\n url.slashes = extracted.slashes || relative && location.slashes;\n url.protocol = extracted.protocol || location.protocol || '';\n address = extracted.rest;\n\n //\n // When the authority component is absent the URL starts with a path\n // component.\n //\n if (\n extracted.protocol === 'file:' && (\n extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) ||\n (!extracted.slashes &&\n (extracted.protocol ||\n extracted.slashesCount < 2 ||\n !isSpecial(url.protocol)))\n ) {\n instructions[3] = [/(.*)/, 'pathname'];\n }\n\n for (; i < instructions.length; i++) {\n instruction = instructions[i];\n\n if (typeof instruction === 'function') {\n address = instruction(address, url);\n continue;\n }\n\n parse = instruction[0];\n key = instruction[1];\n\n if (parse !== parse) {\n url[key] = address;\n } else if ('string' === typeof parse) {\n index = parse === '@'\n ? address.lastIndexOf(parse)\n : address.indexOf(parse);\n\n if (~index) {\n if ('number' === typeof instruction[2]) {\n url[key] = address.slice(0, index);\n address = address.slice(index + instruction[2]);\n } else {\n url[key] = address.slice(index);\n address = address.slice(0, index);\n }\n }\n } else if ((index = parse.exec(address))) {\n url[key] = index[1];\n address = address.slice(0, index.index);\n }\n\n url[key] = url[key] || (\n relative && instruction[3] ? location[key] || '' : ''\n );\n\n //\n // Hostname, host and protocol should be lowercased so they can be used to\n // create a proper `origin`.\n //\n if (instruction[4]) url[key] = url[key].toLowerCase();\n }\n\n //\n // Also parse the supplied query string in to an object. If we're supplied\n // with a custom parser as function use that instead of the default build-in\n // parser.\n //\n if (parser) url.query = parser(url.query);\n\n //\n // If the URL is relative, resolve the pathname against the base URL.\n //\n if (\n relative\n && location.slashes\n && url.pathname.charAt(0) !== '/'\n && (url.pathname !== '' || location.pathname !== '')\n ) {\n url.pathname = resolve(url.pathname, location.pathname);\n }\n\n //\n // Default to a / for pathname if none exists. This normalizes the URL\n // to always have a /\n //\n if (url.pathname.charAt(0) !== '/' && isSpecial(url.protocol)) {\n url.pathname = '/' + url.pathname;\n }\n\n //\n // We should not add port numbers if they are already the default port number\n // for a given protocol. As the host also contains the port number we're going\n // override it with the hostname which contains no port number.\n //\n if (!required(url.port, url.protocol)) {\n url.host = url.hostname;\n url.port = '';\n }\n\n //\n // Parse down the `auth` for the username and password.\n //\n url.username = url.password = '';\n\n if (url.auth) {\n index = url.auth.indexOf(':');\n\n if (~index) {\n url.username = url.auth.slice(0, index);\n url.username = encodeURIComponent(decodeURIComponent(url.username));\n\n url.password = url.auth.slice(index + 1);\n url.password = encodeURIComponent(decodeURIComponent(url.password))\n } else {\n url.username = encodeURIComponent(decodeURIComponent(url.auth));\n }\n\n url.auth = url.password ? url.username +':'+ url.password : url.username;\n }\n\n url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host\n ? url.protocol +'//'+ url.host\n : 'null';\n\n //\n // The href is just the compiled result.\n //\n url.href = url.toString();\n}\n\n/**\n * This is convenience method for changing properties in the URL instance to\n * insure that they all propagate correctly.\n *\n * @param {String} part Property we need to adjust.\n * @param {Mixed} value The newly assigned value.\n * @param {Boolean|Function} fn When setting the query, it will be the function\n * used to parse the query.\n * When setting the protocol, double slash will be\n * removed from the final url if it is true.\n * @returns {URL} URL instance for chaining.\n * @public\n */\nfunction set(part, value, fn) {\n var url = this;\n\n switch (part) {\n case 'query':\n if ('string' === typeof value && value.length) {\n value = (fn || qs.parse)(value);\n }\n\n url[part] = value;\n break;\n\n case 'port':\n url[part] = value;\n\n if (!required(value, url.protocol)) {\n url.host = url.hostname;\n url[part] = '';\n } else if (value) {\n url.host = url.hostname +':'+ value;\n }\n\n break;\n\n case 'hostname':\n url[part] = value;\n\n if (url.port) value += ':'+ url.port;\n url.host = value;\n break;\n\n case 'host':\n url[part] = value;\n\n if (port.test(value)) {\n value = value.split(':');\n url.port = value.pop();\n url.hostname = value.join(':');\n } else {\n url.hostname = value;\n url.port = '';\n }\n\n break;\n\n case 'protocol':\n url.protocol = value.toLowerCase();\n url.slashes = !fn;\n break;\n\n case 'pathname':\n case 'hash':\n if (value) {\n var char = part === 'pathname' ? '/' : '#';\n url[part] = value.charAt(0) !== char ? char + value : value;\n } else {\n url[part] = value;\n }\n break;\n\n case 'username':\n case 'password':\n url[part] = encodeURIComponent(value);\n break;\n\n case 'auth':\n var index = value.indexOf(':');\n\n if (~index) {\n url.username = value.slice(0, index);\n url.username = encodeURIComponent(decodeURIComponent(url.username));\n\n url.password = value.slice(index + 1);\n url.password = encodeURIComponent(decodeURIComponent(url.password));\n } else {\n url.username = encodeURIComponent(decodeURIComponent(value));\n }\n }\n\n for (var i = 0; i < rules.length; i++) {\n var ins = rules[i];\n\n if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase();\n }\n\n url.auth = url.password ? url.username +':'+ url.password : url.username;\n\n url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host\n ? url.protocol +'//'+ url.host\n : 'null';\n\n url.href = url.toString();\n\n return url;\n}\n\n/**\n * Transform the properties back in to a valid and full URL string.\n *\n * @param {Function} stringify Optional query stringify function.\n * @returns {String} Compiled version of the URL.\n * @public\n */\nfunction toString(stringify) {\n if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify;\n\n var query\n , url = this\n , host = url.host\n , protocol = url.protocol;\n\n if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':';\n\n var result =\n protocol +\n ((url.protocol && url.slashes) || isSpecial(url.protocol) ? '//' : '');\n\n if (url.username) {\n result += url.username;\n if (url.password) result += ':'+ url.password;\n result += '@';\n } else if (url.password) {\n result += ':'+ url.password;\n result += '@';\n } else if (\n url.protocol !== 'file:' &&\n isSpecial(url.protocol) &&\n !host &&\n url.pathname !== '/'\n ) {\n //\n // Add back the empty userinfo, otherwise the original invalid URL\n // might be transformed into a valid one with `url.pathname` as host.\n //\n result += '@';\n }\n\n //\n // Trailing colon is removed from `url.host` when it is parsed. If it still\n // ends with a colon, then add back the trailing colon that was removed. This\n // prevents an invalid URL from being transformed into a valid one.\n //\n if (host[host.length - 1] === ':' || (port.test(url.hostname) && !url.port)) {\n host += ':';\n }\n\n result += host + url.pathname;\n\n query = 'object' === typeof url.query ? stringify(url.query) : url.query;\n if (query) result += '?' !== query.charAt(0) ? '?'+ query : query;\n\n if (url.hash) result += url.hash;\n\n return result;\n}\n\nUrl.prototype = { set: set, toString: toString };\n\n//\n// Expose the URL parser and some additional properties that might be useful for\n// others or testing.\n//\nUrl.extractProtocol = extractProtocol;\nUrl.location = lolcation;\nUrl.trimLeft = trimLeft;\nUrl.qs = qs;\n\nmodule.exports = Url;\n","'use strict';\n\n/**\n * Check if we're required to add a port number.\n *\n * @see https://url.spec.whatwg.org/#default-port\n * @param {Number|String} port Port number we need to check\n * @param {String} protocol Protocol we need to check against.\n * @returns {Boolean} Is it a default port for the given protocol\n * @api private\n */\nmodule.exports = function required(port, protocol) {\n protocol = protocol.split(':')[0];\n port = +port;\n\n if (!port) return false;\n\n switch (protocol) {\n case 'http':\n case 'ws':\n return port !== 80;\n\n case 'https':\n case 'wss':\n return port !== 443;\n\n case 'ftp':\n return port !== 21;\n\n case 'gopher':\n return port !== 70;\n\n case 'file':\n return false;\n }\n\n return port !== 0;\n};\n","'use strict';\n\nvar has = Object.prototype.hasOwnProperty\n , undef;\n\n/**\n * Decode a URI encoded string.\n *\n * @param {String} input The URI encoded string.\n * @returns {String|Null} The decoded string.\n * @api private\n */\nfunction decode(input) {\n try {\n return decodeURIComponent(input.replace(/\\+/g, ' '));\n } catch (e) {\n return null;\n }\n}\n\n/**\n * Attempts to encode a given input.\n *\n * @param {String} input The string that needs to be encoded.\n * @returns {String|Null} The encoded string.\n * @api private\n */\nfunction encode(input) {\n try {\n return encodeURIComponent(input);\n } catch (e) {\n return null;\n }\n}\n\n/**\n * Simple query string parser.\n *\n * @param {String} query The query string that needs to be parsed.\n * @returns {Object}\n * @api public\n */\nfunction querystring(query) {\n var parser = /([^=?#&]+)=?([^&]*)/g\n , result = {}\n , part;\n\n while (part = parser.exec(query)) {\n var key = decode(part[1])\n , value = decode(part[2]);\n\n //\n // Prevent overriding of existing properties. This ensures that build-in\n // methods like `toString` or __proto__ are not overriden by malicious\n // querystrings.\n //\n // In the case if failed decoding, we want to omit the key/value pairs\n // from the result.\n //\n if (key === null || value === null || key in result) continue;\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Transform a query string to an object.\n *\n * @param {Object} obj Object that should be transformed.\n * @param {String} prefix Optional prefix.\n * @returns {String}\n * @api public\n */\nfunction querystringify(obj, prefix) {\n prefix = prefix || '';\n\n var pairs = []\n , value\n , key;\n\n //\n // Optionally prefix with a '?' if needed\n //\n if ('string' !== typeof prefix) prefix = '?';\n\n for (key in obj) {\n if (has.call(obj, key)) {\n value = obj[key];\n\n //\n // Edge cases where we actually want to encode the value to an empty\n // string instead of the stringified value.\n //\n if (!value && (value === null || value === undef || isNaN(value))) {\n value = '';\n }\n\n key = encode(key);\n value = encode(value);\n\n //\n // If we failed to encode the strings, we should bail out as we don't\n // want to add invalid strings to the query.\n //\n if (key === null || value === null) continue;\n pairs.push(key +'='+ value);\n }\n }\n\n return pairs.length ? prefix + pairs.join('&') : '';\n}\n\n//\n// Expose the module.\n//\nexports.stringify = querystringify;\nexports.parse = querystring;\n","'use strict';\n\nvar URL = require('url-parse');\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:utils:url');\n}\n\nmodule.exports = {\n getOrigin: function(url) {\n if (!url) {\n return null;\n }\n\n var p = new URL(url);\n if (p.protocol === 'file:') {\n return null;\n }\n\n var port = p.port;\n if (!port) {\n port = (p.protocol === 'https:') ? '443' : '80';\n }\n\n return p.protocol + '//' + p.hostname + ':' + port;\n }\n\n, isOriginEqual: function(a, b) {\n var res = this.getOrigin(a) === this.getOrigin(b);\n debug('same', a, b, res);\n return res;\n }\n\n, isSchemeEqual: function(a, b) {\n return (a.split(':')[0] === b.split(':')[0]);\n }\n\n, addPath: function (url, path) {\n var qs = url.split('?');\n return qs[0] + path + (qs[1] ? '?' + qs[1] : '');\n }\n\n, addQuery: function (url, q) {\n return url + (url.indexOf('?') === -1 ? ('?' + q) : ('&' + q));\n }\n\n, isLoopbackAddr: function (addr) {\n return /^127\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$/i.test(addr) || /^\\[::1\\]$/.test(addr);\n }\n};\n","if (typeof Object.create === 'function') {\n // implementation from standard node.js 'util' module\n module.exports = function inherits(ctor, superCtor) {\n if (superCtor) {\n ctor.super_ = superCtor\n ctor.prototype = Object.create(superCtor.prototype, {\n constructor: {\n value: ctor,\n enumerable: false,\n writable: true,\n configurable: true\n }\n })\n }\n };\n} else {\n // old school shim for old browsers\n module.exports = function inherits(ctor, superCtor) {\n if (superCtor) {\n ctor.super_ = superCtor\n var TempCtor = function () {}\n TempCtor.prototype = superCtor.prototype\n ctor.prototype = new TempCtor()\n ctor.prototype.constructor = ctor\n }\n }\n}\n","'use strict';\n\n/* Simplified implementation of DOM2 EventTarget.\n * http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget\n */\n\nfunction EventTarget() {\n this._listeners = {};\n}\n\nEventTarget.prototype.addEventListener = function(eventType, listener) {\n if (!(eventType in this._listeners)) {\n this._listeners[eventType] = [];\n }\n var arr = this._listeners[eventType];\n // #4\n if (arr.indexOf(listener) === -1) {\n // Make a copy so as not to interfere with a current dispatchEvent.\n arr = arr.concat([listener]);\n }\n this._listeners[eventType] = arr;\n};\n\nEventTarget.prototype.removeEventListener = function(eventType, listener) {\n var arr = this._listeners[eventType];\n if (!arr) {\n return;\n }\n var idx = arr.indexOf(listener);\n if (idx !== -1) {\n if (arr.length > 1) {\n // Make a copy so as not to interfere with a current dispatchEvent.\n this._listeners[eventType] = arr.slice(0, idx).concat(arr.slice(idx + 1));\n } else {\n delete this._listeners[eventType];\n }\n return;\n }\n};\n\nEventTarget.prototype.dispatchEvent = function() {\n var event = arguments[0];\n var t = event.type;\n // equivalent of Array.prototype.slice.call(arguments, 0);\n var args = arguments.length === 1 ? [event] : Array.apply(null, arguments);\n // TODO: This doesn't match the real behavior; per spec, onfoo get\n // their place in line from the /first/ time they're set from\n // non-null. Although WebKit bumps it to the end every time it's\n // set.\n if (this['on' + t]) {\n this['on' + t].apply(this, args);\n }\n if (t in this._listeners) {\n // Grab a reference to the listeners list. removeEventListener may alter the list.\n var listeners = this._listeners[t];\n for (var i = 0; i < listeners.length; i++) {\n listeners[i].apply(this, args);\n }\n }\n};\n\nmodule.exports = EventTarget;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventTarget = require('./eventtarget')\n ;\n\nfunction EventEmitter() {\n EventTarget.call(this);\n}\n\ninherits(EventEmitter, EventTarget);\n\nEventEmitter.prototype.removeAllListeners = function(type) {\n if (type) {\n delete this._listeners[type];\n } else {\n this._listeners = {};\n }\n};\n\nEventEmitter.prototype.once = function(type, listener) {\n var self = this\n , fired = false;\n\n function g() {\n self.removeListener(type, g);\n\n if (!fired) {\n fired = true;\n listener.apply(this, arguments);\n }\n }\n\n this.on(type, g);\n};\n\nEventEmitter.prototype.emit = function() {\n var type = arguments[0];\n var listeners = this._listeners[type];\n if (!listeners) {\n return;\n }\n // equivalent of Array.prototype.slice.call(arguments, 1);\n var l = arguments.length;\n var args = new Array(l - 1);\n for (var ai = 1; ai < l; ai++) {\n args[ai - 1] = arguments[ai];\n }\n for (var i = 0; i < listeners.length; i++) {\n listeners[i].apply(this, args);\n }\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener = EventTarget.prototype.addEventListener;\nEventEmitter.prototype.removeListener = EventTarget.prototype.removeEventListener;\n\nmodule.exports.EventEmitter = EventEmitter;\n","'use strict';\n\nvar utils = require('../utils/event')\n , urlUtils = require('../utils/url')\n , inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n , WebsocketDriver = require('./driver/websocket')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:websocket');\n}\n\nfunction WebSocketTransport(transUrl, ignore, options) {\n if (!WebSocketTransport.enabled()) {\n throw new Error('Transport created when disabled');\n }\n\n EventEmitter.call(this);\n debug('constructor', transUrl);\n\n var self = this;\n var url = urlUtils.addPath(transUrl, '/websocket');\n if (url.slice(0, 5) === 'https') {\n url = 'wss' + url.slice(5);\n } else {\n url = 'ws' + url.slice(4);\n }\n this.url = url;\n\n this.ws = new WebsocketDriver(this.url, [], options);\n this.ws.onmessage = function(e) {\n debug('message event', e.data);\n self.emit('message', e.data);\n };\n // Firefox has an interesting bug. If a websocket connection is\n // created after onunload, it stays alive even when user\n // navigates away from the page. In such situation let's lie -\n // let's not open the ws connection at all. See:\n // https://github.com/sockjs/sockjs-client/issues/28\n // https://bugzilla.mozilla.org/show_bug.cgi?id=696085\n this.unloadRef = utils.unloadAdd(function() {\n debug('unload');\n self.ws.close();\n });\n this.ws.onclose = function(e) {\n debug('close event', e.code, e.reason);\n self.emit('close', e.code, e.reason);\n self._cleanup();\n };\n this.ws.onerror = function(e) {\n debug('error event', e);\n self.emit('close', 1006, 'WebSocket connection broken');\n self._cleanup();\n };\n}\n\ninherits(WebSocketTransport, EventEmitter);\n\nWebSocketTransport.prototype.send = function(data) {\n var msg = '[' + data + ']';\n debug('send', msg);\n this.ws.send(msg);\n};\n\nWebSocketTransport.prototype.close = function() {\n debug('close');\n var ws = this.ws;\n this._cleanup();\n if (ws) {\n ws.close();\n }\n};\n\nWebSocketTransport.prototype._cleanup = function() {\n debug('_cleanup');\n var ws = this.ws;\n if (ws) {\n ws.onmessage = ws.onclose = ws.onerror = null;\n }\n utils.unloadDel(this.unloadRef);\n this.unloadRef = this.ws = null;\n this.removeAllListeners();\n};\n\nWebSocketTransport.enabled = function() {\n debug('enabled');\n return !!WebsocketDriver;\n};\nWebSocketTransport.transportName = 'websocket';\n\n// In theory, ws should require 1 round trip. But in chrome, this is\n// not very stable over SSL. Most likely a ws connection requires a\n// separate SSL connection, in which case 2 round trips are an\n// absolute minumum.\nWebSocketTransport.roundTrips = 2;\n\nmodule.exports = WebSocketTransport;\n","'use strict';\n\nvar Driver = global.WebSocket || global.MozWebSocket;\nif (Driver) {\n\tmodule.exports = function WebSocketBrowserDriver(url) {\n\t\treturn new Driver(url);\n\t};\n} else {\n\tmodule.exports = undefined;\n}\n","'use strict';\n\nvar inherits = require('inherits')\n , urlUtils = require('../../utils/url')\n , BufferedSender = require('./buffered-sender')\n , Polling = require('./polling')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:sender-receiver');\n}\n\nfunction SenderReceiver(transUrl, urlSuffix, senderFunc, Receiver, AjaxObject) {\n var pollUrl = urlUtils.addPath(transUrl, urlSuffix);\n debug(pollUrl);\n var self = this;\n BufferedSender.call(this, transUrl, senderFunc);\n\n this.poll = new Polling(Receiver, pollUrl, AjaxObject);\n this.poll.on('message', function(msg) {\n debug('poll message', msg);\n self.emit('message', msg);\n });\n this.poll.once('close', function(code, reason) {\n debug('poll close', code, reason);\n self.poll = null;\n self.emit('close', code, reason);\n self.close();\n });\n}\n\ninherits(SenderReceiver, BufferedSender);\n\nSenderReceiver.prototype.close = function() {\n BufferedSender.prototype.close.call(this);\n debug('close');\n this.removeAllListeners();\n if (this.poll) {\n this.poll.abort();\n this.poll = null;\n }\n};\n\nmodule.exports = SenderReceiver;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:buffered-sender');\n}\n\nfunction BufferedSender(url, sender) {\n debug(url);\n EventEmitter.call(this);\n this.sendBuffer = [];\n this.sender = sender;\n this.url = url;\n}\n\ninherits(BufferedSender, EventEmitter);\n\nBufferedSender.prototype.send = function(message) {\n debug('send', message);\n this.sendBuffer.push(message);\n if (!this.sendStop) {\n this.sendSchedule();\n }\n};\n\n// For polling transports in a situation when in the message callback,\n// new message is being send. If the sending connection was started\n// before receiving one, it is possible to saturate the network and\n// timeout due to the lack of receiving socket. To avoid that we delay\n// sending messages by some small time, in order to let receiving\n// connection be started beforehand. This is only a halfmeasure and\n// does not fix the big problem, but it does make the tests go more\n// stable on slow networks.\nBufferedSender.prototype.sendScheduleWait = function() {\n debug('sendScheduleWait');\n var self = this;\n var tref;\n this.sendStop = function() {\n debug('sendStop');\n self.sendStop = null;\n clearTimeout(tref);\n };\n tref = setTimeout(function() {\n debug('timeout');\n self.sendStop = null;\n self.sendSchedule();\n }, 25);\n};\n\nBufferedSender.prototype.sendSchedule = function() {\n debug('sendSchedule', this.sendBuffer.length);\n var self = this;\n if (this.sendBuffer.length > 0) {\n var payload = '[' + this.sendBuffer.join(',') + ']';\n this.sendStop = this.sender(this.url, payload, function(err) {\n self.sendStop = null;\n if (err) {\n debug('error', err);\n self.emit('close', err.code || 1006, 'Sending error: ' + err);\n self.close();\n } else {\n self.sendScheduleWait();\n }\n });\n this.sendBuffer = [];\n }\n};\n\nBufferedSender.prototype._cleanup = function() {\n debug('_cleanup');\n this.removeAllListeners();\n};\n\nBufferedSender.prototype.close = function() {\n debug('close');\n this._cleanup();\n if (this.sendStop) {\n this.sendStop();\n this.sendStop = null;\n }\n};\n\nmodule.exports = BufferedSender;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:polling');\n}\n\nfunction Polling(Receiver, receiveUrl, AjaxObject) {\n debug(receiveUrl);\n EventEmitter.call(this);\n this.Receiver = Receiver;\n this.receiveUrl = receiveUrl;\n this.AjaxObject = AjaxObject;\n this._scheduleReceiver();\n}\n\ninherits(Polling, EventEmitter);\n\nPolling.prototype._scheduleReceiver = function() {\n debug('_scheduleReceiver');\n var self = this;\n var poll = this.poll = new this.Receiver(this.receiveUrl, this.AjaxObject);\n\n poll.on('message', function(msg) {\n debug('message', msg);\n self.emit('message', msg);\n });\n\n poll.once('close', function(code, reason) {\n debug('close', code, reason, self.pollIsClosing);\n self.poll = poll = null;\n\n if (!self.pollIsClosing) {\n if (reason === 'network') {\n self._scheduleReceiver();\n } else {\n self.emit('close', code || 1006, reason);\n self.removeAllListeners();\n }\n }\n });\n};\n\nPolling.prototype.abort = function() {\n debug('abort');\n this.removeAllListeners();\n this.pollIsClosing = true;\n if (this.poll) {\n this.poll.abort();\n }\n};\n\nmodule.exports = Polling;\n","'use strict';\n\nvar inherits = require('inherits')\n , urlUtils = require('../../utils/url')\n , SenderReceiver = require('./sender-receiver')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:ajax-based');\n}\n\nfunction createAjaxSender(AjaxObject) {\n return function(url, payload, callback) {\n debug('create ajax sender', url, payload);\n var opt = {};\n if (typeof payload === 'string') {\n opt.headers = {'Content-type': 'text/plain'};\n }\n var ajaxUrl = urlUtils.addPath(url, '/xhr_send');\n var xo = new AjaxObject('POST', ajaxUrl, payload, opt);\n xo.once('finish', function(status) {\n debug('finish', status);\n xo = null;\n\n if (status !== 200 && status !== 204) {\n return callback(new Error('http status ' + status));\n }\n callback();\n });\n return function() {\n debug('abort');\n xo.close();\n xo = null;\n\n var err = new Error('Aborted');\n err.code = 1000;\n callback(err);\n };\n };\n}\n\nfunction AjaxBasedTransport(transUrl, urlSuffix, Receiver, AjaxObject) {\n SenderReceiver.call(this, transUrl, urlSuffix, createAjaxSender(AjaxObject), Receiver, AjaxObject);\n}\n\ninherits(AjaxBasedTransport, SenderReceiver);\n\nmodule.exports = AjaxBasedTransport;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:receiver:xhr');\n}\n\nfunction XhrReceiver(url, AjaxObject) {\n debug(url);\n EventEmitter.call(this);\n var self = this;\n\n this.bufferPosition = 0;\n\n this.xo = new AjaxObject('POST', url, null);\n this.xo.on('chunk', this._chunkHandler.bind(this));\n this.xo.once('finish', function(status, text) {\n debug('finish', status, text);\n self._chunkHandler(status, text);\n self.xo = null;\n var reason = status === 200 ? 'network' : 'permanent';\n debug('close', reason);\n self.emit('close', null, reason);\n self._cleanup();\n });\n}\n\ninherits(XhrReceiver, EventEmitter);\n\nXhrReceiver.prototype._chunkHandler = function(status, text) {\n debug('_chunkHandler', status);\n if (status !== 200 || !text) {\n return;\n }\n\n for (var idx = -1; ; this.bufferPosition += idx + 1) {\n var buf = text.slice(this.bufferPosition);\n idx = buf.indexOf('\\n');\n if (idx === -1) {\n break;\n }\n var msg = buf.slice(0, idx);\n if (msg) {\n debug('message', msg);\n this.emit('message', msg);\n }\n }\n};\n\nXhrReceiver.prototype._cleanup = function() {\n debug('_cleanup');\n this.removeAllListeners();\n};\n\nXhrReceiver.prototype.abort = function() {\n debug('abort');\n if (this.xo) {\n this.xo.close();\n debug('close');\n this.emit('close', null, 'user');\n this.xo = null;\n }\n this._cleanup();\n};\n\nmodule.exports = XhrReceiver;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , utils = require('../../utils/event')\n , urlUtils = require('../../utils/url')\n , XHR = global.XMLHttpRequest\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:browser:xhr');\n}\n\nfunction AbstractXHRObject(method, url, payload, opts) {\n debug(method, url);\n var self = this;\n EventEmitter.call(this);\n\n setTimeout(function () {\n self._start(method, url, payload, opts);\n }, 0);\n}\n\ninherits(AbstractXHRObject, EventEmitter);\n\nAbstractXHRObject.prototype._start = function(method, url, payload, opts) {\n var self = this;\n\n try {\n this.xhr = new XHR();\n } catch (x) {\n // intentionally empty\n }\n\n if (!this.xhr) {\n debug('no xhr');\n this.emit('finish', 0, 'no xhr support');\n this._cleanup();\n return;\n }\n\n // several browsers cache POSTs\n url = urlUtils.addQuery(url, 't=' + (+new Date()));\n\n // Explorer tends to keep connection open, even after the\n // tab gets closed: http://bugs.jquery.com/ticket/5280\n this.unloadRef = utils.unloadAdd(function() {\n debug('unload cleanup');\n self._cleanup(true);\n });\n try {\n this.xhr.open(method, url, true);\n if (this.timeout && 'timeout' in this.xhr) {\n this.xhr.timeout = this.timeout;\n this.xhr.ontimeout = function() {\n debug('xhr timeout');\n self.emit('finish', 0, '');\n self._cleanup(false);\n };\n }\n } catch (e) {\n debug('exception', e);\n // IE raises an exception on wrong port.\n this.emit('finish', 0, '');\n this._cleanup(false);\n return;\n }\n\n if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) {\n debug('withCredentials');\n // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest :\n // \"This never affects same-site requests.\"\n\n this.xhr.withCredentials = true;\n }\n if (opts && opts.headers) {\n for (var key in opts.headers) {\n this.xhr.setRequestHeader(key, opts.headers[key]);\n }\n }\n\n this.xhr.onreadystatechange = function() {\n if (self.xhr) {\n var x = self.xhr;\n var text, status;\n debug('readyState', x.readyState);\n switch (x.readyState) {\n case 3:\n // IE doesn't like peeking into responseText or status\n // on Microsoft.XMLHTTP and readystate=3\n try {\n status = x.status;\n text = x.responseText;\n } catch (e) {\n // intentionally empty\n }\n debug('status', status);\n // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450\n if (status === 1223) {\n status = 204;\n }\n\n // IE does return readystate == 3 for 404 answers.\n if (status === 200 && text && text.length > 0) {\n debug('chunk');\n self.emit('chunk', status, text);\n }\n break;\n case 4:\n status = x.status;\n debug('status', status);\n // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450\n if (status === 1223) {\n status = 204;\n }\n // IE returns this for a bad port\n // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383770(v=vs.85).aspx\n if (status === 12005 || status === 12029) {\n status = 0;\n }\n\n debug('finish', status, x.responseText);\n self.emit('finish', status, x.responseText);\n self._cleanup(false);\n break;\n }\n }\n };\n\n try {\n self.xhr.send(payload);\n } catch (e) {\n self.emit('finish', 0, '');\n self._cleanup(false);\n }\n};\n\nAbstractXHRObject.prototype._cleanup = function(abort) {\n debug('cleanup');\n if (!this.xhr) {\n return;\n }\n this.removeAllListeners();\n utils.unloadDel(this.unloadRef);\n\n // IE needs this field to be a function\n this.xhr.onreadystatechange = function() {};\n if (this.xhr.ontimeout) {\n this.xhr.ontimeout = null;\n }\n\n if (abort) {\n try {\n this.xhr.abort();\n } catch (x) {\n // intentionally empty\n }\n }\n this.unloadRef = this.xhr = null;\n};\n\nAbstractXHRObject.prototype.close = function() {\n debug('close');\n this._cleanup(true);\n};\n\nAbstractXHRObject.enabled = !!XHR;\n// override XMLHttpRequest for IE6/7\n// obfuscate to avoid firewalls\nvar axo = ['Active'].concat('Object').join('X');\nif (!AbstractXHRObject.enabled && (axo in global)) {\n debug('overriding xmlhttprequest');\n XHR = function() {\n try {\n return new global[axo]('Microsoft.XMLHTTP');\n } catch (e) {\n return null;\n }\n };\n AbstractXHRObject.enabled = !!new XHR();\n}\n\nvar cors = false;\ntry {\n cors = 'withCredentials' in new XHR();\n} catch (ignored) {\n // intentionally empty\n}\n\nAbstractXHRObject.supportsCORS = cors;\n\nmodule.exports = AbstractXHRObject;\n","'use strict';\n\nvar inherits = require('inherits')\n , XhrDriver = require('../driver/xhr')\n ;\n\nfunction XHRCorsObject(method, url, payload, opts) {\n XhrDriver.call(this, method, url, payload, opts);\n}\n\ninherits(XHRCorsObject, XhrDriver);\n\nXHRCorsObject.enabled = XhrDriver.enabled && XhrDriver.supportsCORS;\n\nmodule.exports = XHRCorsObject;\n","'use strict';\n\nvar inherits = require('inherits')\n , XhrDriver = require('../driver/xhr')\n ;\n\nfunction XHRLocalObject(method, url, payload /*, opts */) {\n XhrDriver.call(this, method, url, payload, {\n noCredentials: true\n });\n}\n\ninherits(XHRLocalObject, XhrDriver);\n\nXHRLocalObject.enabled = XhrDriver.enabled;\n\nmodule.exports = XHRLocalObject;\n","'use strict';\n\nmodule.exports = {\n isOpera: function() {\n return global.navigator &&\n /opera/i.test(global.navigator.userAgent);\n }\n\n, isKonqueror: function() {\n return global.navigator &&\n /konqueror/i.test(global.navigator.userAgent);\n }\n\n // #187 wrap document.domain in try/catch because of WP8 from file:///\n, hasDomain: function () {\n // non-browser client always has a domain\n if (!global.document) {\n return true;\n }\n\n try {\n return !!global.document.domain;\n } catch (e) {\n return false;\n }\n }\n};\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , XhrReceiver = require('./receiver/xhr')\n , XHRCorsObject = require('./sender/xhr-cors')\n , XHRLocalObject = require('./sender/xhr-local')\n , browser = require('../utils/browser')\n ;\n\nfunction XhrStreamingTransport(transUrl) {\n if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XHRCorsObject);\n}\n\ninherits(XhrStreamingTransport, AjaxBasedTransport);\n\nXhrStreamingTransport.enabled = function(info) {\n if (info.nullOrigin) {\n return false;\n }\n // Opera doesn't support xhr-streaming #60\n // But it might be able to #92\n if (browser.isOpera()) {\n return false;\n }\n\n return XHRCorsObject.enabled;\n};\n\nXhrStreamingTransport.transportName = 'xhr-streaming';\nXhrStreamingTransport.roundTrips = 2; // preflight, ajax\n\n// Safari gets confused when a streaming ajax request is started\n// before onload. This causes the load indicator to spin indefinetely.\n// Only require body when used in a browser\nXhrStreamingTransport.needBody = !!global.document;\n\nmodule.exports = XhrStreamingTransport;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , eventUtils = require('../../utils/event')\n , browser = require('../../utils/browser')\n , urlUtils = require('../../utils/url')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:sender:xdr');\n}\n\n// References:\n// http://ajaxian.com/archives/100-line-ajax-wrapper\n// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx\n\nfunction XDRObject(method, url, payload) {\n debug(method, url);\n var self = this;\n EventEmitter.call(this);\n\n setTimeout(function() {\n self._start(method, url, payload);\n }, 0);\n}\n\ninherits(XDRObject, EventEmitter);\n\nXDRObject.prototype._start = function(method, url, payload) {\n debug('_start');\n var self = this;\n var xdr = new global.XDomainRequest();\n // IE caches even POSTs\n url = urlUtils.addQuery(url, 't=' + (+new Date()));\n\n xdr.onerror = function() {\n debug('onerror');\n self._error();\n };\n xdr.ontimeout = function() {\n debug('ontimeout');\n self._error();\n };\n xdr.onprogress = function() {\n debug('progress', xdr.responseText);\n self.emit('chunk', 200, xdr.responseText);\n };\n xdr.onload = function() {\n debug('load');\n self.emit('finish', 200, xdr.responseText);\n self._cleanup(false);\n };\n this.xdr = xdr;\n this.unloadRef = eventUtils.unloadAdd(function() {\n self._cleanup(true);\n });\n try {\n // Fails with AccessDenied if port number is bogus\n this.xdr.open(method, url);\n if (this.timeout) {\n this.xdr.timeout = this.timeout;\n }\n this.xdr.send(payload);\n } catch (x) {\n this._error();\n }\n};\n\nXDRObject.prototype._error = function() {\n this.emit('finish', 0, '');\n this._cleanup(false);\n};\n\nXDRObject.prototype._cleanup = function(abort) {\n debug('cleanup', abort);\n if (!this.xdr) {\n return;\n }\n this.removeAllListeners();\n eventUtils.unloadDel(this.unloadRef);\n\n this.xdr.ontimeout = this.xdr.onerror = this.xdr.onprogress = this.xdr.onload = null;\n if (abort) {\n try {\n this.xdr.abort();\n } catch (x) {\n // intentionally empty\n }\n }\n this.unloadRef = this.xdr = null;\n};\n\nXDRObject.prototype.close = function() {\n debug('close');\n this._cleanup(true);\n};\n\n// IE 8/9 if the request target uses the same scheme - #79\nXDRObject.enabled = !!(global.XDomainRequest && browser.hasDomain());\n\nmodule.exports = XDRObject;\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , XhrReceiver = require('./receiver/xhr')\n , XDRObject = require('./sender/xdr')\n ;\n\n// According to:\n// http://stackoverflow.com/questions/1641507/detect-browser-support-for-cross-domain-xmlhttprequests\n// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/\n\nfunction XdrStreamingTransport(transUrl) {\n if (!XDRObject.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XDRObject);\n}\n\ninherits(XdrStreamingTransport, AjaxBasedTransport);\n\nXdrStreamingTransport.enabled = function(info) {\n if (info.cookie_needed || info.nullOrigin) {\n return false;\n }\n return XDRObject.enabled && info.sameScheme;\n};\n\nXdrStreamingTransport.transportName = 'xdr-streaming';\nXdrStreamingTransport.roundTrips = 2; // preflight, ajax\n\nmodule.exports = XdrStreamingTransport;\n","module.exports = global.EventSource;\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , EventSourceReceiver = require('./receiver/eventsource')\n , XHRCorsObject = require('./sender/xhr-cors')\n , EventSourceDriver = require('eventsource')\n ;\n\nfunction EventSourceTransport(transUrl) {\n if (!EventSourceTransport.enabled()) {\n throw new Error('Transport created when disabled');\n }\n\n AjaxBasedTransport.call(this, transUrl, '/eventsource', EventSourceReceiver, XHRCorsObject);\n}\n\ninherits(EventSourceTransport, AjaxBasedTransport);\n\nEventSourceTransport.enabled = function() {\n return !!EventSourceDriver;\n};\n\nEventSourceTransport.transportName = 'eventsource';\nEventSourceTransport.roundTrips = 2;\n\nmodule.exports = EventSourceTransport;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n , EventSourceDriver = require('eventsource')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:receiver:eventsource');\n}\n\nfunction EventSourceReceiver(url) {\n debug(url);\n EventEmitter.call(this);\n\n var self = this;\n var es = this.es = new EventSourceDriver(url);\n es.onmessage = function(e) {\n debug('message', e.data);\n self.emit('message', decodeURI(e.data));\n };\n es.onerror = function(e) {\n debug('error', es.readyState, e);\n // ES on reconnection has readyState = 0 or 1.\n // on network error it's CLOSED = 2\n var reason = (es.readyState !== 2 ? 'network' : 'permanent');\n self._cleanup();\n self._close(reason);\n };\n}\n\ninherits(EventSourceReceiver, EventEmitter);\n\nEventSourceReceiver.prototype.abort = function() {\n debug('abort');\n this._cleanup();\n this._close('user');\n};\n\nEventSourceReceiver.prototype._cleanup = function() {\n debug('cleanup');\n var es = this.es;\n if (es) {\n es.onmessage = es.onerror = null;\n es.close();\n this.es = null;\n }\n};\n\nEventSourceReceiver.prototype._close = function(reason) {\n debug('close', reason);\n var self = this;\n // Safari and chrome < 15 crash if we close window before\n // waiting for ES cleanup. See:\n // https://code.google.com/p/chromium/issues/detail?id=89155\n setTimeout(function() {\n self.emit('close', null, reason);\n self.removeAllListeners();\n }, 200);\n};\n\nmodule.exports = EventSourceReceiver;\n","module.exports = '1.6.1';\n","'use strict';\n\nvar eventUtils = require('./event')\n , browser = require('./browser')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:utils:iframe');\n}\n\nmodule.exports = {\n WPrefix: '_jp'\n, currentWindowId: null\n\n, polluteGlobalNamespace: function() {\n if (!(module.exports.WPrefix in global)) {\n global[module.exports.WPrefix] = {};\n }\n }\n\n, postMessage: function(type, data) {\n if (global.parent !== global) {\n global.parent.postMessage(JSON.stringify({\n windowId: module.exports.currentWindowId\n , type: type\n , data: data || ''\n }), '*');\n } else {\n debug('Cannot postMessage, no parent window.', type, data);\n }\n }\n\n, createIframe: function(iframeUrl, errorCallback) {\n var iframe = global.document.createElement('iframe');\n var tref, unloadRef;\n var unattach = function() {\n debug('unattach');\n clearTimeout(tref);\n // Explorer had problems with that.\n try {\n iframe.onload = null;\n } catch (x) {\n // intentionally empty\n }\n iframe.onerror = null;\n };\n var cleanup = function() {\n debug('cleanup');\n if (iframe) {\n unattach();\n // This timeout makes chrome fire onbeforeunload event\n // within iframe. Without the timeout it goes straight to\n // onunload.\n setTimeout(function() {\n if (iframe) {\n iframe.parentNode.removeChild(iframe);\n }\n iframe = null;\n }, 0);\n eventUtils.unloadDel(unloadRef);\n }\n };\n var onerror = function(err) {\n debug('onerror', err);\n if (iframe) {\n cleanup();\n errorCallback(err);\n }\n };\n var post = function(msg, origin) {\n debug('post', msg, origin);\n setTimeout(function() {\n try {\n // When the iframe is not loaded, IE raises an exception\n // on 'contentWindow'.\n if (iframe && iframe.contentWindow) {\n iframe.contentWindow.postMessage(msg, origin);\n }\n } catch (x) {\n // intentionally empty\n }\n }, 0);\n };\n\n iframe.src = iframeUrl;\n iframe.style.display = 'none';\n iframe.style.position = 'absolute';\n iframe.onerror = function() {\n onerror('onerror');\n };\n iframe.onload = function() {\n debug('onload');\n // `onload` is triggered before scripts on the iframe are\n // executed. Give it few seconds to actually load stuff.\n clearTimeout(tref);\n tref = setTimeout(function() {\n onerror('onload timeout');\n }, 2000);\n };\n global.document.body.appendChild(iframe);\n tref = setTimeout(function() {\n onerror('timeout');\n }, 15000);\n unloadRef = eventUtils.unloadAdd(cleanup);\n return {\n post: post\n , cleanup: cleanup\n , loaded: unattach\n };\n }\n\n/* eslint no-undef: \"off\", new-cap: \"off\" */\n, createHtmlfile: function(iframeUrl, errorCallback) {\n var axo = ['Active'].concat('Object').join('X');\n var doc = new global[axo]('htmlfile');\n var tref, unloadRef;\n var iframe;\n var unattach = function() {\n clearTimeout(tref);\n iframe.onerror = null;\n };\n var cleanup = function() {\n if (doc) {\n unattach();\n eventUtils.unloadDel(unloadRef);\n iframe.parentNode.removeChild(iframe);\n iframe = doc = null;\n CollectGarbage();\n }\n };\n var onerror = function(r) {\n debug('onerror', r);\n if (doc) {\n cleanup();\n errorCallback(r);\n }\n };\n var post = function(msg, origin) {\n try {\n // When the iframe is not loaded, IE raises an exception\n // on 'contentWindow'.\n setTimeout(function() {\n if (iframe && iframe.contentWindow) {\n iframe.contentWindow.postMessage(msg, origin);\n }\n }, 0);\n } catch (x) {\n // intentionally empty\n }\n };\n\n doc.open();\n doc.write('<html><s' + 'cript>' +\n 'document.domain=\"' + global.document.domain + '\";' +\n '</s' + 'cript></html>');\n doc.close();\n doc.parentWindow[module.exports.WPrefix] = global[module.exports.WPrefix];\n var c = doc.createElement('div');\n doc.body.appendChild(c);\n iframe = doc.createElement('iframe');\n c.appendChild(iframe);\n iframe.src = iframeUrl;\n iframe.onerror = function() {\n onerror('onerror');\n };\n tref = setTimeout(function() {\n onerror('timeout');\n }, 15000);\n unloadRef = eventUtils.unloadAdd(cleanup);\n return {\n post: post\n , cleanup: cleanup\n , loaded: unattach\n };\n }\n};\n\nmodule.exports.iframeEnabled = false;\nif (global.document) {\n // postMessage misbehaves in konqueror 4.6.5 - the messages are delivered with\n // huge delay, or not at all.\n module.exports.iframeEnabled = (typeof global.postMessage === 'function' ||\n typeof global.postMessage === 'object') && (!browser.isKonqueror());\n}\n","'use strict';\n\n// Few cool transports do work only for same-origin. In order to make\n// them work cross-domain we shall use iframe, served from the\n// remote domain. New browsers have capabilities to communicate with\n// cross domain iframe using postMessage(). In IE it was implemented\n// from IE 8+, but of course, IE got some details wrong:\n// http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx\n// http://stevesouders.com/misc/test-postmessage.php\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n , version = require('../version')\n , urlUtils = require('../utils/url')\n , iframeUtils = require('../utils/iframe')\n , eventUtils = require('../utils/event')\n , random = require('../utils/random')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:transport:iframe');\n}\n\nfunction IframeTransport(transport, transUrl, baseUrl) {\n if (!IframeTransport.enabled()) {\n throw new Error('Transport created when disabled');\n }\n EventEmitter.call(this);\n\n var self = this;\n this.origin = urlUtils.getOrigin(baseUrl);\n this.baseUrl = baseUrl;\n this.transUrl = transUrl;\n this.transport = transport;\n this.windowId = random.string(8);\n\n var iframeUrl = urlUtils.addPath(baseUrl, '/iframe.html') + '#' + this.windowId;\n debug(transport, transUrl, iframeUrl);\n\n this.iframeObj = iframeUtils.createIframe(iframeUrl, function(r) {\n debug('err callback');\n self.emit('close', 1006, 'Unable to load an iframe (' + r + ')');\n self.close();\n });\n\n this.onmessageCallback = this._message.bind(this);\n eventUtils.attachEvent('message', this.onmessageCallback);\n}\n\ninherits(IframeTransport, EventEmitter);\n\nIframeTransport.prototype.close = function() {\n debug('close');\n this.removeAllListeners();\n if (this.iframeObj) {\n eventUtils.detachEvent('message', this.onmessageCallback);\n try {\n // When the iframe is not loaded, IE raises an exception\n // on 'contentWindow'.\n this.postMessage('c');\n } catch (x) {\n // intentionally empty\n }\n this.iframeObj.cleanup();\n this.iframeObj = null;\n this.onmessageCallback = this.iframeObj = null;\n }\n};\n\nIframeTransport.prototype._message = function(e) {\n debug('message', e.data);\n if (!urlUtils.isOriginEqual(e.origin, this.origin)) {\n debug('not same origin', e.origin, this.origin);\n return;\n }\n\n var iframeMessage;\n try {\n iframeMessage = JSON.parse(e.data);\n } catch (ignored) {\n debug('bad json', e.data);\n return;\n }\n\n if (iframeMessage.windowId !== this.windowId) {\n debug('mismatched window id', iframeMessage.windowId, this.windowId);\n return;\n }\n\n switch (iframeMessage.type) {\n case 's':\n this.iframeObj.loaded();\n // window global dependency\n this.postMessage('s', JSON.stringify([\n version\n , this.transport\n , this.transUrl\n , this.baseUrl\n ]));\n break;\n case 't':\n this.emit('message', iframeMessage.data);\n break;\n case 'c':\n var cdata;\n try {\n cdata = JSON.parse(iframeMessage.data);\n } catch (ignored) {\n debug('bad json', iframeMessage.data);\n return;\n }\n this.emit('close', cdata[0], cdata[1]);\n this.close();\n break;\n }\n};\n\nIframeTransport.prototype.postMessage = function(type, data) {\n debug('postMessage', type, data);\n this.iframeObj.post(JSON.stringify({\n windowId: this.windowId\n , type: type\n , data: data || ''\n }), this.origin);\n};\n\nIframeTransport.prototype.send = function(message) {\n debug('send', message);\n this.postMessage('m', message);\n};\n\nIframeTransport.enabled = function() {\n return iframeUtils.iframeEnabled;\n};\n\nIframeTransport.transportName = 'iframe';\nIframeTransport.roundTrips = 2;\n\nmodule.exports = IframeTransport;\n","'use strict';\n\nmodule.exports = {\n isObject: function(obj) {\n var type = typeof obj;\n return type === 'function' || type === 'object' && !!obj;\n }\n\n, extend: function(obj) {\n if (!this.isObject(obj)) {\n return obj;\n }\n var source, prop;\n for (var i = 1, length = arguments.length; i < length; i++) {\n source = arguments[i];\n for (prop in source) {\n if (Object.prototype.hasOwnProperty.call(source, prop)) {\n obj[prop] = source[prop];\n }\n }\n }\n return obj;\n }\n};\n","'use strict';\n\nvar inherits = require('inherits')\n , IframeTransport = require('../iframe')\n , objectUtils = require('../../utils/object')\n ;\n\nmodule.exports = function(transport) {\n\n function IframeWrapTransport(transUrl, baseUrl) {\n IframeTransport.call(this, transport.transportName, transUrl, baseUrl);\n }\n\n inherits(IframeWrapTransport, IframeTransport);\n\n IframeWrapTransport.enabled = function(url, info) {\n if (!global.document) {\n return false;\n }\n\n var iframeInfo = objectUtils.extend({}, info);\n iframeInfo.sameOrigin = true;\n return transport.enabled(iframeInfo) && IframeTransport.enabled();\n };\n\n IframeWrapTransport.transportName = 'iframe-' + transport.transportName;\n IframeWrapTransport.needBody = true;\n IframeWrapTransport.roundTrips = IframeTransport.roundTrips + transport.roundTrips - 1; // html, javascript (2) + transport - no CORS (1)\n\n IframeWrapTransport.facadeTransport = transport;\n\n return IframeWrapTransport;\n};\n","'use strict';\n\nvar inherits = require('inherits')\n , HtmlfileReceiver = require('./receiver/htmlfile')\n , XHRLocalObject = require('./sender/xhr-local')\n , AjaxBasedTransport = require('./lib/ajax-based')\n ;\n\nfunction HtmlFileTransport(transUrl) {\n if (!HtmlfileReceiver.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/htmlfile', HtmlfileReceiver, XHRLocalObject);\n}\n\ninherits(HtmlFileTransport, AjaxBasedTransport);\n\nHtmlFileTransport.enabled = function(info) {\n return HtmlfileReceiver.enabled && info.sameOrigin;\n};\n\nHtmlFileTransport.transportName = 'htmlfile';\nHtmlFileTransport.roundTrips = 2;\n\nmodule.exports = HtmlFileTransport;\n","'use strict';\n\nvar inherits = require('inherits')\n , iframeUtils = require('../../utils/iframe')\n , urlUtils = require('../../utils/url')\n , EventEmitter = require('events').EventEmitter\n , random = require('../../utils/random')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:receiver:htmlfile');\n}\n\nfunction HtmlfileReceiver(url) {\n debug(url);\n EventEmitter.call(this);\n var self = this;\n iframeUtils.polluteGlobalNamespace();\n\n this.id = 'a' + random.string(6);\n url = urlUtils.addQuery(url, 'c=' + decodeURIComponent(iframeUtils.WPrefix + '.' + this.id));\n\n debug('using htmlfile', HtmlfileReceiver.htmlfileEnabled);\n var constructFunc = HtmlfileReceiver.htmlfileEnabled ?\n iframeUtils.createHtmlfile : iframeUtils.createIframe;\n\n global[iframeUtils.WPrefix][this.id] = {\n start: function() {\n debug('start');\n self.iframeObj.loaded();\n }\n , message: function(data) {\n debug('message', data);\n self.emit('message', data);\n }\n , stop: function() {\n debug('stop');\n self._cleanup();\n self._close('network');\n }\n };\n this.iframeObj = constructFunc(url, function() {\n debug('callback');\n self._cleanup();\n self._close('permanent');\n });\n}\n\ninherits(HtmlfileReceiver, EventEmitter);\n\nHtmlfileReceiver.prototype.abort = function() {\n debug('abort');\n this._cleanup();\n this._close('user');\n};\n\nHtmlfileReceiver.prototype._cleanup = function() {\n debug('_cleanup');\n if (this.iframeObj) {\n this.iframeObj.cleanup();\n this.iframeObj = null;\n }\n delete global[iframeUtils.WPrefix][this.id];\n};\n\nHtmlfileReceiver.prototype._close = function(reason) {\n debug('_close', reason);\n this.emit('close', null, reason);\n this.removeAllListeners();\n};\n\nHtmlfileReceiver.htmlfileEnabled = false;\n\n// obfuscate to avoid firewalls\nvar axo = ['Active'].concat('Object').join('X');\nif (axo in global) {\n try {\n HtmlfileReceiver.htmlfileEnabled = !!new global[axo]('htmlfile');\n } catch (x) {\n // intentionally empty\n }\n}\n\nHtmlfileReceiver.enabled = HtmlfileReceiver.htmlfileEnabled || iframeUtils.iframeEnabled;\n\nmodule.exports = HtmlfileReceiver;\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , XhrReceiver = require('./receiver/xhr')\n , XHRCorsObject = require('./sender/xhr-cors')\n , XHRLocalObject = require('./sender/xhr-local')\n ;\n\nfunction XhrPollingTransport(transUrl) {\n if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XHRCorsObject);\n}\n\ninherits(XhrPollingTransport, AjaxBasedTransport);\n\nXhrPollingTransport.enabled = function(info) {\n if (info.nullOrigin) {\n return false;\n }\n\n if (XHRLocalObject.enabled && info.sameOrigin) {\n return true;\n }\n return XHRCorsObject.enabled;\n};\n\nXhrPollingTransport.transportName = 'xhr-polling';\nXhrPollingTransport.roundTrips = 2; // preflight, ajax\n\nmodule.exports = XhrPollingTransport;\n","'use strict';\n\nvar inherits = require('inherits')\n , AjaxBasedTransport = require('./lib/ajax-based')\n , XdrStreamingTransport = require('./xdr-streaming')\n , XhrReceiver = require('./receiver/xhr')\n , XDRObject = require('./sender/xdr')\n ;\n\nfunction XdrPollingTransport(transUrl) {\n if (!XDRObject.enabled) {\n throw new Error('Transport created when disabled');\n }\n AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XDRObject);\n}\n\ninherits(XdrPollingTransport, AjaxBasedTransport);\n\nXdrPollingTransport.enabled = XdrStreamingTransport.enabled;\nXdrPollingTransport.transportName = 'xdr-polling';\nXdrPollingTransport.roundTrips = 2; // preflight, ajax\n\nmodule.exports = XdrPollingTransport;\n","'use strict';\n\nvar random = require('../../utils/random')\n , urlUtils = require('../../utils/url')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:sender:jsonp');\n}\n\nvar form, area;\n\nfunction createIframe(id) {\n debug('createIframe', id);\n try {\n // ie6 dynamic iframes with target=\"\" support (thanks Chris Lambacher)\n return global.document.createElement('<iframe name=\"' + id + '\">');\n } catch (x) {\n var iframe = global.document.createElement('iframe');\n iframe.name = id;\n return iframe;\n }\n}\n\nfunction createForm() {\n debug('createForm');\n form = global.document.createElement('form');\n form.style.display = 'none';\n form.style.position = 'absolute';\n form.method = 'POST';\n form.enctype = 'application/x-www-form-urlencoded';\n form.acceptCharset = 'UTF-8';\n\n area = global.document.createElement('textarea');\n area.name = 'd';\n form.appendChild(area);\n\n global.document.body.appendChild(form);\n}\n\nmodule.exports = function(url, payload, callback) {\n debug(url, payload);\n if (!form) {\n createForm();\n }\n var id = 'a' + random.string(8);\n form.target = id;\n form.action = urlUtils.addQuery(urlUtils.addPath(url, '/jsonp_send'), 'i=' + id);\n\n var iframe = createIframe(id);\n iframe.id = id;\n iframe.style.display = 'none';\n form.appendChild(iframe);\n\n try {\n area.value = payload;\n } catch (e) {\n // seriously broken browsers get here\n }\n form.submit();\n\n var completed = function(err) {\n debug('completed', id, err);\n if (!iframe.onerror) {\n return;\n }\n iframe.onreadystatechange = iframe.onerror = iframe.onload = null;\n // Opera mini doesn't like if we GC iframe\n // immediately, thus this timeout.\n setTimeout(function() {\n debug('cleaning up', id);\n iframe.parentNode.removeChild(iframe);\n iframe = null;\n }, 500);\n area.value = '';\n // It is not possible to detect if the iframe succeeded or\n // failed to submit our form.\n callback(err);\n };\n iframe.onerror = function() {\n debug('onerror', id);\n completed();\n };\n iframe.onload = function() {\n debug('onload', id);\n completed();\n };\n iframe.onreadystatechange = function(e) {\n debug('onreadystatechange', id, iframe.readyState, e);\n if (iframe.readyState === 'complete') {\n completed();\n }\n };\n return function() {\n debug('aborted', id);\n completed(new Error('Aborted'));\n };\n};\n","'use strict';\n\n// The simplest and most robust transport, using the well-know cross\n// domain hack - JSONP. This transport is quite inefficient - one\n// message could use up to one http request. But at least it works almost\n// everywhere.\n// Known limitations:\n// o you will get a spinning cursor\n// o for Konqueror a dumb timer is needed to detect errors\n\nvar inherits = require('inherits')\n , SenderReceiver = require('./lib/sender-receiver')\n , JsonpReceiver = require('./receiver/jsonp')\n , jsonpSender = require('./sender/jsonp')\n ;\n\nfunction JsonPTransport(transUrl) {\n if (!JsonPTransport.enabled()) {\n throw new Error('Transport created when disabled');\n }\n SenderReceiver.call(this, transUrl, '/jsonp', jsonpSender, JsonpReceiver);\n}\n\ninherits(JsonPTransport, SenderReceiver);\n\nJsonPTransport.enabled = function() {\n return !!global.document;\n};\n\nJsonPTransport.transportName = 'jsonp-polling';\nJsonPTransport.roundTrips = 1;\nJsonPTransport.needBody = true;\n\nmodule.exports = JsonPTransport;\n","'use strict';\n\nvar utils = require('../../utils/iframe')\n , random = require('../../utils/random')\n , browser = require('../../utils/browser')\n , urlUtils = require('../../utils/url')\n , inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:receiver:jsonp');\n}\n\nfunction JsonpReceiver(url) {\n debug(url);\n var self = this;\n EventEmitter.call(this);\n\n utils.polluteGlobalNamespace();\n\n this.id = 'a' + random.string(6);\n var urlWithId = urlUtils.addQuery(url, 'c=' + encodeURIComponent(utils.WPrefix + '.' + this.id));\n\n global[utils.WPrefix][this.id] = this._callback.bind(this);\n this._createScript(urlWithId);\n\n // Fallback mostly for Konqueror - stupid timer, 35 seconds shall be plenty.\n this.timeoutId = setTimeout(function() {\n debug('timeout');\n self._abort(new Error('JSONP script loaded abnormally (timeout)'));\n }, JsonpReceiver.timeout);\n}\n\ninherits(JsonpReceiver, EventEmitter);\n\nJsonpReceiver.prototype.abort = function() {\n debug('abort');\n if (global[utils.WPrefix][this.id]) {\n var err = new Error('JSONP user aborted read');\n err.code = 1000;\n this._abort(err);\n }\n};\n\nJsonpReceiver.timeout = 35000;\nJsonpReceiver.scriptErrorTimeout = 1000;\n\nJsonpReceiver.prototype._callback = function(data) {\n debug('_callback', data);\n this._cleanup();\n\n if (this.aborting) {\n return;\n }\n\n if (data) {\n debug('message', data);\n this.emit('message', data);\n }\n this.emit('close', null, 'network');\n this.removeAllListeners();\n};\n\nJsonpReceiver.prototype._abort = function(err) {\n debug('_abort', err);\n this._cleanup();\n this.aborting = true;\n this.emit('close', err.code, err.message);\n this.removeAllListeners();\n};\n\nJsonpReceiver.prototype._cleanup = function() {\n debug('_cleanup');\n clearTimeout(this.timeoutId);\n if (this.script2) {\n this.script2.parentNode.removeChild(this.script2);\n this.script2 = null;\n }\n if (this.script) {\n var script = this.script;\n // Unfortunately, you can't really abort script loading of\n // the script.\n script.parentNode.removeChild(script);\n script.onreadystatechange = script.onerror =\n script.onload = script.onclick = null;\n this.script = null;\n }\n delete global[utils.WPrefix][this.id];\n};\n\nJsonpReceiver.prototype._scriptError = function() {\n debug('_scriptError');\n var self = this;\n if (this.errorTimer) {\n return;\n }\n\n this.errorTimer = setTimeout(function() {\n if (!self.loadedOkay) {\n self._abort(new Error('JSONP script loaded abnormally (onerror)'));\n }\n }, JsonpReceiver.scriptErrorTimeout);\n};\n\nJsonpReceiver.prototype._createScript = function(url) {\n debug('_createScript', url);\n var self = this;\n var script = this.script = global.document.createElement('script');\n var script2; // Opera synchronous load trick.\n\n script.id = 'a' + random.string(8);\n script.src = url;\n script.type = 'text/javascript';\n script.charset = 'UTF-8';\n script.onerror = this._scriptError.bind(this);\n script.onload = function() {\n debug('onload');\n self._abort(new Error('JSONP script loaded abnormally (onload)'));\n };\n\n // IE9 fires 'error' event after onreadystatechange or before, in random order.\n // Use loadedOkay to determine if actually errored\n script.onreadystatechange = function() {\n debug('onreadystatechange', script.readyState);\n if (/loaded|closed/.test(script.readyState)) {\n if (script && script.htmlFor && script.onclick) {\n self.loadedOkay = true;\n try {\n // In IE, actually execute the script.\n script.onclick();\n } catch (x) {\n // intentionally empty\n }\n }\n if (script) {\n self._abort(new Error('JSONP script loaded abnormally (onreadystatechange)'));\n }\n }\n };\n // IE: event/htmlFor/onclick trick.\n // One can't rely on proper order for onreadystatechange. In order to\n // make sure, set a 'htmlFor' and 'event' properties, so that\n // script code will be installed as 'onclick' handler for the\n // script object. Later, onreadystatechange, manually execute this\n // code. FF and Chrome doesn't work with 'event' and 'htmlFor'\n // set. For reference see:\n // http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html\n // Also, read on that about script ordering:\n // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order\n if (typeof script.async === 'undefined' && global.document.attachEvent) {\n // According to mozilla docs, in recent browsers script.async defaults\n // to 'true', so we may use it to detect a good browser:\n // https://developer.mozilla.org/en/HTML/Element/script\n if (!browser.isOpera()) {\n // Naively assume we're in IE\n try {\n script.htmlFor = script.id;\n script.event = 'onclick';\n } catch (x) {\n // intentionally empty\n }\n script.async = true;\n } else {\n // Opera, second sync script hack\n script2 = this.script2 = global.document.createElement('script');\n script2.text = \"try{var a = document.getElementById('\" + script.id + \"'); if(a)a.onerror();}catch(x){};\";\n script.async = script2.async = false;\n }\n }\n if (typeof script.async !== 'undefined') {\n script.async = true;\n }\n\n var head = global.document.getElementsByTagName('head')[0];\n head.insertBefore(script, head.firstChild);\n if (script2) {\n head.insertBefore(script2, head.firstChild);\n }\n};\n\nmodule.exports = JsonpReceiver;\n","'use strict';\n\nmodule.exports = [\n // streaming transports\n require('./transport/websocket')\n, require('./transport/xhr-streaming')\n, require('./transport/xdr-streaming')\n, require('./transport/eventsource')\n, require('./transport/lib/iframe-wrap')(require('./transport/eventsource'))\n\n // polling transports\n, require('./transport/htmlfile')\n, require('./transport/lib/iframe-wrap')(require('./transport/htmlfile'))\n, require('./transport/xhr-polling')\n, require('./transport/xdr-polling')\n, require('./transport/lib/iframe-wrap')(require('./transport/xhr-polling'))\n, require('./transport/jsonp-polling')\n];\n","/* eslint-disable */\n/* jscs: disable */\n'use strict';\n\n// pulled specific shims from https://github.com/es-shims/es5-shim\n\nvar ArrayPrototype = Array.prototype;\nvar ObjectPrototype = Object.prototype;\nvar FunctionPrototype = Function.prototype;\nvar StringPrototype = String.prototype;\nvar array_slice = ArrayPrototype.slice;\n\nvar _toString = ObjectPrototype.toString;\nvar isFunction = function (val) {\n return ObjectPrototype.toString.call(val) === '[object Function]';\n};\nvar isArray = function isArray(obj) {\n return _toString.call(obj) === '[object Array]';\n};\nvar isString = function isString(obj) {\n return _toString.call(obj) === '[object String]';\n};\n\nvar supportsDescriptors = Object.defineProperty && (function () {\n try {\n Object.defineProperty({}, 'x', {});\n return true;\n } catch (e) { /* this is ES3 */\n return false;\n }\n}());\n\n// Define configurable, writable and non-enumerable props\n// if they don't exist.\nvar defineProperty;\nif (supportsDescriptors) {\n defineProperty = function (object, name, method, forceAssign) {\n if (!forceAssign && (name in object)) { return; }\n Object.defineProperty(object, name, {\n configurable: true,\n enumerable: false,\n writable: true,\n value: method\n });\n };\n} else {\n defineProperty = function (object, name, method, forceAssign) {\n if (!forceAssign && (name in object)) { return; }\n object[name] = method;\n };\n}\nvar defineProperties = function (object, map, forceAssign) {\n for (var name in map) {\n if (ObjectPrototype.hasOwnProperty.call(map, name)) {\n defineProperty(object, name, map[name], forceAssign);\n }\n }\n};\n\nvar toObject = function (o) {\n if (o == null) { // this matches both null and undefined\n throw new TypeError(\"can't convert \" + o + ' to object');\n }\n return Object(o);\n};\n\n//\n// Util\n// ======\n//\n\n// ES5 9.4\n// http://es5.github.com/#x9.4\n// http://jsperf.com/to-integer\n\nfunction toInteger(num) {\n var n = +num;\n if (n !== n) { // isNaN\n n = 0;\n } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {\n n = (n > 0 || -1) * Math.floor(Math.abs(n));\n }\n return n;\n}\n\nfunction ToUint32(x) {\n return x >>> 0;\n}\n\n//\n// Function\n// ========\n//\n\n// ES-5 15.3.4.5\n// http://es5.github.com/#x15.3.4.5\n\nfunction Empty() {}\n\ndefineProperties(FunctionPrototype, {\n bind: function bind(that) { // .length is 1\n // 1. Let Target be the this value.\n var target = this;\n // 2. If IsCallable(Target) is false, throw a TypeError exception.\n if (!isFunction(target)) {\n throw new TypeError('Function.prototype.bind called on incompatible ' + target);\n }\n // 3. Let A be a new (possibly empty) internal list of all of the\n // argument values provided after thisArg (arg1, arg2 etc), in order.\n // XXX slicedArgs will stand in for \"A\" if used\n var args = array_slice.call(arguments, 1); // for normal call\n // 4. Let F be a new native ECMAScript object.\n // 11. Set the [[Prototype]] internal property of F to the standard\n // built-in Function prototype object as specified in 15.3.3.1.\n // 12. Set the [[Call]] internal property of F as described in\n // 15.3.4.5.1.\n // 13. Set the [[Construct]] internal property of F as described in\n // 15.3.4.5.2.\n // 14. Set the [[HasInstance]] internal property of F as described in\n // 15.3.4.5.3.\n var binder = function () {\n\n if (this instanceof bound) {\n // 15.3.4.5.2 [[Construct]]\n // When the [[Construct]] internal method of a function object,\n // F that was created using the bind function is called with a\n // list of arguments ExtraArgs, the following steps are taken:\n // 1. Let target be the value of F's [[TargetFunction]]\n // internal property.\n // 2. If target has no [[Construct]] internal method, a\n // TypeError exception is thrown.\n // 3. Let boundArgs be the value of F's [[BoundArgs]] internal\n // property.\n // 4. Let args be a new list containing the same values as the\n // list boundArgs in the same order followed by the same\n // values as the list ExtraArgs in the same order.\n // 5. Return the result of calling the [[Construct]] internal\n // method of target providing args as the arguments.\n\n var result = target.apply(\n this,\n args.concat(array_slice.call(arguments))\n );\n if (Object(result) === result) {\n return result;\n }\n return this;\n\n } else {\n // 15.3.4.5.1 [[Call]]\n // When the [[Call]] internal method of a function object, F,\n // which was created using the bind function is called with a\n // this value and a list of arguments ExtraArgs, the following\n // steps are taken:\n // 1. Let boundArgs be the value of F's [[BoundArgs]] internal\n // property.\n // 2. Let boundThis be the value of F's [[BoundThis]] internal\n // property.\n // 3. Let target be the value of F's [[TargetFunction]] internal\n // property.\n // 4. Let args be a new list containing the same values as the\n // list boundArgs in the same order followed by the same\n // values as the list ExtraArgs in the same order.\n // 5. Return the result of calling the [[Call]] internal method\n // of target providing boundThis as the this value and\n // providing args as the arguments.\n\n // equiv: target.call(this, ...boundArgs, ...args)\n return target.apply(\n that,\n args.concat(array_slice.call(arguments))\n );\n\n }\n\n };\n\n // 15. If the [[Class]] internal property of Target is \"Function\", then\n // a. Let L be the length property of Target minus the length of A.\n // b. Set the length own property of F to either 0 or L, whichever is\n // larger.\n // 16. Else set the length own property of F to 0.\n\n var boundLength = Math.max(0, target.length - args.length);\n\n // 17. Set the attributes of the length own property of F to the values\n // specified in 15.3.5.1.\n var boundArgs = [];\n for (var i = 0; i < boundLength; i++) {\n boundArgs.push('$' + i);\n }\n\n // XXX Build a dynamic function with desired amount of arguments is the only\n // way to set the length property of a function.\n // In environments where Content Security Policies enabled (Chrome extensions,\n // for ex.) all use of eval or Function costructor throws an exception.\n // However in all of these environments Function.prototype.bind exists\n // and so this code will never be executed.\n var bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);\n\n if (target.prototype) {\n Empty.prototype = target.prototype;\n bound.prototype = new Empty();\n // Clean up dangling references.\n Empty.prototype = null;\n }\n\n // TODO\n // 18. Set the [[Extensible]] internal property of F to true.\n\n // TODO\n // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).\n // 20. Call the [[DefineOwnProperty]] internal method of F with\n // arguments \"caller\", PropertyDescriptor {[[Get]]: thrower, [[Set]]:\n // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and\n // false.\n // 21. Call the [[DefineOwnProperty]] internal method of F with\n // arguments \"arguments\", PropertyDescriptor {[[Get]]: thrower,\n // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},\n // and false.\n\n // TODO\n // NOTE Function objects created using Function.prototype.bind do not\n // have a prototype property or the [[Code]], [[FormalParameters]], and\n // [[Scope]] internal properties.\n // XXX can't delete prototype in pure-js.\n\n // 22. Return F.\n return bound;\n }\n});\n\n//\n// Array\n// =====\n//\n\n// ES5 15.4.3.2\n// http://es5.github.com/#x15.4.3.2\n// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray\ndefineProperties(Array, { isArray: isArray });\n\n\nvar boxedString = Object('a');\nvar splitString = boxedString[0] !== 'a' || !(0 in boxedString);\n\nvar properlyBoxesContext = function properlyBoxed(method) {\n // Check node 0.6.21 bug where third parameter is not boxed\n var properlyBoxesNonStrict = true;\n var properlyBoxesStrict = true;\n if (method) {\n method.call('foo', function (_, __, context) {\n if (typeof context !== 'object') { properlyBoxesNonStrict = false; }\n });\n\n method.call([1], function () {\n 'use strict';\n properlyBoxesStrict = typeof this === 'string';\n }, 'x');\n }\n return !!method && properlyBoxesNonStrict && properlyBoxesStrict;\n};\n\ndefineProperties(ArrayPrototype, {\n forEach: function forEach(fun /*, thisp*/) {\n var object = toObject(this),\n self = splitString && isString(this) ? this.split('') : object,\n thisp = arguments[1],\n i = -1,\n length = self.length >>> 0;\n\n // If no callback function or if callback is not a callable function\n if (!isFunction(fun)) {\n throw new TypeError(); // TODO message\n }\n\n while (++i < length) {\n if (i in self) {\n // Invoke the callback function with call, passing arguments:\n // context, property value, property key, thisArg object\n // context\n fun.call(thisp, self[i], i, object);\n }\n }\n }\n}, !properlyBoxesContext(ArrayPrototype.forEach));\n\n// ES5 15.4.4.14\n// http://es5.github.com/#x15.4.4.14\n// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf\nvar hasFirefox2IndexOfBug = Array.prototype.indexOf && [0, 1].indexOf(1, 2) !== -1;\ndefineProperties(ArrayPrototype, {\n indexOf: function indexOf(sought /*, fromIndex */ ) {\n var self = splitString && isString(this) ? this.split('') : toObject(this),\n length = self.length >>> 0;\n\n if (!length) {\n return -1;\n }\n\n var i = 0;\n if (arguments.length > 1) {\n i = toInteger(arguments[1]);\n }\n\n // handle negative indices\n i = i >= 0 ? i : Math.max(0, length + i);\n for (; i < length; i++) {\n if (i in self && self[i] === sought) {\n return i;\n }\n }\n return -1;\n }\n}, hasFirefox2IndexOfBug);\n\n//\n// String\n// ======\n//\n\n// ES5 15.5.4.14\n// http://es5.github.com/#x15.5.4.14\n\n// [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]\n// Many browsers do not split properly with regular expressions or they\n// do not perform the split correctly under obscure conditions.\n// See http://blog.stevenlevithan.com/archives/cross-browser-split\n// I've tested in many browsers and this seems to cover the deviant ones:\n// 'ab'.split(/(?:ab)*/) should be [\"\", \"\"], not [\"\"]\n// '.'.split(/(.?)(.?)/) should be [\"\", \".\", \"\", \"\"], not [\"\", \"\"]\n// 'tesst'.split(/(s)*/) should be [\"t\", undefined, \"e\", \"s\", \"t\"], not\n// [undefined, \"t\", undefined, \"e\", ...]\n// ''.split(/.?/) should be [], not [\"\"]\n// '.'.split(/()()/) should be [\".\"], not [\"\", \"\", \".\"]\n\nvar string_split = StringPrototype.split;\nif (\n 'ab'.split(/(?:ab)*/).length !== 2 ||\n '.'.split(/(.?)(.?)/).length !== 4 ||\n 'tesst'.split(/(s)*/)[1] === 't' ||\n 'test'.split(/(?:)/, -1).length !== 4 ||\n ''.split(/.?/).length ||\n '.'.split(/()()/).length > 1\n) {\n (function () {\n var compliantExecNpcg = /()??/.exec('')[1] === void 0; // NPCG: nonparticipating capturing group\n\n StringPrototype.split = function (separator, limit) {\n var string = this;\n if (separator === void 0 && limit === 0) {\n return [];\n }\n\n // If `separator` is not a regex, use native split\n if (_toString.call(separator) !== '[object RegExp]') {\n return string_split.call(this, separator, limit);\n }\n\n var output = [],\n flags = (separator.ignoreCase ? 'i' : '') +\n (separator.multiline ? 'm' : '') +\n (separator.extended ? 'x' : '') + // Proposed for ES6\n (separator.sticky ? 'y' : ''), // Firefox 3+\n lastLastIndex = 0,\n // Make `global` and avoid `lastIndex` issues by working with a copy\n separator2, match, lastIndex, lastLength;\n separator = new RegExp(separator.source, flags + 'g');\n string += ''; // Type-convert\n if (!compliantExecNpcg) {\n // Doesn't need flags gy, but they don't hurt\n separator2 = new RegExp('^' + separator.source + '$(?!\\\\s)', flags);\n }\n /* Values for `limit`, per the spec:\n * If undefined: 4294967295 // Math.pow(2, 32) - 1\n * If 0, Infinity, or NaN: 0\n * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;\n * If negative number: 4294967296 - Math.floor(Math.abs(limit))\n * If other: Type-convert, then use the above rules\n */\n limit = limit === void 0 ?\n -1 >>> 0 : // Math.pow(2, 32) - 1\n ToUint32(limit);\n while (match = separator.exec(string)) {\n // `separator.lastIndex` is not reliable cross-browser\n lastIndex = match.index + match[0].length;\n if (lastIndex > lastLastIndex) {\n output.push(string.slice(lastLastIndex, match.index));\n // Fix browsers whose `exec` methods don't consistently return `undefined` for\n // nonparticipating capturing groups\n if (!compliantExecNpcg && match.length > 1) {\n match[0].replace(separator2, function () {\n for (var i = 1; i < arguments.length - 2; i++) {\n if (arguments[i] === void 0) {\n match[i] = void 0;\n }\n }\n });\n }\n if (match.length > 1 && match.index < string.length) {\n ArrayPrototype.push.apply(output, match.slice(1));\n }\n lastLength = match[0].length;\n lastLastIndex = lastIndex;\n if (output.length >= limit) {\n break;\n }\n }\n if (separator.lastIndex === match.index) {\n separator.lastIndex++; // Avoid an infinite loop\n }\n }\n if (lastLastIndex === string.length) {\n if (lastLength || !separator.test('')) {\n output.push('');\n }\n } else {\n output.push(string.slice(lastLastIndex));\n }\n return output.length > limit ? output.slice(0, limit) : output;\n };\n }());\n\n// [bugfix, chrome]\n// If separator is undefined, then the result array contains just one String,\n// which is the this value (converted to a String). If limit is not undefined,\n// then the output array is truncated so that it contains no more than limit\n// elements.\n// \"0\".split(undefined, 0) -> []\n} else if ('0'.split(void 0, 0).length) {\n StringPrototype.split = function split(separator, limit) {\n if (separator === void 0 && limit === 0) { return []; }\n return string_split.call(this, separator, limit);\n };\n}\n\n// ECMA-262, 3rd B.2.3\n// Not an ECMAScript standard, although ECMAScript 3rd Edition has a\n// non-normative section suggesting uniform semantics and it should be\n// normalized across all browsers\n// [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE\nvar string_substr = StringPrototype.substr;\nvar hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';\ndefineProperties(StringPrototype, {\n substr: function substr(start, length) {\n return string_substr.call(\n this,\n start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start,\n length\n );\n }\n}, hasNegativeSubstrBug);\n","'use strict';\n\n// Some extra characters that Chrome gets wrong, and substitutes with\n// something else on the wire.\n// eslint-disable-next-line no-control-regex, no-misleading-character-class\nvar extraEscapable = /[\\x00-\\x1f\\ud800-\\udfff\\ufffe\\uffff\\u0300-\\u0333\\u033d-\\u0346\\u034a-\\u034c\\u0350-\\u0352\\u0357-\\u0358\\u035c-\\u0362\\u0374\\u037e\\u0387\\u0591-\\u05af\\u05c4\\u0610-\\u0617\\u0653-\\u0654\\u0657-\\u065b\\u065d-\\u065e\\u06df-\\u06e2\\u06eb-\\u06ec\\u0730\\u0732-\\u0733\\u0735-\\u0736\\u073a\\u073d\\u073f-\\u0741\\u0743\\u0745\\u0747\\u07eb-\\u07f1\\u0951\\u0958-\\u095f\\u09dc-\\u09dd\\u09df\\u0a33\\u0a36\\u0a59-\\u0a5b\\u0a5e\\u0b5c-\\u0b5d\\u0e38-\\u0e39\\u0f43\\u0f4d\\u0f52\\u0f57\\u0f5c\\u0f69\\u0f72-\\u0f76\\u0f78\\u0f80-\\u0f83\\u0f93\\u0f9d\\u0fa2\\u0fa7\\u0fac\\u0fb9\\u1939-\\u193a\\u1a17\\u1b6b\\u1cda-\\u1cdb\\u1dc0-\\u1dcf\\u1dfc\\u1dfe\\u1f71\\u1f73\\u1f75\\u1f77\\u1f79\\u1f7b\\u1f7d\\u1fbb\\u1fbe\\u1fc9\\u1fcb\\u1fd3\\u1fdb\\u1fe3\\u1feb\\u1fee-\\u1fef\\u1ff9\\u1ffb\\u1ffd\\u2000-\\u2001\\u20d0-\\u20d1\\u20d4-\\u20d7\\u20e7-\\u20e9\\u2126\\u212a-\\u212b\\u2329-\\u232a\\u2adc\\u302b-\\u302c\\uaab2-\\uaab3\\uf900-\\ufa0d\\ufa10\\ufa12\\ufa15-\\ufa1e\\ufa20\\ufa22\\ufa25-\\ufa26\\ufa2a-\\ufa2d\\ufa30-\\ufa6d\\ufa70-\\ufad9\\ufb1d\\ufb1f\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40-\\ufb41\\ufb43-\\ufb44\\ufb46-\\ufb4e\\ufff0-\\uffff]/g\n , extraLookup;\n\n// This may be quite slow, so let's delay until user actually uses bad\n// characters.\nvar unrollLookup = function(escapable) {\n var i;\n var unrolled = {};\n var c = [];\n for (i = 0; i < 65536; i++) {\n c.push( String.fromCharCode(i) );\n }\n escapable.lastIndex = 0;\n c.join('').replace(escapable, function(a) {\n unrolled[ a ] = '\\\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\n return '';\n });\n escapable.lastIndex = 0;\n return unrolled;\n};\n\n// Quote string, also taking care of unicode characters that browsers\n// often break. Especially, take care of unicode surrogates:\n// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates\nmodule.exports = {\n quote: function(string) {\n var quoted = JSON.stringify(string);\n\n // In most cases this should be very fast and good enough.\n extraEscapable.lastIndex = 0;\n if (!extraEscapable.test(quoted)) {\n return quoted;\n }\n\n if (!extraLookup) {\n extraLookup = unrollLookup(extraEscapable);\n }\n\n return quoted.replace(extraEscapable, function(a) {\n return extraLookup[a];\n });\n }\n};\n","'use strict';\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:utils:transport');\n}\n\nmodule.exports = function(availableTransports) {\n return {\n filterToEnabled: function(transportsWhitelist, info) {\n var transports = {\n main: []\n , facade: []\n };\n if (!transportsWhitelist) {\n transportsWhitelist = [];\n } else if (typeof transportsWhitelist === 'string') {\n transportsWhitelist = [transportsWhitelist];\n }\n\n availableTransports.forEach(function(trans) {\n if (!trans) {\n return;\n }\n\n if (trans.transportName === 'websocket' && info.websocket === false) {\n debug('disabled from server', 'websocket');\n return;\n }\n\n if (transportsWhitelist.length &&\n transportsWhitelist.indexOf(trans.transportName) === -1) {\n debug('not in whitelist', trans.transportName);\n return;\n }\n\n if (trans.enabled(info)) {\n debug('enabled', trans.transportName);\n transports.main.push(trans);\n if (trans.facadeTransport) {\n transports.facade.push(trans.facadeTransport);\n }\n } else {\n debug('disabled', trans.transportName);\n }\n });\n return transports;\n }\n };\n};\n","'use strict';\n\nvar logObject = {};\n['log', 'debug', 'warn'].forEach(function (level) {\n var levelExists;\n\n try {\n levelExists = global.console && global.console[level] && global.console[level].apply;\n } catch(e) {\n // do nothing\n }\n\n logObject[level] = levelExists ? function () {\n return global.console[level].apply(global.console, arguments);\n } : (level === 'log' ? function () {} : logObject.log);\n});\n\nmodule.exports = logObject;\n","'use strict';\n\nfunction Event(eventType) {\n this.type = eventType;\n}\n\nEvent.prototype.initEvent = function(eventType, canBubble, cancelable) {\n this.type = eventType;\n this.bubbles = canBubble;\n this.cancelable = cancelable;\n this.timeStamp = +new Date();\n return this;\n};\n\nEvent.prototype.stopPropagation = function() {};\nEvent.prototype.preventDefault = function() {};\n\nEvent.CAPTURING_PHASE = 1;\nEvent.AT_TARGET = 2;\nEvent.BUBBLING_PHASE = 3;\n\nmodule.exports = Event;\n","'use strict';\n\nmodule.exports = global.location || {\n origin: 'http://localhost:80'\n, protocol: 'http:'\n, host: 'localhost'\n, port: 80\n, href: 'http://localhost/'\n, hash: ''\n};\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , objectUtils = require('./utils/object')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:info-ajax');\n}\n\nfunction InfoAjax(url, AjaxObject) {\n EventEmitter.call(this);\n\n var self = this;\n var t0 = +new Date();\n this.xo = new AjaxObject('GET', url);\n\n this.xo.once('finish', function(status, text) {\n var info, rtt;\n if (status === 200) {\n rtt = (+new Date()) - t0;\n if (text) {\n try {\n info = JSON.parse(text);\n } catch (e) {\n debug('bad json', text);\n }\n }\n\n if (!objectUtils.isObject(info)) {\n info = {};\n }\n }\n self.emit('finish', info, rtt);\n self.removeAllListeners();\n });\n}\n\ninherits(InfoAjax, EventEmitter);\n\nInfoAjax.prototype.close = function() {\n this.removeAllListeners();\n this.xo.close();\n};\n\nmodule.exports = InfoAjax;\n","'use strict';\n\nvar inherits = require('inherits')\n , EventEmitter = require('events').EventEmitter\n , XHRLocalObject = require('./transport/sender/xhr-local')\n , InfoAjax = require('./info-ajax')\n ;\n\nfunction InfoReceiverIframe(transUrl) {\n var self = this;\n EventEmitter.call(this);\n\n this.ir = new InfoAjax(transUrl, XHRLocalObject);\n this.ir.once('finish', function(info, rtt) {\n self.ir = null;\n self.emit('message', JSON.stringify([info, rtt]));\n });\n}\n\ninherits(InfoReceiverIframe, EventEmitter);\n\nInfoReceiverIframe.transportName = 'iframe-info-receiver';\n\nInfoReceiverIframe.prototype.close = function() {\n if (this.ir) {\n this.ir.close();\n this.ir = null;\n }\n this.removeAllListeners();\n};\n\nmodule.exports = InfoReceiverIframe;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , urlUtils = require('./utils/url')\n , XDR = require('./transport/sender/xdr')\n , XHRCors = require('./transport/sender/xhr-cors')\n , XHRLocal = require('./transport/sender/xhr-local')\n , XHRFake = require('./transport/sender/xhr-fake')\n , InfoIframe = require('./info-iframe')\n , InfoAjax = require('./info-ajax')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:info-receiver');\n}\n\nfunction InfoReceiver(baseUrl, urlInfo) {\n debug(baseUrl);\n var self = this;\n EventEmitter.call(this);\n\n setTimeout(function() {\n self.doXhr(baseUrl, urlInfo);\n }, 0);\n}\n\ninherits(InfoReceiver, EventEmitter);\n\n// TODO this is currently ignoring the list of available transports and the whitelist\n\nInfoReceiver._getReceiver = function(baseUrl, url, urlInfo) {\n // determine method of CORS support (if needed)\n if (urlInfo.sameOrigin) {\n return new InfoAjax(url, XHRLocal);\n }\n if (XHRCors.enabled) {\n return new InfoAjax(url, XHRCors);\n }\n if (XDR.enabled && urlInfo.sameScheme) {\n return new InfoAjax(url, XDR);\n }\n if (InfoIframe.enabled()) {\n return new InfoIframe(baseUrl, url);\n }\n return new InfoAjax(url, XHRFake);\n};\n\nInfoReceiver.prototype.doXhr = function(baseUrl, urlInfo) {\n var self = this\n , url = urlUtils.addPath(baseUrl, '/info')\n ;\n debug('doXhr', url);\n\n this.xo = InfoReceiver._getReceiver(baseUrl, url, urlInfo);\n\n this.timeoutRef = setTimeout(function() {\n debug('timeout');\n self._cleanup(false);\n self.emit('finish');\n }, InfoReceiver.timeout);\n\n this.xo.once('finish', function(info, rtt) {\n debug('finish', info, rtt);\n self._cleanup(true);\n self.emit('finish', info, rtt);\n });\n};\n\nInfoReceiver.prototype._cleanup = function(wasClean) {\n debug('_cleanup');\n clearTimeout(this.timeoutRef);\n this.timeoutRef = null;\n if (!wasClean && this.xo) {\n this.xo.close();\n }\n this.xo = null;\n};\n\nInfoReceiver.prototype.close = function() {\n debug('close');\n this.removeAllListeners();\n this._cleanup(false);\n};\n\nInfoReceiver.timeout = 8000;\n\nmodule.exports = InfoReceiver;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n ;\n\nfunction XHRFake(/* method, url, payload, opts */) {\n var self = this;\n EventEmitter.call(this);\n\n this.to = setTimeout(function() {\n self.emit('finish', 200, '{}');\n }, XHRFake.timeout);\n}\n\ninherits(XHRFake, EventEmitter);\n\nXHRFake.prototype.close = function() {\n clearTimeout(this.to);\n};\n\nXHRFake.timeout = 2000;\n\nmodule.exports = XHRFake;\n","'use strict';\n\nvar EventEmitter = require('events').EventEmitter\n , inherits = require('inherits')\n , utils = require('./utils/event')\n , IframeTransport = require('./transport/iframe')\n , InfoReceiverIframe = require('./info-iframe-receiver')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:info-iframe');\n}\n\nfunction InfoIframe(baseUrl, url) {\n var self = this;\n EventEmitter.call(this);\n\n var go = function() {\n var ifr = self.ifr = new IframeTransport(InfoReceiverIframe.transportName, url, baseUrl);\n\n ifr.once('message', function(msg) {\n if (msg) {\n var d;\n try {\n d = JSON.parse(msg);\n } catch (e) {\n debug('bad json', msg);\n self.emit('finish');\n self.close();\n return;\n }\n\n var info = d[0], rtt = d[1];\n self.emit('finish', info, rtt);\n }\n self.close();\n });\n\n ifr.once('close', function() {\n self.emit('finish');\n self.close();\n });\n };\n\n // TODO this seems the same as the 'needBody' from transports\n if (!global.document.body) {\n utils.attachEvent('load', go);\n } else {\n go();\n }\n}\n\ninherits(InfoIframe, EventEmitter);\n\nInfoIframe.enabled = function() {\n return IframeTransport.enabled();\n};\n\nInfoIframe.prototype.close = function() {\n if (this.ifr) {\n this.ifr.close();\n }\n this.removeAllListeners();\n this.ifr = null;\n};\n\nmodule.exports = InfoIframe;\n","'use strict';\n\nvar urlUtils = require('./utils/url')\n , eventUtils = require('./utils/event')\n , FacadeJS = require('./facade')\n , InfoIframeReceiver = require('./info-iframe-receiver')\n , iframeUtils = require('./utils/iframe')\n , loc = require('./location')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:iframe-bootstrap');\n}\n\nmodule.exports = function(SockJS, availableTransports) {\n var transportMap = {};\n availableTransports.forEach(function(at) {\n if (at.facadeTransport) {\n transportMap[at.facadeTransport.transportName] = at.facadeTransport;\n }\n });\n\n // hard-coded for the info iframe\n // TODO see if we can make this more dynamic\n transportMap[InfoIframeReceiver.transportName] = InfoIframeReceiver;\n var parentOrigin;\n\n /* eslint-disable camelcase */\n SockJS.bootstrap_iframe = function() {\n /* eslint-enable camelcase */\n var facade;\n iframeUtils.currentWindowId = loc.hash.slice(1);\n var onMessage = function(e) {\n if (e.source !== parent) {\n return;\n }\n if (typeof parentOrigin === 'undefined') {\n parentOrigin = e.origin;\n }\n if (e.origin !== parentOrigin) {\n return;\n }\n\n var iframeMessage;\n try {\n iframeMessage = JSON.parse(e.data);\n } catch (ignored) {\n debug('bad json', e.data);\n return;\n }\n\n if (iframeMessage.windowId !== iframeUtils.currentWindowId) {\n return;\n }\n switch (iframeMessage.type) {\n case 's':\n var p;\n try {\n p = JSON.parse(iframeMessage.data);\n } catch (ignored) {\n debug('bad json', iframeMessage.data);\n break;\n }\n var version = p[0];\n var transport = p[1];\n var transUrl = p[2];\n var baseUrl = p[3];\n debug(version, transport, transUrl, baseUrl);\n // change this to semver logic\n if (version !== SockJS.version) {\n throw new Error('Incompatible SockJS! Main site uses:' +\n ' \"' + version + '\", the iframe:' +\n ' \"' + SockJS.version + '\".');\n }\n\n if (!urlUtils.isOriginEqual(transUrl, loc.href) ||\n !urlUtils.isOriginEqual(baseUrl, loc.href)) {\n throw new Error('Can\\'t connect to different domain from within an ' +\n 'iframe. (' + loc.href + ', ' + transUrl + ', ' + baseUrl + ')');\n }\n facade = new FacadeJS(new transportMap[transport](transUrl, baseUrl));\n break;\n case 'm':\n facade._send(iframeMessage.data);\n break;\n case 'c':\n if (facade) {\n facade._close();\n }\n facade = null;\n break;\n }\n };\n\n eventUtils.attachEvent('message', onMessage);\n\n // Start\n iframeUtils.postMessage('s');\n };\n};\n","'use strict';\n\nvar iframeUtils = require('./utils/iframe')\n ;\n\nfunction FacadeJS(transport) {\n this._transport = transport;\n transport.on('message', this._transportMessage.bind(this));\n transport.on('close', this._transportClose.bind(this));\n}\n\nFacadeJS.prototype._transportClose = function(code, reason) {\n iframeUtils.postMessage('c', JSON.stringify([code, reason]));\n};\nFacadeJS.prototype._transportMessage = function(frame) {\n iframeUtils.postMessage('t', frame);\n};\nFacadeJS.prototype._send = function(data) {\n this._transport.send(data);\n};\nFacadeJS.prototype._close = function() {\n this._transport.close();\n this._transport.removeAllListeners();\n};\n\nmodule.exports = FacadeJS;\n","'use strict';\n\nrequire('./shims');\n\nvar URL = require('url-parse')\n , inherits = require('inherits')\n , random = require('./utils/random')\n , escape = require('./utils/escape')\n , urlUtils = require('./utils/url')\n , eventUtils = require('./utils/event')\n , transport = require('./utils/transport')\n , objectUtils = require('./utils/object')\n , browser = require('./utils/browser')\n , log = require('./utils/log')\n , Event = require('./event/event')\n , EventTarget = require('./event/eventtarget')\n , loc = require('./location')\n , CloseEvent = require('./event/close')\n , TransportMessageEvent = require('./event/trans-message')\n , InfoReceiver = require('./info-receiver')\n ;\n\nvar debug = function() {};\nif (process.env.NODE_ENV !== 'production') {\n debug = require('debug')('sockjs-client:main');\n}\n\nvar transports;\n\n// follow constructor steps defined at http://dev.w3.org/html5/websockets/#the-websocket-interface\nfunction SockJS(url, protocols, options) {\n if (!(this instanceof SockJS)) {\n return new SockJS(url, protocols, options);\n }\n if (arguments.length < 1) {\n throw new TypeError(\"Failed to construct 'SockJS: 1 argument required, but only 0 present\");\n }\n EventTarget.call(this);\n\n this.readyState = SockJS.CONNECTING;\n this.extensions = '';\n this.protocol = '';\n\n // non-standard extension\n options = options || {};\n if (options.protocols_whitelist) {\n log.warn(\"'protocols_whitelist' is DEPRECATED. Use 'transports' instead.\");\n }\n this._transportsWhitelist = options.transports;\n this._transportOptions = options.transportOptions || {};\n this._timeout = options.timeout || 0;\n\n var sessionId = options.sessionId || 8;\n if (typeof sessionId === 'function') {\n this._generateSessionId = sessionId;\n } else if (typeof sessionId === 'number') {\n this._generateSessionId = function() {\n return random.string(sessionId);\n };\n } else {\n throw new TypeError('If sessionId is used in the options, it needs to be a number or a function.');\n }\n\n this._server = options.server || random.numberString(1000);\n\n // Step 1 of WS spec - parse and validate the url. Issue #8\n var parsedUrl = new URL(url);\n if (!parsedUrl.host || !parsedUrl.protocol) {\n throw new SyntaxError(\"The URL '\" + url + \"' is invalid\");\n } else if (parsedUrl.hash) {\n throw new SyntaxError('The URL must not contain a fragment');\n } else if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {\n throw new SyntaxError(\"The URL's scheme must be either 'http:' or 'https:'. '\" + parsedUrl.protocol + \"' is not allowed.\");\n }\n\n var secure = parsedUrl.protocol === 'https:';\n // Step 2 - don't allow secure origin with an insecure protocol\n if (loc.protocol === 'https:' && !secure) {\n // exception is 127.0.0.0/8 and ::1 urls\n if (!urlUtils.isLoopbackAddr(parsedUrl.hostname)) {\n throw new Error('SecurityError: An insecure SockJS connection may not be initiated from a page loaded over HTTPS');\n }\n }\n\n // Step 3 - check port access - no need here\n // Step 4 - parse protocols argument\n if (!protocols) {\n protocols = [];\n } else if (!Array.isArray(protocols)) {\n protocols = [protocols];\n }\n\n // Step 5 - check protocols argument\n var sortedProtocols = protocols.sort();\n sortedProtocols.forEach(function(proto, i) {\n if (!proto) {\n throw new SyntaxError(\"The protocols entry '\" + proto + \"' is invalid.\");\n }\n if (i < (sortedProtocols.length - 1) && proto === sortedProtocols[i + 1]) {\n throw new SyntaxError(\"The protocols entry '\" + proto + \"' is duplicated.\");\n }\n });\n\n // Step 6 - convert origin\n var o = urlUtils.getOrigin(loc.href);\n this._origin = o ? o.toLowerCase() : null;\n\n // remove the trailing slash\n parsedUrl.set('pathname', parsedUrl.pathname.replace(/\\/+$/, ''));\n\n // store the sanitized url\n this.url = parsedUrl.href;\n debug('using url', this.url);\n\n // Step 7 - start connection in background\n // obtain server info\n // http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-26\n this._urlInfo = {\n nullOrigin: !browser.hasDomain()\n , sameOrigin: urlUtils.isOriginEqual(this.url, loc.href)\n , sameScheme: urlUtils.isSchemeEqual(this.url, loc.href)\n };\n\n this._ir = new InfoReceiver(this.url, this._urlInfo);\n this._ir.once('finish', this._receiveInfo.bind(this));\n}\n\ninherits(SockJS, EventTarget);\n\nfunction userSetCode(code) {\n return code === 1000 || (code >= 3000 && code <= 4999);\n}\n\nSockJS.prototype.close = function(code, reason) {\n // Step 1\n if (code && !userSetCode(code)) {\n throw new Error('InvalidAccessError: Invalid code');\n }\n // Step 2.4 states the max is 123 bytes, but we are just checking length\n if (reason && reason.length > 123) {\n throw new SyntaxError('reason argument has an invalid length');\n }\n\n // Step 3.1\n if (this.readyState === SockJS.CLOSING || this.readyState === SockJS.CLOSED) {\n return;\n }\n\n // TODO look at docs to determine how to set this\n var wasClean = true;\n this._close(code || 1000, reason || 'Normal closure', wasClean);\n};\n\nSockJS.prototype.send = function(data) {\n // #13 - convert anything non-string to string\n // TODO this currently turns objects into [object Object]\n if (typeof data !== 'string') {\n data = '' + data;\n }\n if (this.readyState === SockJS.CONNECTING) {\n throw new Error('InvalidStateError: The connection has not been established yet');\n }\n if (this.readyState !== SockJS.OPEN) {\n return;\n }\n this._transport.send(escape.quote(data));\n};\n\nSockJS.version = require('./version');\n\nSockJS.CONNECTING = 0;\nSockJS.OPEN = 1;\nSockJS.CLOSING = 2;\nSockJS.CLOSED = 3;\n\nSockJS.prototype._receiveInfo = function(info, rtt) {\n debug('_receiveInfo', rtt);\n this._ir = null;\n if (!info) {\n this._close(1002, 'Cannot connect to server');\n return;\n }\n\n // establish a round-trip timeout (RTO) based on the\n // round-trip time (RTT)\n this._rto = this.countRTO(rtt);\n // allow server to override url used for the actual transport\n this._transUrl = info.base_url ? info.base_url : this.url;\n info = objectUtils.extend(info, this._urlInfo);\n debug('info', info);\n // determine list of desired and supported transports\n var enabledTransports = transports.filterToEnabled(this._transportsWhitelist, info);\n this._transports = enabledTransports.main;\n debug(this._transports.length + ' enabled transports');\n\n this._connect();\n};\n\nSockJS.prototype._connect = function() {\n for (var Transport = this._transports.shift(); Transport; Transport = this._transports.shift()) {\n debug('attempt', Transport.transportName);\n if (Transport.needBody) {\n if (!global.document.body ||\n (typeof global.document.readyState !== 'undefined' &&\n global.document.readyState !== 'complete' &&\n global.document.readyState !== 'interactive')) {\n debug('waiting for body');\n this._transports.unshift(Transport);\n eventUtils.attachEvent('load', this._connect.bind(this));\n return;\n }\n }\n\n // calculate timeout based on RTO and round trips. Default to 5s\n var timeoutMs = Math.max(this._timeout, (this._rto * Transport.roundTrips) || 5000);\n this._transportTimeoutId = setTimeout(this._transportTimeout.bind(this), timeoutMs);\n debug('using timeout', timeoutMs);\n\n var transportUrl = urlUtils.addPath(this._transUrl, '/' + this._server + '/' + this._generateSessionId());\n var options = this._transportOptions[Transport.transportName];\n debug('transport url', transportUrl);\n var transportObj = new Transport(transportUrl, this._transUrl, options);\n transportObj.on('message', this._transportMessage.bind(this));\n transportObj.once('close', this._transportClose.bind(this));\n transportObj.transportName = Transport.transportName;\n this._transport = transportObj;\n\n return;\n }\n this._close(2000, 'All transports failed', false);\n};\n\nSockJS.prototype._transportTimeout = function() {\n debug('_transportTimeout');\n if (this.readyState === SockJS.CONNECTING) {\n if (this._transport) {\n this._transport.close();\n }\n\n this._transportClose(2007, 'Transport timed out');\n }\n};\n\nSockJS.prototype._transportMessage = function(msg) {\n debug('_transportMessage', msg);\n var self = this\n , type = msg.slice(0, 1)\n , content = msg.slice(1)\n , payload\n ;\n\n // first check for messages that don't need a payload\n switch (type) {\n case 'o':\n this._open();\n return;\n case 'h':\n this.dispatchEvent(new Event('heartbeat'));\n debug('heartbeat', this.transport);\n return;\n }\n\n if (content) {\n try {\n payload = JSON.parse(content);\n } catch (e) {\n debug('bad json', content);\n }\n }\n\n if (typeof payload === 'undefined') {\n debug('empty payload', content);\n return;\n }\n\n switch (type) {\n case 'a':\n if (Array.isArray(payload)) {\n payload.forEach(function(p) {\n debug('message', self.transport, p);\n self.dispatchEvent(new TransportMessageEvent(p));\n });\n }\n break;\n case 'm':\n debug('message', this.transport, payload);\n this.dispatchEvent(new TransportMessageEvent(payload));\n break;\n case 'c':\n if (Array.isArray(payload) && payload.length === 2) {\n this._close(payload[0], payload[1], true);\n }\n break;\n }\n};\n\nSockJS.prototype._transportClose = function(code, reason) {\n debug('_transportClose', this.transport, code, reason);\n if (this._transport) {\n this._transport.removeAllListeners();\n this._transport = null;\n this.transport = null;\n }\n\n if (!userSetCode(code) && code !== 2000 && this.readyState === SockJS.CONNECTING) {\n this._connect();\n return;\n }\n\n this._close(code, reason);\n};\n\nSockJS.prototype._open = function() {\n debug('_open', this._transport && this._transport.transportName, this.readyState);\n if (this.readyState === SockJS.CONNECTING) {\n if (this._transportTimeoutId) {\n clearTimeout(this._transportTimeoutId);\n this._transportTimeoutId = null;\n }\n this.readyState = SockJS.OPEN;\n this.transport = this._transport.transportName;\n this.dispatchEvent(new Event('open'));\n debug('connected', this.transport);\n } else {\n // The server might have been restarted, and lost track of our\n // connection.\n this._close(1006, 'Server lost session');\n }\n};\n\nSockJS.prototype._close = function(code, reason, wasClean) {\n debug('_close', this.transport, code, reason, wasClean, this.readyState);\n var forceFail = false;\n\n if (this._ir) {\n forceFail = true;\n this._ir.close();\n this._ir = null;\n }\n if (this._transport) {\n this._transport.close();\n this._transport = null;\n this.transport = null;\n }\n\n if (this.readyState === SockJS.CLOSED) {\n throw new Error('InvalidStateError: SockJS has already been closed');\n }\n\n this.readyState = SockJS.CLOSING;\n setTimeout(function() {\n this.readyState = SockJS.CLOSED;\n\n if (forceFail) {\n this.dispatchEvent(new Event('error'));\n }\n\n var e = new CloseEvent('close');\n e.wasClean = wasClean || false;\n e.code = code || 1000;\n e.reason = reason;\n\n this.dispatchEvent(e);\n this.onmessage = this.onclose = this.onerror = null;\n debug('disconnected');\n }.bind(this), 0);\n};\n\n// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/\n// and RFC 2988.\nSockJS.prototype.countRTO = function(rtt) {\n // In a local environment, when using IE8/9 and the `jsonp-polling`\n // transport the time needed to establish a connection (the time that pass\n // from the opening of the transport to the call of `_dispatchOpen`) is\n // around 200msec (the lower bound used in the article above) and this\n // causes spurious timeouts. For this reason we calculate a value slightly\n // larger than that used in the article.\n if (rtt > 100) {\n return 4 * rtt; // rto > 400msec\n }\n return 300 + rtt; // 300msec < rto <= 400msec\n};\n\nmodule.exports = function(availableTransports) {\n transports = transport(availableTransports);\n require('./iframe-bootstrap')(SockJS, availableTransports);\n return SockJS;\n};\n","'use strict';\n\nvar inherits = require('inherits')\n , Event = require('./event')\n ;\n\nfunction CloseEvent() {\n Event.call(this);\n this.initEvent('close', false, false);\n this.wasClean = false;\n this.code = 0;\n this.reason = '';\n}\n\ninherits(CloseEvent, Event);\n\nmodule.exports = CloseEvent;\n","'use strict';\n\nvar inherits = require('inherits')\n , Event = require('./event')\n ;\n\nfunction TransportMessageEvent(data) {\n Event.call(this);\n this.initEvent('message', false, false);\n this.data = data;\n}\n\ninherits(TransportMessageEvent, Event);\n\nmodule.exports = TransportMessageEvent;\n","'use strict';\n\nvar transportList = require('./transport-list');\n\nmodule.exports = require('./main')(transportList);\n\n// TODO can't get rid of this until all servers do\nif ('_sockjs_onload' in global) {\n setTimeout(global._sockjs_onload, 1);\n}\n","/**\r\n * ConnectionManager\r\n * WebSocket/STOMP 연결 관리 (Chat, WebRTC 공유)\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport { ConnectionState, ErrorTypes, WebSocketPaths, DefaultConfig, LogLevel } from '../constants.js';\r\nimport { Client as StompClient } from '@stomp/stompjs';\r\nimport SockJS from 'sockjs-client';\r\n\r\n// STOMP Client 가져오기 (CDN 사용 시 전역 객체 우선)\r\nconst getStompClient = () => {\r\n // noinspection JSUnresolvedReference\r\n if (typeof window !== 'undefined' && window.StompJs && window.StompJs.Client) {\r\n return window.StompJs.Client;\r\n }\r\n return StompClient;\r\n};\r\n\r\n// SockJS 가져오기 (CDN 사용 시 전역 객체 우선)\r\nconst getSockJS = () => {\r\n // noinspection JSUnresolvedReference\r\n if (typeof window !== 'undefined' && window.SockJS) {\r\n return window.SockJS;\r\n }\r\n return SockJS;\r\n};\r\n\r\nclass ConnectionManager extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {string} options.serverUrl - 서버 URL\r\n * @param {string} options.jwtToken - JWT 토큰\r\n * @param {string} options.apiKey - API 키\r\n * @param {string} options.projectId - 프로젝트 ID\r\n * @param {boolean} [options.useSockJS=true] - SockJS 사용 여부\r\n * @param {number} [options.reconnectDelay] - 재연결 지연 시간\r\n * @param {number} [options.maxReconnectAttempts] - 최대 재연결 시도 횟수\r\n * @param {number} [options.heartbeatIncoming] - 수신 하트비트 간격\r\n * @param {number} [options.heartbeatOutgoing] - 발신 하트비트 간격\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n super();\r\n\r\n this.serverUrl = options.serverUrl.replace(/\\/$/, '');\r\n this.jwtToken = options.jwtToken;\r\n this.apiKey = options.apiKey;\r\n this.projectId = options.projectId;\r\n this.useSockJS = options.useSockJS !== false;\r\n\r\n this.reconnectDelay = options.reconnectDelay || DefaultConfig.reconnectDelay;\r\n this.maxReconnectAttempts = options.maxReconnectAttempts || DefaultConfig.maxReconnectAttempts;\r\n this.heartbeatIncoming = options.heartbeatIncoming || DefaultConfig.heartbeatIncoming;\r\n this.heartbeatOutgoing = options.heartbeatOutgoing || DefaultConfig.heartbeatOutgoing;\r\n\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'ConnectionManager');\r\n\r\n // 상태\r\n this.state = ConnectionState.DISCONNECTED;\r\n this.stompClient = null;\r\n this.reconnectAttempts = 0;\r\n this.subscriptions = new Map();\r\n this.pendingSubscriptions = [];\r\n this.userId = null;\r\n }\r\n\r\n /**\r\n * 연결 상태 변경\r\n * @private\r\n */\r\n _setState(state) {\r\n const prevState = this.state;\r\n this.state = state;\r\n this.emit('stateChange', { state, prevState });\r\n this.logger.info(`State changed: ${prevState} -> ${state}`);\r\n }\r\n\r\n /**\r\n * WebSocket URL 생성.\r\n *\r\n * <p>JWT 는 STOMP connectHeaders 의 Authorization 헤더로만 전달합니다.\r\n * URL query parameter 에 토큰을 넣지 않습니다 — 서버 로그, 프록시 로그,\r\n * 브라우저 히스토리에 JWT 가 평문 노출되는 것을 방지.</p>\r\n *\r\n * @private\r\n */\r\n _getWebSocketUrl() {\r\n const endpoint = this.useSockJS\r\n ? WebSocketPaths.SOCKJS_ENDPOINT\r\n : WebSocketPaths.NATIVE_ENDPOINT;\r\n\r\n return `${this.serverUrl}${endpoint}`;\r\n }\r\n\r\n /**\r\n * STOMP 클라이언트 생성\r\n * @private\r\n */\r\n _createStompClient() {\r\n const wsUrl = this._getWebSocketUrl();\r\n\r\n const config = {\r\n connectHeaders: {\r\n 'Authorization': this.jwtToken.startsWith('Bearer ')\r\n ? this.jwtToken\r\n : `Bearer ${this.jwtToken}`,\r\n 'X-API-KEY': this.apiKey,\r\n 'X-PROJECT-ID': this.projectId\r\n },\r\n heartbeatIncoming: this.heartbeatIncoming,\r\n heartbeatOutgoing: this.heartbeatOutgoing,\r\n reconnectDelay: this.reconnectDelay,\r\n debug: (str) => {\r\n if (this.logger.level <= LogLevel.DEBUG) {\r\n this.logger.debug('STOMP:', str);\r\n }\r\n },\r\n onConnect: (frame) => this._onConnect(frame),\r\n onDisconnect: (frame) => this._onDisconnect(frame),\r\n onStompError: (frame) => this._onStompError(frame),\r\n onWebSocketClose: (event) => this._onWebSocketClose(event),\r\n onWebSocketError: (event) => this._onWebSocketError(event)\r\n };\r\n\r\n if (this.useSockJS) {\r\n const SockJS = getSockJS();\r\n config.webSocketFactory = () => new SockJS(wsUrl);\r\n } else {\r\n // Native WebSocket — 토큰은 connectHeaders 로만 전달 (URL 노출 방지)\r\n const wsProtocol = this.serverUrl.startsWith('https') ? 'wss' : 'ws';\r\n const wsHost = this.serverUrl.replace(/^https?:\\/\\//, '');\r\n config.brokerURL = `${wsProtocol}://${wsHost}${WebSocketPaths.NATIVE_ENDPOINT}`;\r\n }\r\n\r\n const Client = getStompClient();\r\n return new Client(config);\r\n }\r\n\r\n /**\r\n * 연결\r\n * @returns {Promise<void>}\r\n */\r\n async connect() {\r\n if (this.state === ConnectionState.CONNECTED) {\r\n this.logger.warn('Already connected');\r\n return;\r\n }\r\n\r\n if (this.state === ConnectionState.CONNECTING) {\r\n this.logger.warn('Connection in progress');\r\n return;\r\n }\r\n\r\n this._setState(ConnectionState.CONNECTING);\r\n this.reconnectAttempts = 0;\r\n\r\n return new Promise((resolve, reject) => {\r\n this._connectResolve = resolve;\r\n this._connectReject = reject;\r\n\r\n try {\r\n this.stompClient = this._createStompClient();\r\n this.stompClient.activate();\r\n } catch (error) {\r\n this._setState(ConnectionState.ERROR);\r\n this._drainPendingSubscriptions('Connection activation failed');\r\n reject(error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * 연결 성공 콜백\r\n * @private\r\n */\r\n _onConnect(frame) {\r\n this._setState(ConnectionState.CONNECTED);\r\n this.reconnectAttempts = 0;\r\n this.logger.info('Connected to server', frame);\r\n\r\n // 기존 구독 재등록 (재연결 시)\r\n this._restoreSubscriptions();\r\n\r\n // 대기 중인 구독 처리\r\n this._processPendingSubscriptions();\r\n\r\n this.emit('connected', { frame });\r\n\r\n if (this._connectResolve) {\r\n this._connectResolve();\r\n this._connectResolve = null;\r\n this._connectReject = null;\r\n }\r\n }\r\n\r\n /**\r\n * 연결 해제 콜백\r\n * @private\r\n */\r\n _onDisconnect(frame) {\r\n this.logger.info('Disconnected from server', frame);\r\n this._setState(ConnectionState.DISCONNECTED);\r\n this.emit('disconnected', { frame });\r\n }\r\n\r\n /**\r\n * STOMP 에러 콜백\r\n * @private\r\n */\r\n _onStompError(frame) {\r\n this.logger.error('STOMP error:', frame);\r\n this._setState(ConnectionState.ERROR);\r\n\r\n const error = {\r\n type: ErrorTypes.CONNECTION_FAILED,\r\n message: frame.headers?.message || 'STOMP error',\r\n frame\r\n };\r\n\r\n // terminal 전이 — 대기 중 구독 Promise 가 영구 hang 되지 않도록 drain.\r\n this._drainPendingSubscriptions(`STOMP error: ${error.message}`);\r\n\r\n this.emit('error', error);\r\n\r\n if (this._connectReject) {\r\n this._connectReject(new Error(error.message));\r\n this._connectResolve = null;\r\n this._connectReject = null;\r\n }\r\n }\r\n\r\n /**\r\n * WebSocket 종료 콜백\r\n * @private\r\n */\r\n _onWebSocketClose(event) {\r\n this.logger.warn('WebSocket closed:', event);\r\n\r\n if (this.state !== ConnectionState.DISCONNECTED) {\r\n this._handleReconnect();\r\n }\r\n }\r\n\r\n /**\r\n * WebSocket 에러 콜백\r\n * @private\r\n */\r\n _onWebSocketError(event) {\r\n this.logger.error('WebSocket error:', event);\r\n\r\n // 초기 연결 실패 경로 — _connectReject 가 살아있다는 건 아직 한 번도\r\n // CONNECTED 된 적이 없다는 뜻. pending 구독도 함께 drain 해서 호출자 hang 방지.\r\n // 이미 CONNECTED 후 일시 끊김은 _onWebSocketClose → _handleReconnect 로 흘러\r\n // 재연결 성공 시 _processPendingSubscriptions 가 살린다.\r\n if (this._connectReject) {\r\n this._drainPendingSubscriptions('WebSocket connection failed');\r\n this._connectReject(new Error('WebSocket connection failed'));\r\n this._connectResolve = null;\r\n this._connectReject = null;\r\n }\r\n }\r\n\r\n /**\r\n * 재연결 처리.\r\n *\r\n * <p>max 초과 시 STOMP 내부 재연결 루프(reconnectDelay) 를 차단하기 위해\r\n * stompClient 를 반드시 deactivate 하고 참조를 해제한다. state 변경만으로는\r\n * STOMP 자체가 소유한 타이머를 멈출 수 없음.</p>\r\n *\r\n * @private\r\n */\r\n _handleReconnect() {\r\n this.reconnectAttempts++;\r\n\r\n if (this.reconnectAttempts > this.maxReconnectAttempts) {\r\n if (this.stompClient) {\r\n try {\r\n this.stompClient.deactivate();\r\n } catch (error) {\r\n this.logger.warn('Error deactivating stomp client on max reconnect:', error);\r\n }\r\n this.stompClient = null;\r\n }\r\n\r\n // terminal 전이 — 재연결 성공으로 살릴 기회가 사라졌으므로 pending 도 drain.\r\n this._drainPendingSubscriptions('Max reconnection attempts exceeded');\r\n\r\n this._setState(ConnectionState.ERROR);\r\n this.emit('error', {\r\n type: ErrorTypes.CONNECTION_LOST,\r\n message: 'Max reconnection attempts exceeded'\r\n });\r\n return;\r\n }\r\n\r\n this._setState(ConnectionState.RECONNECTING);\r\n this.emit('reconnecting', { attempt: this.reconnectAttempts });\r\n this.logger.info(`Reconnecting... attempt ${this.reconnectAttempts}`);\r\n }\r\n\r\n /**\r\n * 기존 구독 재등록 (재연결 시)\r\n * @private\r\n */\r\n _restoreSubscriptions() {\r\n if (this.subscriptions.size === 0) return;\r\n\r\n this.logger.info(`Restoring ${this.subscriptions.size} subscriptions after reconnect`);\r\n\r\n const previousSubscriptions = new Map(this.subscriptions);\r\n this.subscriptions.clear();\r\n\r\n previousSubscriptions.forEach(({ callback, headers }, destination) => {\r\n this._subscribeInternal(destination, callback, headers);\r\n this.logger.debug(`Restored subscription: ${destination}`);\r\n });\r\n }\r\n\r\n /**\r\n * 대기 중인 구독 처리\r\n * @private\r\n */\r\n _processPendingSubscriptions() {\r\n while (this.pendingSubscriptions.length > 0) {\r\n const { destination, callback, headers, resolve } = this.pendingSubscriptions.shift();\r\n const subscription = this._subscribeInternal(destination, callback, headers);\r\n if (resolve) {\r\n resolve(subscription);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 내부 구독 처리\r\n * @private\r\n */\r\n _subscribeInternal(destination, callback, headers = {}) {\r\n const subscription = this.stompClient.subscribe(destination, (message) => {\r\n try {\r\n const body = JSON.parse(message.body);\r\n callback(body, message);\r\n } catch (error) {\r\n this.logger.error('Failed to parse message:', error);\r\n callback(message.body, message);\r\n }\r\n }, headers);\r\n\r\n // 재연결 시 복구를 위해 callback, headers도 함께 저장\r\n this.subscriptions.set(destination, { subscription, callback, headers });\r\n this.logger.debug(`Subscribed to: ${destination}`);\r\n\r\n return subscription;\r\n }\r\n\r\n /**\r\n * 채널 구독\r\n * @param {string} destination - 구독 경로\r\n * @param {Function} callback - 메시지 콜백\r\n * @param {Object} [headers] - 추가 헤더\r\n * @returns {Promise<Object>} - 구독 객체\r\n */\r\n subscribe(destination, callback, headers = {}) {\r\n // 이미 구독 중이면 기존 구독 해제 후 재구독\r\n if (this.subscriptions.has(destination)) {\r\n this.unsubscribe(destination);\r\n }\r\n\r\n if (this.state !== ConnectionState.CONNECTED) {\r\n return new Promise((resolve, reject) => {\r\n this.pendingSubscriptions.push({ destination, callback, headers, resolve, reject });\r\n this.logger.debug(`Queued subscription for: ${destination}`);\r\n });\r\n }\r\n\r\n return Promise.resolve(this._subscribeInternal(destination, callback, headers));\r\n }\r\n\r\n /**\r\n * 구독 해제\r\n * @param {string} destination - 구독 경로\r\n */\r\n unsubscribe(destination) {\r\n const entry = this.subscriptions.get(destination);\r\n if (entry) {\r\n entry.subscription.unsubscribe();\r\n this.subscriptions.delete(destination);\r\n this.logger.debug(`Unsubscribed from: ${destination}`);\r\n }\r\n }\r\n\r\n /**\r\n * 모든 구독 해제\r\n */\r\n unsubscribeAll() {\r\n this.subscriptions.forEach(({ subscription }, destination) => {\r\n subscription.unsubscribe();\r\n this.logger.debug(`Unsubscribed from: ${destination}`);\r\n });\r\n this.subscriptions.clear();\r\n }\r\n\r\n /**\r\n * 메시지 전송\r\n * @param {string} destination - 전송 경로\r\n * @param {Object} body - 메시지 본문\r\n * @param {Object} [headers] - 추가 헤더\r\n */\r\n send(destination, body, headers = {}) {\r\n if (this.state !== ConnectionState.CONNECTED) {\r\n this.logger.warn('Cannot send message: not connected');\r\n return false;\r\n }\r\n\r\n this.stompClient.publish({\r\n destination,\r\n body: JSON.stringify(body),\r\n headers\r\n });\r\n\r\n this.logger.debug(`Sent to ${destination}:`, body);\r\n return true;\r\n }\r\n\r\n /**\r\n * JWT 토큰 업데이트\r\n * @param {string} token\r\n */\r\n updateToken(token) {\r\n this.jwtToken = token;\r\n\r\n if (this.stompClient) {\r\n this.stompClient.connectHeaders = {\r\n ...this.stompClient.connectHeaders,\r\n 'Authorization': token.startsWith('Bearer ') ? token : `Bearer ${token}`\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * 연결 해제.\r\n *\r\n * <p>대기 중 구독이 남아 있으면 각 Promise 를 reject 하고 큐를 비운다.\r\n * clear 만 하면 호출자 {@code await} 가 영원히 hang 된다.</p>\r\n */\r\n async disconnect() {\r\n this._drainPendingSubscriptions('Disconnected before subscription completed');\r\n\r\n if (this.stompClient) {\r\n this.unsubscribeAll();\r\n await this.stompClient.deactivate();\r\n this.stompClient = null;\r\n }\r\n\r\n this._setState(ConnectionState.DISCONNECTED);\r\n this.logger.info('Disconnected');\r\n }\r\n\r\n /**\r\n * 대기 중 구독 Promise 일괄 reject + 큐 비움.\r\n * @private\r\n */\r\n _drainPendingSubscriptions(reason) {\r\n if (this.pendingSubscriptions.length === 0) return;\r\n\r\n const pending = this.pendingSubscriptions;\r\n this.pendingSubscriptions = [];\r\n\r\n pending.forEach(({ destination, reject }) => {\r\n if (reject) {\r\n reject(new Error(`${reason}: ${destination}`));\r\n }\r\n });\r\n\r\n this.logger.debug(`Drained ${pending.length} pending subscriptions: ${reason}`);\r\n }\r\n\r\n /**\r\n * 연결 상태 확인\r\n * @returns {boolean}\r\n */\r\n isConnected() {\r\n return this.state === ConnectionState.CONNECTED;\r\n }\r\n\r\n /**\r\n * 현재 상태 반환\r\n * @returns {string}\r\n */\r\n getState() {\r\n return this.state;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리.\r\n *\r\n * <p>{@link #disconnect} 가 async 이므로 await 필수. 최상위 {@link TalkFlowClient#destroy}\r\n * 는 별도 {@code await this.disconnect()} 선행으로 이미 완충되지만,\r\n * ConnectionManager 를 직접 소유한 사용자가 destroy 만 호출하는 경로도 안전해야 함.</p>\r\n */\r\n async destroy() {\r\n await this.disconnect();\r\n this.removeAllListeners();\r\n this.logger.info('ConnectionManager destroyed');\r\n }\r\n}\r\n\r\nexport default ConnectionManager;\r\n","// noinspection JSValidateJSDoc\r\n\r\n/**\r\n * ChatClient\r\n * 채팅 기능 클라이언트\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport { WebSocketPaths, LogLevel, RoomListEventType, ChatRoomType } from '../constants.js';\r\n\r\nconst MAX_FILES_PER_MESSAGE = 20;\r\n\r\nclass ChatClient extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {Object} options.connectionManager - ConnectionManager 인스턴스\r\n * @param {Object} options.apiClient - ApiClient 인스턴스\r\n * @param {string} options.userId - 사용자 ID\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n super();\r\n\r\n this.connectionManager = options.connectionManager;\r\n this.apiClient = options.apiClient;\r\n this.userId = options.userId;\r\n\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'ChatClient');\r\n\r\n // 구독 중인 채팅방\r\n this.subscribedRooms = new Map();\r\n\r\n // 채팅방 리스트 구독 상태 (카톡 스타일 리스트 실시간 업데이트)\r\n this.roomListSubscribed = false;\r\n\r\n // 현재 보고 있는 채팅방 ID (이 방에서만 자동 읽음 처리)\r\n this._activeRoomId = null;\r\n\r\n // 타이핑 표시 debounce 타이머 (roomId → setTimeout ID)\r\n // 이 Map 은 outgoing 전용 — 사용자가 startTyping() 호출 시 N초 후 자동 stopTyping() 발신용.\r\n this._typingTimers = new Map();\r\n\r\n // 어시스턴트 응답 준비 중 typing=true 수신 후 client-side timeout (roomId → setTimeout ID).\r\n // 서버는 typing=false 를 발행하지 않으며 (멘션 N personas / 락 실패 / LLM 실패 / skip 등 종료 추적\r\n // 부담 회피), SDK 가 (1) assistant 메시지 도착 시 즉시 해제 (2) 본 timeout 만료 시 자동 해제 책임.\r\n // outgoing 타이머 (_typingTimers) 와 의미가 달라 별도 Map 유지.\r\n this._assistantTypingTimers = new Map();\r\n // 어시 typing 자동 해제 시한 (ms) — LLM 호출 최대 시간 (Sonnet 5-10초) + 부수 작업 + 여유.\r\n // 운영 환경 LLM 응답 지연 분포에 따라 조정 가능.\r\n this._assistantTypingTimeoutMs = 30000;\r\n // 어시 typing 의 generic sender 식별자 — 서버 ChatMessageSentAssistantListener 의\r\n // ASSISTANT_TYPING_USER_ID 와 정확히 일치해야 함. UI 가 roomId+userId 로 typing state 추적 시\r\n // typing=true / typing=false 가 같은 키로 매칭되도록 일관 유지 필수.\r\n this._assistantTypingUserId = '__assistant__';\r\n this._assistantTypingUserName = 'AI';\r\n\r\n // AI 진행 표시(생각중/검색중/작성중) — assistant-stream 채널의 PHASE/DONE 수신 상태.\r\n // roomId → { streamId, personaId, phase, timer }. typing 과 별개의 가산 레이어(서버가 둘 다 발행).\r\n // 자동 해제 3경로: (1) DONE 수신 (2) 어시 메시지 도착 (3) TTL(expiresAt) 만료 — typing 타이머와 동형.\r\n // 현재는 PM_BACKSTAGE(방당 단일 PM) 만 phase 를 발행하므로 방 단위 1개 진행으로 추적한다.\r\n this._assistantProgress = new Map();\r\n // phase → 기본 표시 라벨(설계 §2 — 서버는 의미만, 라벨 텍스트는 SDK 매핑). 소비자는 payload.phase 로 커스텀 가능.\r\n this._assistantPhaseLabels = {\r\n THINKING: '생각 중…',\r\n SEARCHING: '검색 중…',\r\n PLANNING: '기획 중…',\r\n WRITING: '작성 중…'\r\n };\r\n // PHASE 의 expiresAt 누락/이상 시 fallback TTL (ms) — 서버 ttl(최소 30s) 보다 약간 길게.\r\n this._assistantProgressFallbackTtlMs = 35000;\r\n\r\n // 메시지 dedup 보호 — 같은 messageId 의 중복 수신 차단.\r\n // 서버측 멱등 race 복구 / 네트워크 재전송 / WebSocket 재연결 직후 등 다양한 중복 시나리오 커버.\r\n //\r\n // chat WebSocket (MESSAGE_CREATED) 와 roomList (MESSAGE_RECEIVED) 는 같은 messageId 의 별개 이벤트라\r\n // 각각 다른 키 공간 사용. 정상 흐름에서 두 이벤트가 거의 동시에 도착하므로 키를 공유하면 둘 중\r\n // 하나가 차단되는 부작용 발생.\r\n //\r\n // 구조: roomId → insertion-order Map<messageId, true> (LRU). size 초과 시 가장 오래된 항목 제거.\r\n this._seenChatMessageIdsByRoom = new Map();\r\n this._seenRoomListMessageIdsByRoom = new Map();\r\n // 방 하나당 최근 N개의 messageId 만 dedup 대상 — 정상 흐름에서 충돌 가능성 없는 적정 크기.\r\n this._maxSeenPerRoom = 200;\r\n }\r\n\r\n /**\r\n * 방별 LRU dedup 체크 — 이미 본 messageId 면 {@code true} 반환 (중복).\r\n * 처음 보는 messageId 면 기록 후 {@code false} 반환.\r\n *\r\n * <p>Map 의 insertion-order 보존 특성을 이용한 가벼운 LRU. 외부 lib 없음.</p>\r\n *\r\n * @private\r\n * @param {Map<string, Map<string, true>>} bucket - 키 공간 (chat / roomList 별도)\r\n * @param {string} roomId\r\n * @param {string} messageId\r\n * @returns {boolean} 이미 본 메시지면 true (호출자가 무시)\r\n */\r\n _shouldDedupMessage(bucket, roomId, messageId) {\r\n if (!roomId || !messageId) return false;\r\n let seen = bucket.get(roomId);\r\n if (!seen) {\r\n seen = new Map();\r\n bucket.set(roomId, seen);\r\n }\r\n if (seen.has(messageId)) {\r\n return true;\r\n }\r\n seen.set(messageId, true);\r\n if (seen.size > this._maxSeenPerRoom) {\r\n // 가장 오래된 키 제거 (Map insertion-order 의 첫 항목).\r\n const oldestKey = seen.keys().next().value;\r\n seen.delete(oldestKey);\r\n }\r\n return false;\r\n }\r\n\r\n // ==================== REST API ====================\r\n\r\n /**\r\n * 내 채팅방 목록 조회\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기 (1-100)\r\n * @param {string} [params.lastId] - 마지막 채팅방 ID (커서)\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값 (epoch millis)\r\n * @param {string} [params.type] - 채팅방 타입 필터 ('DIRECT' | 'GROUP'). 생략 시 전체.\r\n * 'GROUP' 은 \"그룹 탭\" 시맨틱 — PRIVATE_GROUP/TEAM 을 포함한 그룹 전체를 반환\r\n * (PRIVATE_GROUP/TEAM 값을 보내도 동일하게 그룹 전체 — 타입별 정확 필터 아님).\r\n * 특정 타입만 필요하면 응답의 {@code roomType} 필드로 클라이언트에서 거른다.\r\n * @returns {Promise<Object>}\r\n */\r\n async getRooms(params = {}) {\r\n const { size = 50, lastId, lastSortValue, type } = params;\r\n return this.apiClient.get('/api/v1/rooms/my', { size, lastId, lastSortValue, type });\r\n }\r\n\r\n /**\r\n * 채팅방 입장 (상세 조회).\r\n *\r\n * <p>서버 {@code markAllAsRead} 를 트리거하므로 방 안의 누적 unread 가 전부 읽음 처리된다.\r\n * 실시간 읽음 이벤트 보정 수신이 필요하면 {@link #enterRoom} 사용을 권장 — subscribe 를\r\n * {@link #getRoom} 보다 먼저 수행해서 \"응답 후, 구독 전\" 구간의 read 이벤트 유실을 막는다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async getRoom(roomId) {\r\n return this.apiClient.get(`/api/v1/rooms/${roomId}`);\r\n }\r\n\r\n /**\r\n * 채팅방 진입 — <b>권장 진입 경로</b>.\r\n *\r\n * <p>순서를 고정해서 race-free 진입을 보장:</p>\r\n * <ol>\r\n * <li><b>subscribeRoom</b> — WebSocket 구독 먼저. 서버가 발행하는 read/message/typing 이벤트의\r\n * 도달을 이 시점부터 보장.</li>\r\n * <li><b>setActiveRoom</b> — 이후 수신되는 신규 메시지 자동 읽음 처리 활성화.</li>\r\n * <li><b>getRoom</b> — 서버 {@code markAllAsRead} + 초기 메시지 50개 fetch. 이 시점에 서버가\r\n * 발행하는 read 이벤트는 (1) 의 구독으로 모두 수신됨.</li>\r\n * </ol>\r\n *\r\n * <p>구독이 {@code getRoom} 보다 먼저 완료돼야 \"서버에서 발행된 read 이벤트가 아직 구독 안 된\r\n * 클라에 유실\" 되는 케이스가 제거된다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>} {@link #getRoom} 의 응답 (방 상세 + 초기 메시지)\r\n *\r\n * @example\r\n * // 방 클릭 시\r\n * const detail = await client.chat.enterRoom('room-id');\r\n * renderRoom(detail);\r\n */\r\n async enterRoom(roomId) {\r\n // \"이 호출이 새로 만든 구독인지\" 기억 — getRoom 실패 시 rollback 시에 이전 구독은 유지.\r\n const wasAlreadySubscribed = this.subscribedRooms.has(roomId);\r\n\r\n // 1) WebSocket 구독을 먼저 — 이후 서버 read 이벤트 유실 방지\r\n if (!wasAlreadySubscribed) {\r\n await this.subscribeRoom(roomId);\r\n }\r\n // 2) 자동 읽음 처리 활성 방 지정\r\n this.setActiveRoom(roomId);\r\n\r\n try {\r\n // 3) 서버 입장 API — markAllAsRead + 초기 메시지 조회\r\n return await this.getRoom(roomId);\r\n } catch (e) {\r\n // getRoom 실패 시 half-open 상태 방지 — 이 호출이 켠 리소스만 되돌림.\r\n if (this.getActiveRoom() === roomId) {\r\n this.clearActiveRoom();\r\n }\r\n if (!wasAlreadySubscribed) {\r\n this.unsubscribeRoom(roomId);\r\n }\r\n throw e;\r\n }\r\n }\r\n\r\n /**\r\n * 채팅방 정보 조회 (입장하지 않고 정보만)\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async getRoomInfo(roomId) {\r\n return this.apiClient.get(`/api/v1/rooms/${roomId}/info`);\r\n }\r\n\r\n /**\r\n * 내 방 언어 설정 (다국어). 이 방에서 내가 볼 언어를 지정한다.\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string|null} language - BCP-47 코드(예 'ko','en','ja') / 'OFF'(원문 보기) /\r\n * null·''(오버라이드 해제 → 사용자 기본 언어로 복귀)\r\n * @returns {Promise<Object>} 갱신된 방 상세\r\n */\r\n async setMyRoomLanguage(roomId, language) {\r\n return this.apiClient.put(`/api/v1/rooms/${roomId}/my-language`, { language });\r\n }\r\n\r\n /**\r\n * 1:1 채팅방 생성/조회\r\n * @param {string} friendId - 상대방 사용자 ID (projectUserId)\r\n * @returns {Promise<Object>}\r\n */\r\n async createOneToOneRoom(friendId) {\r\n return this.apiClient.post(`/api/v1/rooms/direct/${friendId}`);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방 생성.\r\n *\r\n * <p>방 타입 {@code GROUP} (공개) / {@code PRIVATE_GROUP} (비밀) / {@code TEAM} (팀) 지정 가능.\r\n * 비밀방인 경우 {@code password} 필수 (서버가 bcrypt 해시로 저장). 팀방은 비밀번호 금지 —\r\n * 초대 전용이라 비밀번호 개념이 없고, 멤버는 입장 시점과 무관하게 방 전체 히스토리를 본다.</p>\r\n *\r\n * <p>{@code invitedAssistantPersonaIds} 로 AI 어시스턴트 페르소나도 함께 추가 가능.\r\n * 페르소나 ID 는 {@link #getAssistants} 로 사전 조회. 서버가 해당 프로젝트에서 사용 가능한\r\n * 페르소나인지 검증 (글로벌 또는 자체/오버라이드). 활성 페르소나만 허용.</p>\r\n *\r\n * @param {Object} data\r\n * @param {string} data.roomName - 채팅방 이름 (필수, max 100자)\r\n * @param {string} [data.description] - 채팅방 설명 (max 500자)\r\n * @param {string[]} [data.invitedUserIds] - 초대할 사용자 ID 배열 (max 50명)\r\n * @param {string[]} [data.invitedAssistantPersonaIds] - 추가할 어시스턴트 ID 배열 (max 10개).\r\n * {@link #getAssistants} 로 조회한 어시스턴트의 {@code id} 를 전달.\r\n * @param {string} [data.roomType='GROUP'] - 방 타입 ('GROUP' | 'PRIVATE_GROUP' | 'TEAM'). 기본값 'GROUP'.\r\n * 'TEAM' = 초대 전용(직접 입장 불가) + 멤버 전체 히스토리 열람 + 비참여자에게 목록 비노출.\r\n * @param {string} [data.password] - 비밀번호 (roomType='PRIVATE_GROUP' 일 때 필수, 4~50자.\r\n * 'GROUP'/'TEAM' 에는 금지)\r\n * @param {number} [data.messageRetentionHours] - 메시지 보관 기간 (시간, 1~8760). 미설정 시 무제한.\r\n * 설정 시 메시지를 삭제하지 않고 조회 시 기간 필터링만 적용. 변경 시 즉시 반영.\r\n * @param {string} [data.assistantMode='GENERAL'] - 어시스턴트 참여 모드\r\n * ('GENERAL' | 'PEOPLE_ONLY' | 'CALL_ONLY'). 생략 시 서버 기본값 GENERAL.\r\n * @param {string} [data.roomAiType] - 방 AI 사용 방식 intent ('NONE' | 'PERSONA_MULTI' | 'PM_BACKSTAGE').\r\n * 생략 시 서버가 API 키 권한 + 첨부 페르소나로 파생 (레거시 호환). {@code 'NONE'} 이면\r\n * {@code invitedAssistantPersonaIds}/{@code assistantMode} 동시 지정 불가 (서버 400).\r\n * {@code 'PM_BACKSTAGE'} 는 PM 자동부착 — persona/assistantMode 대신 {@code engagementIntensity} 로 제어.\r\n * 권한 없는 값 지정 시 403.\r\n * <b>주의(함정):</b> PERSONA_MULTI 권한이 없는 PM 전용 키는 {@code roomAiType} 을 생략하면 서버 파생이\r\n * {@code 'NONE'}(사람 방)으로 떨어진다 (PM_BACKSTAGE 로 자동 승격되지 않음). {@link #getRoomAiMeta} 의\r\n * {@code availableRoomAiTypes} 로 확인한 타입을 반드시 명시 전송할 것.\r\n * @param {string} [data.engagementIntensity] - PM 개입 강도 ('QUIET' | 'NORMAL' | 'ACTIVE').\r\n * {@code 'PM_BACKSTAGE'} 방 전용 — 그 외 타입에 지정 시 서버 거절. 생략 시 서버 기본값 'NORMAL'.\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // 공개 그룹방\r\n * await chat.createGroupRoom({\r\n * roomName: '개발팀 잡담방',\r\n * invitedUserIds: ['user-1', 'user-2']\r\n * });\r\n *\r\n * @example\r\n * // 어시스턴트 포함 그룹방\r\n * const assistants = await chat.getAssistants();\r\n * // UI 에 표시하고 사용자가 선택한 id 들을 그대로 전달\r\n * await chat.createGroupRoom({\r\n * roomName: '계약 검토방',\r\n * invitedUserIds: ['user-1', 'user-2'],\r\n * invitedAssistantPersonaIds: [/* 사용자가 선택한 id 배열 *\\/],\r\n * assistantMode: 'GENERAL'\r\n * });\r\n *\r\n * @example\r\n * // 비밀 그룹방\r\n * await chat.createGroupRoom({\r\n * roomName: '비공개 회의실',\r\n * invitedUserIds: ['user-1'],\r\n * roomType: ChatRoomType.PRIVATE_GROUP,\r\n * password: 'secret1234'\r\n * });\r\n *\r\n * @example\r\n * // 팀 채팅방 — 초대 전용 + 전체 히스토리 (중간에 들어와도 이전 대화 열람)\r\n * await chat.createGroupRoom({\r\n * roomName: '백엔드팀',\r\n * invitedUserIds: ['user-1', 'user-2'],\r\n * roomType: ChatRoomType.TEAM\r\n * });\r\n */\r\n async createGroupRoom(data) {\r\n const roomType = data.roomType || ChatRoomType.GROUP;\r\n\r\n // 클라이언트 측 사전 검증 (서버에서도 재검증됨)\r\n if (roomType === ChatRoomType.PRIVATE_GROUP) {\r\n if (!data.password || data.password.length < 4) {\r\n throw new Error('PRIVATE_GROUP requires a password of at least 4 characters');\r\n }\r\n } else if (roomType === ChatRoomType.GROUP) {\r\n if (data.password) {\r\n throw new Error('Public GROUP rooms cannot have a password');\r\n }\r\n } else if (roomType === ChatRoomType.TEAM) {\r\n if (data.password) {\r\n throw new Error('TEAM rooms cannot have a password (invite-only)');\r\n }\r\n }\r\n\r\n return this.apiClient.post('/api/v1/rooms/group', {\r\n roomName: data.roomName,\r\n description: data.description,\r\n invitedUserIds: data.invitedUserIds,\r\n roomType,\r\n password: data.password,\r\n messageRetentionHours: data.messageRetentionHours,\r\n invitedAssistantPersonaIds: data.invitedAssistantPersonaIds,\r\n assistantMode: data.assistantMode,\r\n roomAiType: data.roomAiType,\r\n engagementIntensity: data.engagementIntensity\r\n });\r\n }\r\n\r\n /**\r\n * 사용 가능한 어시스턴트 리스트 조회.\r\n *\r\n * <p>받은 리스트를 UI 에 표시하고, 사용자가 선택한 어시스턴트의 {@code id} 를\r\n * {@link #createGroupRoom} 의 {@code invitedAssistantPersonaIds} 로 전달.</p>\r\n *\r\n * @returns {Promise<Array<{id: string, displayName: string, mentionKey: string, role: string}>>}\r\n *\r\n * @example\r\n * const assistants = await chat.getAssistants();\r\n * // [{ id, displayName, mentionKey, role }, ...]\r\n */\r\n async getAssistants() {\r\n const response = await this.apiClient.get('/api/v1/assistants');\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 현재 API 키로 생성 가능한 방 AI 타입 조회 (discovery).\r\n *\r\n * <p>방 생성 UI 에서 {@link #createGroupRoom} 의 {@code roomAiType} 으로 만들 수 있는 타입만 노출하는 데 사용한다.\r\n * 키 권한(capability)으로 결정 — {@code 'NONE'}(사람 채팅)은 항상, {@code 'PERSONA_MULTI'}/{@code 'PM_BACKSTAGE'}\r\n * 는 권한 보유 시 포함.</p>\r\n *\r\n * <p><b>중요:</b> 여기서 고른 타입을 {@link #createGroupRoom} 의 {@code roomAiType} 으로 <b>반드시 명시 전송</b>해야 한다.\r\n * 생략하면 서버가 키 권한 + persona 로 파생하는데, PERSONA_MULTI 권한이 없는 PM 전용 키는 {@code 'NONE'}(사람 방)으로\r\n * 떨어진다 (PM_BACKSTAGE 자동 승격 없음).</p>\r\n *\r\n * @returns {Promise<{availableRoomAiTypes: string[]}>}\r\n *\r\n * @example\r\n * const meta = await chat.getRoomAiMeta();\r\n * // meta.availableRoomAiTypes 예: ['NONE', 'PM_BACKSTAGE']\r\n * // → UI 에서 이 타입들만 방 생성 옵션으로 노출, 고른 타입을 createGroupRoom 의 roomAiType 으로 명시 전송\r\n */\r\n async getRoomAiMeta() {\r\n const response = await this.apiClient.get('/api/v1/rooms/ai-meta');\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 어시스턴트 메시지 평점 등록 (1~5점).\r\n *\r\n * <p>AI 어시스턴트가 생성한 메시지({@code senderType === 'ASSISTANT'})에 대해서만 의미가 있다.\r\n * UI 는 어시 메시지이면서 삭제되지 않은 메시지에만 평점 버튼을 노출하는 것을 권장한다\r\n * (서버도 사람 메시지 / 삭제 메시지는 거부).</p>\r\n *\r\n * <p>같은 메시지를 다시 평가하면 갱신된다 (단, 서버에서 해당 평점이 아직 검토 전일 때만).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 평가할 어시스턴트 메시지의 {@code messageId}\r\n * @param {number} rating - 평점 (1~5 정수)\r\n * @param {string} [comment] - 자유 코멘트 (max 2000자)\r\n * @returns {Promise<void>}\r\n *\r\n * @example\r\n * await chat.rateAssistantMessage(roomId, msg.messageId, 5, '정확한 요약이었어요');\r\n */\r\n async rateAssistantMessage(roomId, messageId, rating, comment) {\r\n // 클라이언트 측 사전 검증 (서버에서도 @Min(1)@Max(5) 재검증됨)\r\n if (!Number.isInteger(rating) || rating < 1 || rating > 5) {\r\n throw new Error('rating must be an integer between 1 and 5');\r\n }\r\n\r\n await this.apiClient.post(\r\n `/api/v1/chat-messages/${roomId}/messages/${messageId}/rating`,\r\n { rating, comment }\r\n );\r\n }\r\n\r\n /**\r\n * 어시스턴트로 대화 요약 (버튼 진입점, 동기).\r\n *\r\n * <p>지정한 페르소나가 최근 메시지 범위를 요약해 방에 어시스턴트 메시지로 broadcast 한다.\r\n * HTTP 응답은 LLM 처리(수 초) 후 결과를 반환하며, 생성된 메시지는 일반 {@code chatMessage}\r\n * 이벤트로도 수신된다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} options\r\n * @param {string} options.personaId - 요약을 수행할 어시스턴트 ID ({@link #getAssistants} 의 {@code id}). 필수.\r\n * @param {string} [options.format] - 요약 포맷 ({@link SummarizeFormat}). 미지정 시 서버 기본값 {@code SHORT}.\r\n * @param {number} [options.messageCount] - 요약 대상 최근 메시지 수 (1~50). 미지정 시 서버 기본값.\r\n * @returns {Promise<{outcome: string, messageId: (string|null), detail: (string|null)}>}\r\n * {@code outcome}: {@code 'EMITTED'} (생성됨, {@code messageId} 존재) |\r\n * {@code 'SKIPPED'} / {@code 'FAILED'} ({@code detail} 에 사유).\r\n *\r\n * @example\r\n * const result = await chat.summarizeWithAssistant(roomId, {\r\n * personaId,\r\n * format: SummarizeFormat.MINUTES,\r\n * messageCount: 30\r\n * });\r\n * if (result.outcome !== 'EMITTED') console.warn(result.detail);\r\n */\r\n async summarizeWithAssistant(roomId, options = {}) {\r\n const { personaId, format, messageCount } = options;\r\n if (!personaId) {\r\n throw new Error('summarizeWithAssistant requires options.personaId');\r\n }\r\n\r\n const response = await this.apiClient.post(\r\n `/api/v1/rooms/${roomId}/assistant/tools/summarize`,\r\n { personaId, format, messageCount }\r\n );\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 어시스턴트로 특정 메시지 번역 (버튼 진입점, 동기).\r\n *\r\n * <p>지정한 페르소나가 {@code sourceMessageId} 메시지를 {@code targetLang} 으로 번역해\r\n * 방에 어시스턴트 메시지로 broadcast 한다. 생성된 메시지는 일반 {@code chatMessage}\r\n * 이벤트로도 수신된다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} options\r\n * @param {string} options.personaId - 번역을 수행할 어시스턴트 ID ({@link #getAssistants} 의 {@code id}). 필수.\r\n * @param {string} options.sourceMessageId - 번역 대상 메시지의 {@code messageId}. 필수.\r\n * @param {string} [options.targetLang] - 목표 언어 (예: {@code 'en'}, {@code '日本語'}). 미지정 시 서버 정책.\r\n * @returns {Promise<{outcome: string, messageId: (string|null), detail: (string|null)}>}\r\n * {@code outcome}: {@code 'EMITTED'} (생성됨, {@code messageId} 존재) |\r\n * {@code 'SKIPPED'} / {@code 'FAILED'} ({@code detail} 에 사유).\r\n *\r\n * @example\r\n * const result = await chat.translateWithAssistant(roomId, {\r\n * personaId,\r\n * sourceMessageId: msg.messageId,\r\n * targetLang: 'en'\r\n * });\r\n */\r\n async translateWithAssistant(roomId, options = {}) {\r\n const { personaId, targetLang, sourceMessageId } = options;\r\n if (!personaId) {\r\n throw new Error('translateWithAssistant requires options.personaId');\r\n }\r\n if (!sourceMessageId) {\r\n throw new Error('translateWithAssistant requires options.sourceMessageId');\r\n }\r\n\r\n const response = await this.apiClient.post(\r\n `/api/v1/rooms/${roomId}/assistant/tools/translate`,\r\n { personaId, targetLang, sourceMessageId }\r\n );\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 조회 — PM_BACKSTAGE 방 전용.\r\n *\r\n * <p>방 레이어는 전역 PM 프롬프트(+회사 레이어)에 <b>합성</b>되는 방 단위 추가 지시문이다 (교체 아님).\r\n * 등록된 적이 없으면 404 ({@code PM_PROMPT_LAYER_NOT_FOUND}) — \"아직 없음\" 상태로 처리할 것.</p>\r\n *\r\n * <p>권한: 방 참여자 전체 조회 가능. PM_BACKSTAGE 가 아닌 방이면 400\r\n * ({@code PM_PROMPT_ROOM_TYPE_UNSUPPORTED}).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>} PmPromptLayer — {@code {id, scope:'ROOM', projectId, roomId, content, active, activeVersion, editorType, updatedAt}}\r\n *\r\n * @example\r\n * try {\r\n * const layer = await chat.getRoomPmPrompt(roomId);\r\n * console.log(layer.active, layer.content);\r\n * } catch (e) {\r\n * if (e.status === 404) {\r\n * // 레이어 미등록 — 입력 UI 노출\r\n * }\r\n * }\r\n */\r\n async getRoomPmPrompt(roomId) {\r\n const response = await this.apiClient.get(`/api/v1/rooms/${roomId}/pm-prompt`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 등록/수정 (upsert) — <b>방 주인(OWNER) 전용</b>.\r\n *\r\n * <p>저장 시 새 버전이 자동 기록된다 ({@link #getRoomPmPromptVersions}). 본문 한도 2,000자.\r\n * 비주인 호출은 403 ({@code NOT_ROOM_ADMIN}).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} content - 레이어 본문 (1~2,000자). 방의 목적/맥락/금지사항 등 PM 에게 줄 추가 지시문.\r\n * @returns {Promise<Object>} 저장된 PmPromptLayer\r\n *\r\n * @example\r\n * await chat.upsertRoomPmPrompt(roomId, '이 방은 모바일앱 리뉴얼 TF. 결정사항은 표로 정리해줘.');\r\n */\r\n async upsertRoomPmPrompt(roomId, content) {\r\n if (typeof content !== 'string' || !content.trim()) {\r\n throw new Error('upsertRoomPmPrompt requires non-blank content');\r\n }\r\n if (content.length > 2000) {\r\n throw new Error('upsertRoomPmPrompt content exceeds 2000 characters');\r\n }\r\n\r\n const response = await this.apiClient.put(`/api/v1/rooms/${roomId}/pm-prompt`, { content });\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 합성 복귀 (활성화) — 방 주인(OWNER) 전용.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>} 변경된 PmPromptLayer ({@code active: true})\r\n */\r\n async activateRoomPmPrompt(roomId) {\r\n const response = await this.apiClient.patch(`/api/v1/rooms/${roomId}/pm-prompt/activate`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 합성 제외 (비활성화, soft delete) — 방 주인(OWNER) 전용.\r\n *\r\n * <p>본문/이력은 보존되고 PM 응답 합성에서만 빠진다. {@link #activateRoomPmPrompt} 로 복귀.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>} 변경된 PmPromptLayer ({@code active: false})\r\n */\r\n async deactivateRoomPmPrompt(roomId) {\r\n const response = await this.apiClient.patch(`/api/v1/rooms/${roomId}/pm-prompt/deactivate`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 버전 이력 조회 — 방 참여자 전체.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Array<Object>>} 버전 목록 — 각 항목 {@code {version, content, active, editorType, createdAt}} ({@code active}=현재 활성 버전)\r\n */\r\n async getRoomPmPromptVersions(roomId) {\r\n const response = await this.apiClient.get(`/api/v1/rooms/${roomId}/pm-prompt/versions`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 방 PM 프롬프트 레이어 버전 활성화 — 방 주인(OWNER) 전용.\r\n *\r\n * <p>active 포인터 이동 방식 — 지정 버전을 활성으로 전환하고 라이브 본문을 그 버전으로 투영한다.\r\n * <b>새 버전을 만들지 않는다</b>(버전 수 불변, 글로벌/회사/방 PM 통일 모델). 존재하지 않는\r\n * 버전이면 404 ({@code PM_PROMPT_LAYER_VERSION_NOT_FOUND}).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {number} version - 활성화할 버전 번호 ({@link #getRoomPmPromptVersions} 의 {@code version})\r\n * @returns {Promise<Object>} 활성 버전이 반영된 PmPromptLayer\r\n */\r\n async activateRoomPmPromptVersion(roomId, version) {\r\n if (!Number.isInteger(version) || version < 1) {\r\n throw new Error('activateRoomPmPromptVersion requires a positive integer version');\r\n }\r\n\r\n const response = await this.apiClient.post(\r\n `/api/v1/rooms/${roomId}/pm-prompt/versions/${version}/activate`\r\n );\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * PM 프롬프트 합성 미리보기 — 방 참여자 전체.\r\n *\r\n * <p>전역(+회사)(+방) 레이어가 합성된 최종 SYSTEM 본문을 반환한다. 컨텍스트 정책 등\r\n * 런타임 부착분은 미포함 — 레이어 합성 결과까지만.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<{composedPrompt: string}>}\r\n *\r\n * @example\r\n * const { composedPrompt } = await chat.previewRoomPmPrompt(roomId);\r\n */\r\n async previewRoomPmPrompt(roomId) {\r\n const response = await this.apiClient.get(`/api/v1/rooms/${roomId}/pm-prompt/preview`);\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방 입장.\r\n *\r\n * <p>비밀방 ({@code PRIVATE_GROUP}) 은 최초 입장 시 또는 나갔다 재입장 시 비밀번호 필요.\r\n * 이미 active 참가자라면 비밀번호 없이 재접근 가능 (서버의 fast path).</p>\r\n *\r\n * <p>팀방 ({@code TEAM}) 은 초대 전용 — 직접 입장 시도는 {@code TEAM_ROOM_INVITE_ONLY}(403).\r\n * {@link #inviteToGroupRoom} 으로 초대받아야 입장된다 (이미 멤버면 fast path 멱등 통과).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} [password] - 비밀방 입장 비밀번호 (비밀방 최초/재입장 시 필수)\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // 공개방 입장\r\n * await chat.joinGroupRoom('room-id');\r\n *\r\n * @example\r\n * // 비밀방 입장\r\n * await chat.joinGroupRoom('room-id', 'secret1234');\r\n */\r\n async joinGroupRoom(roomId, password) {\r\n const body = password ? { password } : undefined;\r\n return this.apiClient.post(`/api/v1/rooms/group/${roomId}/join`, body);\r\n }\r\n\r\n /**\r\n * 채팅방 나가기 (구독도 자동 해제됨)\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async leaveRoom(roomId) {\r\n const result = await this.apiClient.post(`/api/v1/rooms/${roomId}/leave`);\r\n this.unsubscribeRoom(roomId);\r\n return result;\r\n }\r\n\r\n /**\r\n * 그룹 채팅방 설정 수정 (방장만).\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} data - 수정할 필드 (null/undefined 이면 변경 안 함)\r\n * @param {string} [data.roomName] - 방 이름\r\n * @param {string} [data.description] - 설명\r\n * @param {string} [data.roomType] - 공개/비공개 전환 ('GROUP' | 'PRIVATE_GROUP').\r\n * 'PRIVATE_GROUP' 전환 시 password 필수, 'GROUP' 전환 시 기존 비번 제거. 생략 시 변경 안 함.\r\n * @param {string} [data.password] - 비밀번호 (PRIVATE_GROUP). 공백만으로는 설정 불가, 4~50자.\r\n * @param {number} [data.messageRetentionHours] - 보관 기간 (시간)\r\n * @param {boolean} [data.anyoneCanInvite] - 누구나 초대 가능 여부\r\n * @param {string} [data.assistantMode] - 어시스턴트 참여 모드\r\n * ('GENERAL' | 'PEOPLE_ONLY' | 'CALL_ONLY'). PERSONA_MULTI 방 전용.\r\n * @param {string} [data.engagementIntensity] - PM 개입 강도 ('QUIET' | 'NORMAL' | 'ACTIVE').\r\n * PM_BACKSTAGE 방 전용 — PM 참여 레벨 변경. 키 PM_BACKSTAGE 권한 필요(없으면 403).\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // PERSONA_MULTI 방 — 어시스턴트 모드 변경\r\n * await chat.updateGroupRoom('room-id', { assistantMode: 'CALL_ONLY' });\r\n *\r\n * @example\r\n * // PM_BACKSTAGE 방 — PM 개입 강도 변경\r\n * await chat.updateGroupRoom('room-id', { engagementIntensity: 'QUIET' });\r\n *\r\n * @example\r\n * // 공개 → 비공개 전환 (비밀번호 필수)\r\n * await chat.updateGroupRoom('room-id', { roomType: 'PRIVATE_GROUP', password: 'secret12' });\r\n *\r\n * @example\r\n * // 비공개 → 공개 전환 (기존 비밀번호 제거됨)\r\n * await chat.updateGroupRoom('room-id', { roomType: 'GROUP' });\r\n */\r\n async updateGroupRoom(roomId, data) {\r\n return this.apiClient.put(`/api/v1/rooms/group/${roomId}`, data);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방에 회원 / 어시스턴트 추가 초대.\r\n *\r\n * <p>두 가지 호출 형식 지원 (backward-compat):</p>\r\n * <ul>\r\n * <li>{@code inviteToGroupRoom(roomId, ['user-1', 'user-2'])} — 회원만 초대 (기존)</li>\r\n * <li>{@code inviteToGroupRoom(roomId, { userIds, assistantPersonaIds })} — 회원 + 어시 (신규)</li>\r\n * </ul>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string[]|{userIds?: string[], assistantPersonaIds?: string[]}} userIdsOrData\r\n * 배열이면 회원 ID 목록, 객체면 {@code userIds} / {@code assistantPersonaIds}.\r\n * 둘 중 하나 이상 필수.\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // 회원만\r\n * await chat.inviteToGroupRoom(roomId, ['user-1', 'user-2']);\r\n *\r\n * @example\r\n * // 회원 + 어시스턴트\r\n * await chat.inviteToGroupRoom(roomId, {\r\n * userIds: ['user-3'],\r\n * assistantPersonaIds: [/* 어시스턴트 id 배열 *\\/]\r\n * });\r\n */\r\n async inviteToGroupRoom(roomId, userIdsOrData) {\r\n const body = Array.isArray(userIdsOrData)\r\n ? { userIds: userIdsOrData }\r\n : {\r\n userIds: userIdsOrData?.userIds,\r\n assistantPersonaIds: userIdsOrData?.assistantPersonaIds\r\n };\r\n return this.apiClient.post(`/api/v1/rooms/group/${roomId}/invite`, body);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방에서 참가자 단순 추방 — 방 관리자(OWNER/ADMIN) 만 호출 가능.\r\n *\r\n * <p>대상은 방에서 제거되지만 재입장 / 재초대 가능. 영구 차단이 필요하면\r\n * {@link #banMember} 사용.</p>\r\n *\r\n * <p>서버 응답:</p>\r\n * <ul>\r\n * <li>400 {@code CANNOT_KICK_SELF} — 자기 자신 추방 시도</li>\r\n * <li>403 {@code NOT_ROOM_ADMIN} — 방장 아님</li>\r\n * <li>404 {@code USER_NOT_IN_ROOM} — 대상이 현재 참가자 아님</li>\r\n * </ul>\r\n *\r\n * @param {string} roomId\r\n * @param {string} userId - 추방할 사용자 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async kickMember(roomId, userId) {\r\n return this.apiClient.delete(`/api/v1/rooms/${roomId}/members/${userId}`);\r\n }\r\n\r\n /**\r\n * 그룹 채팅방에서 참가자 추방 + 영구 차단 — 방 관리자(OWNER/ADMIN) 만 호출 가능.\r\n *\r\n * <p>{@link #kickMember} 와 동일하게 방에서 제거하되 {@code bannedUserIds} 에 추가되어\r\n * {@link #unbanMember} 호출 전까지 재입장 / 재초대가 거부된다. MVP 정책상 현재 또는\r\n * 과거 참가자만 차단 가능 (선제적 차단 불가 — 404 {@code USER_NOT_IN_ROOM}).</p>\r\n *\r\n * @param {string} roomId\r\n * @param {string} userId - 차단할 사용자 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async banMember(roomId, userId) {\r\n return this.apiClient.post(`/api/v1/rooms/${roomId}/banned-members`, { userId });\r\n }\r\n\r\n /**\r\n * 영구 차단 해제 — 방 관리자(OWNER/ADMIN) 만 호출 가능.\r\n *\r\n * <p>{@code bannedUserIds} 에서 제거만 수행. 실제 재입장은 별도 초대 / join 호출 필요.</p>\r\n *\r\n * @param {string} roomId\r\n * @param {string} userId - 해제할 사용자 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async unbanMember(roomId, userId) {\r\n return this.apiClient.delete(`/api/v1/rooms/${roomId}/banned-members/${userId}`);\r\n }\r\n\r\n /**\r\n * 방의 영구 차단 사용자 목록 조회 — 방 관리자(OWNER/ADMIN) 만 호출 가능.\r\n *\r\n * <p>응답 각 item: {@code { userId, nickname, profileUrl }}.\r\n * 일반 참가자가 호출 시 403 {@code NOT_ROOM_ADMIN}.</p>\r\n *\r\n * @param {string} roomId\r\n * @returns {Promise<Array<{userId: string, nickname: string, profileUrl: string}>>}\r\n */\r\n async getBannedMembers(roomId) {\r\n const response = await this.apiClient.get(`/api/v1/rooms/${roomId}/banned-members`);\r\n return response && typeof response === 'object' && 'data' in response\r\n ? response.data\r\n : response;\r\n }\r\n\r\n /**\r\n * 메시지 고정 (방장만).\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 고정할 메시지 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async pinMessage(roomId, messageId) {\r\n return this.apiClient.post(`/api/v1/rooms/group/${roomId}/pin/${messageId}`);\r\n }\r\n\r\n /**\r\n * 메시지 고정 해제 (방장만).\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async unpinMessage(roomId) {\r\n return this.apiClient.post(`/api/v1/rooms/group/${roomId}/unpin`);\r\n }\r\n\r\n /**\r\n * 이모지 리액션 토글.\r\n * <p>한 사용자는 메시지당 하나의 이모지만 보유.\r\n * 같은 이모지 재전송 시 해제, 다른 이모지 전송 시 교체.</p>\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 메시지 ID\r\n * @param {string} emoji - 이모지 (예: \"👍\", \"😂\")\r\n * @returns {Promise<Object>}\r\n */\r\n async toggleReaction(roomId, messageId, emoji) {\r\n return this.apiClient.post(`/api/v1/chat-messages/${roomId}/messages/${messageId}/reaction`, { emoji });\r\n }\r\n\r\n /**\r\n * 참가 가능한 그룹 채팅방 목록 조회\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기\r\n * @param {string} [params.lastId] - 마지막 채팅방 ID\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값\r\n * @returns {Promise<Object>}\r\n */\r\n async getAvailableGroupRooms(params = {}) {\r\n const { size = 50, lastId, lastSortValue } = params;\r\n return this.apiClient.get('/api/v1/rooms/groups/available', { size, lastId, lastSortValue });\r\n }\r\n\r\n /**\r\n * 모든 그룹 채팅방 목록 조회\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기\r\n * @param {string} [params.lastId] - 마지막 채팅방 ID\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값\r\n * @returns {Promise<Object>}\r\n */\r\n async getAllGroupRooms(params = {}) {\r\n const { size = 50, lastId, lastSortValue } = params;\r\n return this.apiClient.get('/api/v1/rooms/groups/all', { size, lastId, lastSortValue });\r\n }\r\n\r\n /**\r\n * 메시지 목록 조회\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기 (1-100)\r\n * @param {string} [params.lastId] - 마지막 메시지 ID (커서)\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값 (epoch millis)\r\n * @returns {Promise<Object>}\r\n */\r\n async getMessages(roomId, params = {}) {\r\n const { size = 50, lastId, lastSortValue } = params;\r\n const response = await this.apiClient.get(\r\n `/api/v1/chat-messages/${roomId}/list`, { size, lastId, lastSortValue }\r\n );\r\n // REST 와 WebSocket 의 타임스탬프 직렬화가 다르다 (REST=ISO 문자열, WS=epoch number).\r\n // 선언된 타입(number)으로 통일 — 기존 응답 구조(envelope)는 그대로 둔다.\r\n const page = this._unwrapSuccessResponse(response);\r\n if (page && Array.isArray(page.content)) {\r\n page.content.forEach((m) => this._normalizeMessageTimestamps(m));\r\n }\r\n return response;\r\n }\r\n\r\n /**\r\n * 링크 미리보기 조회.\r\n *\r\n * <p>서버가 OG / Twitter Card / HTML fallback 을 파싱해 미리보기 카드를 반환합니다.\r\n * 외부 사이트 차단 등으로 HTML 을 못 가져오면 공개 DNS 로 확인되는 URL 에 한해\r\n * 도메인 + favicon 기반 최소 카드를 반환할 수 있습니다.\r\n * 이 경우 {@code previewSource} 는 {@code FAVICON_FALLBACK} 입니다.\r\n * 미리보기를 만들 수 없는 URL 이면 {@code null} 을 반환합니다.</p>\r\n *\r\n * <p>민감한 URL 이 query string 으로 남지 않도록 POST body 로 호출합니다.</p>\r\n *\r\n * @param {string} url - 미리보기를 조회할 URL\r\n * @returns {Promise<Object|null>} LinkPreview 또는 null\r\n */\r\n async fetchLinkPreview(url) {\r\n const normalizedUrl = typeof url === 'string' ? url.trim() : '';\r\n if (!normalizedUrl) {\r\n throw new Error('url is required');\r\n }\r\n\r\n const response = await this.apiClient.post('/api/v1/link-preview', {\r\n url: normalizedUrl\r\n });\r\n\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 메시지 전송 (REST API).\r\n *\r\n * <p>멱등 POST — SDK 가 내부에서 UUID 를 자동 생성하며, 호출자는 {@code messageId} 를\r\n * 지정할 수 없다. 같은 요청의 네트워크 재시도(SDK 내부 구현 시) 에도 같은 UUID 가 쓰이도록\r\n * 설계된다. 서버 Unique 제약 + pre-check 로 중복 저장이 차단되며, 재시도 히트 시\r\n * {@code duplicate: true} 로 기존 결과가 그대로 반환된다.</p>\r\n *\r\n * <p>파일 분리 시 ({@code separateFiles: true}) 하나의 요청이 여러 메시지로 저장된다.\r\n * 각 메시지는 고유한 서버 {@code messageId} 를 가지며 응답의 {@code messages} 배열로 전달된다.</p>\r\n *\r\n * <p>답글: {@code data.replyToMessageId} 에 원본 메시지의 서버 {@code messageId} 를 지정.\r\n * 서버가 원본을 조회해서 snapshot 을 생성 후 저장하며, 응답/수신 메시지의 {@code replyTo}\r\n * 필드에 원본 요약이 포함됨. 원본이 존재하지 않으면 서버가 400 (REPLY_TARGET_NOT_FOUND) 반환.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} data\r\n * @param {string} [data.message] - 메시지 내용 (message 또는 fileInfos 중 하나 필수)\r\n * @param {Object[]} [data.fileInfos] - 파일 메타데이터 배열\r\n * @param {boolean} [data.separateFiles=true] - 파일 분리 여부\r\n * @param {string} [data.replyToMessageId] - 답글 대상 원본 메시지 ID (옵션)\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n * - {@code duplicate}: 멱등 재시도로 기존 결과를 반환한 경우 {@code true}\r\n * - {@code messages}: 저장된 메시지 배열 (파일 분리 시 여러 개). 각 원소의 {@code messageId} 는 서버가 부여한 고유 ID.\r\n *\r\n * @example\r\n * // 일반 메시지\r\n * const { messages } = await chat.sendMessage('room-id', { message: '안녕하세요' });\r\n * console.log(messages[0].messageId); // 서버가 생성한 ID — 답글/삭제/리액션에 사용\r\n *\r\n * @example\r\n * // 답글\r\n * await chat.sendMessage('room-id', {\r\n * message: '네 동감이에요',\r\n * replyToMessageId: 'original-msg-id'\r\n * });\r\n *\r\n * @example\r\n * // 파일 분리 전송 — messages 배열에 텍스트 + 파일별 메시지가 모두 담김\r\n * const { messages } = await chat.sendMessage('room-id', {\r\n * message: '사진 보냅니다',\r\n * fileInfos: [f0, f1, f2],\r\n * separateFiles: true\r\n * });\r\n * // messages.length === 4 (텍스트 1 + 이미지 3)\r\n */\r\n async sendMessage(roomId, data) {\r\n const messageId = this._generateMessageId();\r\n return this._sendMessageWithId(roomId, messageId, data);\r\n }\r\n\r\n /**\r\n * 메시지 전송 — Optimistic UI 버전.\r\n *\r\n * <p>{@link #sendMessage} 와 동일하게 서버에 메시지를 전송하지만, 호출 즉시\r\n * SDK 가 생성한 {@code baseMessageId} 와 응답 {@code messages} 배열과 1-1 매칭되는\r\n * 예측 {@code messageIds} 배열을 동기 반환한다. 호출자는 promise 가 resolve 되기 전에\r\n * 미리 placeholder 메시지를 UI 에 그려둘 수 있고, resolve 후 같은 ID 로 안전하게 교체할 수 있다.</p>\r\n *\r\n * <p>{@code messageIds} 는 서버의 messageId derivation 규칙 (텍스트=base, 파일=base#N) 을\r\n * SDK 가 mirror 하여 사전 계산한 값이다. 정상 케이스에서는 응답\r\n * {@code messages.map(m => m.messageId)} 와 길이/순서가 정확히 일치한다.</p>\r\n *\r\n * <p><b>분류 규칙 drift 방어</b>: 서버 파일 분류 규칙이 변경되어 예측이 어긋나면\r\n * SDK 가 warn 로그를 남기며, 이 경우 응답의 실제 {@code messageId} 로 reconcile 해야 한다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} data - {@link #sendMessage} 와 동일한 페이로드\r\n * @returns {{baseMessageId: string, messageIds: string[], promise: Promise<{duplicate: boolean, messages: Object[]}>}}\r\n *\r\n * @example\r\n * const { messageIds, promise } = chat.sendMessageOptimistic('room-id', {\r\n * message: '안녕하세요'\r\n * });\r\n * messageIds.forEach(id => appendMessage({ messageId: id, status: 'sending' }));\r\n * try {\r\n * const { messages } = await promise;\r\n * messages.forEach(m => replaceMessage(m.messageId, m));\r\n * } catch (e) {\r\n * messageIds.forEach(id => markMessageFailed(id, e));\r\n * }\r\n */\r\n sendMessageOptimistic(roomId, data) {\r\n const baseMessageId = this._generateMessageId();\r\n const messageIds = this._predictMessageIdsFromData(baseMessageId, data);\r\n\r\n const promise = this._sendMessageWithId(roomId, baseMessageId, data)\r\n .then((result) => this._attachOptimisticMetadata(messageIds, result));\r\n\r\n return { baseMessageId, messageIds, promise };\r\n }\r\n\r\n /**\r\n * messageId 를 외부에서 주입받아 실제 send API 를 호출하는 내부 메서드.\r\n * <p>{@link #sendMessage} 와 {@link #sendMessageOptimistic} 가 공유하는 단일 코어 — base messageId 를\r\n * 양쪽이 같은 값으로 사용해야 optimistic 핸들의 예측 ID 가 서버 저장 ID 와 일치한다.</p>\r\n *\r\n * @private\r\n * @param {string} roomId\r\n * @param {string} messageId - SDK 가 생성한 base messageId (UUID)\r\n * @param {Object} data\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n */\r\n async _sendMessageWithId(roomId, messageId, data) {\r\n // 메시지 전송 시 타이핑 표시 자동 중단\r\n this.stopTyping(roomId);\r\n\r\n // separateFiles 는 SDK 가 명시 전송 — 서버 default 가 변경되어도 SDK 의 prediction\r\n // 가정 (`undefined === true`) 과 wire 값이 어긋나지 않도록 fix.\r\n const response = await this.apiClient.post(`/api/v1/chat-messages/${roomId}/send`, {\r\n messageId,\r\n message: data.message,\r\n fileInfos: data.fileInfos,\r\n separateFiles: data.separateFiles !== false,\r\n replyToMessageId: data.replyToMessageId\r\n });\r\n\r\n const result = this._unwrapSuccessResponse(response);\r\n if (result && Array.isArray(result.messages)) {\r\n result.messages.forEach((m) => this._normalizeMessageTimestamps(m));\r\n }\r\n return result;\r\n }\r\n\r\n /**\r\n * 텍스트 메시지 전송 (간편 버전).\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} message - 메시지 내용\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n *\r\n * @example\r\n * const { messages } = await chat.sendTextMessage('room-id', '안녕하세요');\r\n * console.log(messages[0].messageId);\r\n */\r\n async sendTextMessage(roomId, message) {\r\n return this.sendMessage(roomId, { message });\r\n }\r\n\r\n /**\r\n * 텍스트 메시지 전송 — Optimistic UI 버전.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} message - 메시지 내용\r\n * @returns {{baseMessageId: string, messageIds: string[], promise: Promise<{duplicate: boolean, messages: Object[]}>}}\r\n *\r\n * @example\r\n * const { baseMessageId, promise } = chat.sendTextMessageOptimistic('room-id', '안녕');\r\n * appendMessage({ messageId: baseMessageId, content: '안녕', status: 'sending' });\r\n * const { messages } = await promise;\r\n * replaceMessage(baseMessageId, messages[0]);\r\n */\r\n sendTextMessageOptimistic(roomId, message) {\r\n return this.sendMessageOptimistic(roomId, { message });\r\n }\r\n\r\n /**\r\n * 채팅 첨부 파일 업로드 (Low-level).\r\n *\r\n * <p>파일 바이트를 서버로 업로드하고 서버가 생성한 {@code FileMetaData} 를 반환.\r\n * 반환값은 {@link #sendMessage} 의 {@code fileInfos} 에 그대로 넣어 전송.</p>\r\n *\r\n * <p>업로드와 메시지 전송을 분리 호출해야 하는 케이스 (업로드 후 프리뷰 표시 →\r\n * 사용자가 전송 버튼 클릭 시 메시지 전송 등) 에 사용. 한 번에 처리하려면\r\n * {@link #sendFileMessage} 가 편리.</p>\r\n *\r\n * @param {string} roomId - 업로드 대상 채팅방 ID (권한 검증 + S3 namespace)\r\n * @param {File|Blob} file - 업로드할 파일\r\n * @param {Object} [options]\r\n * @param {Function} [options.onProgress] - {@code ({loaded, total, percent}) => void} — 진행률 콜백\r\n * @param {AbortSignal} [options.signal] - 업로드 취소용 AbortSignal\r\n * @returns {Promise<Object>} FileMetaData (fileId/fileUrl/fileName/fileType/fileSize/imageInfo/videoInfo/audioInfo/documentInfo)\r\n *\r\n * @example\r\n * const meta = await chat.uploadFile('room-id', fileInput.files[0], {\r\n * onProgress: ({ percent }) => console.log(`${percent}%`)\r\n * });\r\n * await chat.sendMessage('room-id', {\r\n * fileInfos: [meta],\r\n * message: '첨부합니다'\r\n * });\r\n */\r\n async uploadFile(roomId, file, options = {}) {\r\n if (!roomId) {\r\n throw new Error('roomId is required');\r\n }\r\n if (!file) {\r\n throw new Error('file is required');\r\n }\r\n\r\n const response = await this.apiClient.upload(\r\n `/api/v1/files/${roomId}/upload`,\r\n file,\r\n {\r\n onProgress: options.onProgress,\r\n signal: options.signal\r\n }\r\n );\r\n\r\n // 서버 SuccessResponse 래퍼 안의 data 가 FileMetaData\r\n return this._unwrapSuccessResponse(response);\r\n }\r\n\r\n /**\r\n * 파일 업로드 + 메시지 전송 통합 (High-level).\r\n *\r\n * <p>내부적으로:</p>\r\n * <ol>\r\n * <li>각 파일을 {@link #uploadFile} 로 업로드 (배열이면 병렬)</li>\r\n * <li>수신된 {@code FileMetaData} 들을 모아 {@link #sendMessage} 로 전송</li>\r\n * </ol>\r\n *\r\n * <p>업로드 중 하나라도 실패하면 전체 rejected — 이미 업로드된 S3 객체는\r\n * 고아로 남으며 라이프사이클 정책으로 정리됨 (설계 결정).</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {File|Blob|Array<File|Blob>} files - 단일 파일 또는 파일 배열\r\n * @param {Object} [options]\r\n * @param {string} [options.message] - 함께 보낼 텍스트 본문\r\n * @param {boolean} [options.separateFiles] - 파일별 개별 메시지 분리 여부 (서버 설정)\r\n * @param {string} [options.replyToMessageId] - 답글 대상 원본 메시지의 서버 {@code messageId}\r\n * @param {Function} [options.onUploadProgress] - {@code ({loaded,total,percent,fileIndex}) => void} — 파일별 진행률\r\n * @param {AbortSignal} [options.signal] - 취소 시그널 — 업로드 중에만 유효\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n *\r\n * @example\r\n * // 단일 파일\r\n * await chat.sendFileMessage('room-id', file, {\r\n * message: '첨부',\r\n * onUploadProgress: ({ percent }) => console.log(`${percent}%`)\r\n * });\r\n *\r\n * @example\r\n * // 다중 파일 (병렬 업로드)\r\n * await chat.sendFileMessage('room-id', [file1, file2, file3], {\r\n * message: '여러 파일',\r\n * onUploadProgress: ({ fileIndex, percent }) =>\r\n * console.log(`파일 ${fileIndex}: ${percent}%`)\r\n * });\r\n */\r\n async sendFileMessage(roomId, files, options = {}) {\r\n const fileArray = this._normalizeFileArray(files);\r\n const metas = await this._uploadFiles(roomId, fileArray, options);\r\n const fileInfos = this._applyFileMetadata(metas, options.metadata);\r\n\r\n return this.sendMessage(roomId, {\r\n message: options.message,\r\n fileInfos,\r\n separateFiles: options.separateFiles,\r\n replyToMessageId: options.replyToMessageId\r\n });\r\n }\r\n\r\n /**\r\n * 파일 업로드 + 메시지 전송 — Optimistic UI 버전.\r\n *\r\n * <p>호출 즉시 {@code baseMessageId} 와 응답 {@code messages} 와 1-1 매칭되는 예측\r\n * {@code messageIds} 를 동기 반환한다. 호출자는 업로드 시작 전에 placeholder 메시지를\r\n * UI 에 그릴 수 있다.</p>\r\n *\r\n * <p><b>입력 검증은 동기 throw</b>: 빈 배열 / max 초과 등 {@link #_normalizeFileArray} 의\r\n * 검증 실패는 promise 가 만들어지기 전에 즉시 throw 되므로, 잘못된 입력에 대해 placeholder 가\r\n * 먼저 그려지는 일은 없다.</p>\r\n *\r\n * <p><b>업로드 실패 처리</b>: 업로드 단계에서 실패하면 promise 가 reject 된다. 호출자는 UI 의\r\n * placeholder 들을 모두 실패 상태로 표시하고, S3 에 부분 업로드된 객체는 라이프사이클 정책으로 정리된다.</p>\r\n *\r\n * @param {string} roomId\r\n * @param {File|Blob|Array<File|Blob>} files\r\n * @param {Object} [options] - {@link #sendFileMessage} 와 동일\r\n * @returns {{baseMessageId: string, messageIds: string[], promise: Promise<{duplicate: boolean, messages: Object[]}>}}\r\n *\r\n * @example\r\n * const { messageIds, promise } = chat.sendFileMessageOptimistic('room-id', files, {\r\n * message: '사진 보냅니다'\r\n * });\r\n * messageIds.forEach((id, i) => appendMessage({\r\n * messageId: id,\r\n * localFile: files[i] || null,\r\n * status: 'uploading'\r\n * }));\r\n * try {\r\n * const { messages } = await promise;\r\n * messages.forEach(m => replaceMessage(m.messageId, m));\r\n * } catch (e) {\r\n * messageIds.forEach(id => markMessageFailed(id, e));\r\n * }\r\n */\r\n sendFileMessageOptimistic(roomId, files, options = {}) {\r\n // validation 은 동기 — placeholder 가 그려지기 전에 throw 되어야 함\r\n const fileArray = this._normalizeFileArray(files);\r\n\r\n const baseMessageId = this._generateMessageId();\r\n const messageIds = this._predictMessageIds(\r\n baseMessageId,\r\n this._predictGroupCount(fileArray, options.separateFiles !== false),\r\n this._hasTextMessage(options.message)\r\n );\r\n\r\n const promise = (async () => {\r\n const metas = await this._uploadFiles(roomId, fileArray, options);\r\n const fileInfos = this._applyFileMetadata(metas, options.metadata);\r\n const result = await this._sendMessageWithId(roomId, baseMessageId, {\r\n message: options.message,\r\n fileInfos,\r\n separateFiles: options.separateFiles,\r\n replyToMessageId: options.replyToMessageId\r\n });\r\n return this._attachOptimisticMetadata(messageIds, result);\r\n })();\r\n\r\n return { baseMessageId, messageIds, promise };\r\n }\r\n\r\n /**\r\n * 파일 입력 정규화 — 단일 파일 / 배열 양쪽 허용 + 개수 검증.\r\n * <p>{@link #sendFileMessage} 와 {@link #sendFileMessageOptimistic} 가 공유.</p>\r\n *\r\n * @private\r\n * @param {File|Blob|Array<File|Blob>} files\r\n * @returns {Array<File|Blob>}\r\n * @throws {Error} 빈 배열 또는 MAX_FILES_PER_MESSAGE 초과\r\n */\r\n _normalizeFileArray(files) {\r\n const fileArray = Array.isArray(files) ? files : [files];\r\n if (fileArray.length === 0) {\r\n throw new Error('At least one file is required');\r\n }\r\n if (fileArray.length > MAX_FILES_PER_MESSAGE) {\r\n throw new Error(`A maximum of ${MAX_FILES_PER_MESSAGE} files can be sent in one message`);\r\n }\r\n return fileArray;\r\n }\r\n\r\n /**\r\n * 파일 배열 병렬 업로드 — 각 파일의 진행률은 fileIndex 로 구분.\r\n * <p>{@link #sendFileMessage} 와 {@link #sendFileMessageOptimistic} 가 공유.</p>\r\n *\r\n * @private\r\n * @param {string} roomId\r\n * @param {Array<File|Blob>} fileArray\r\n * @param {Object} options - signal, onUploadProgress 사용\r\n * @returns {Promise<Object[]>} FileMetaData 배열\r\n */\r\n async _uploadFiles(roomId, fileArray, options = {}) {\r\n return Promise.all(\r\n fileArray.map((file, index) =>\r\n this.uploadFile(roomId, file, {\r\n signal: options.signal,\r\n onProgress: options.onUploadProgress\r\n ? (p) => options.onUploadProgress({ ...p, fileIndex: index })\r\n : undefined\r\n })\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 업로드된 FileMetaData 배열에 고객사 pass-through metadata 를 병합.\r\n *\r\n * <p>{@link #sendFileMessage} / {@link #sendFileMessageOptimistic} 가 공유.\r\n * 호출자가 {@code options.metadata} 를 안 주면 metas 가 그대로 반환되어 기존 동작과 100% 호환.</p>\r\n *\r\n * @private\r\n * @param {Object[]} metas - uploadFile 결과의 FileMetaData 배열\r\n * @param {Object|Array<Object|null|undefined>|null|undefined} metadata - 단일 객체(broadcast) 또는 index 별 배열\r\n * @returns {Object[]} metadata 가 병합된 새 배열 (기존 meta 객체는 수정하지 않음)\r\n */\r\n _applyFileMetadata(metas, metadata) {\r\n if (metadata === undefined || metadata === null) {\r\n return metas;\r\n }\r\n\r\n return metas.map((meta, index) => {\r\n const itemMetadata = Array.isArray(metadata)\r\n ? metadata[index]\r\n : metadata;\r\n\r\n if (itemMetadata === undefined || itemMetadata === null) {\r\n return meta;\r\n }\r\n\r\n return {\r\n ...meta,\r\n metadata: itemMetadata\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * 답글 전송 (간편 버전) — 텍스트 답글 전용.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} message - 답글 내용\r\n * @param {string} replyToMessageId - 원본 메시지의 서버 {@code messageId}\r\n * @returns {Promise<{duplicate: boolean, messages: Object[]}>}\r\n *\r\n * @example\r\n * // \"안녕하세요\" 메시지에 답장\r\n * await chat.sendReply('room-id', '네 반가워요', 'original-msg-id');\r\n */\r\n async sendReply(roomId, message, replyToMessageId) {\r\n return this.sendMessage(roomId, { message, replyToMessageId });\r\n }\r\n\r\n /**\r\n * 답글 전송 — Optimistic UI 버전.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} message - 답글 내용\r\n * @param {string} replyToMessageId - 원본 메시지의 서버 {@code messageId}\r\n * @returns {{baseMessageId: string, messageIds: string[], promise: Promise<{duplicate: boolean, messages: Object[]}>}}\r\n */\r\n sendReplyOptimistic(roomId, message, replyToMessageId) {\r\n return this.sendMessageOptimistic(roomId, { message, replyToMessageId });\r\n }\r\n\r\n /**\r\n * 메시지 삭제.\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 메시지 ID\r\n * @param {string} [deleteType='ALL'] - 삭제 유형.\r\n * {@code 'ALL'} — 모두에게 삭제 (양쪽 \"삭제된 메시지입니다\" 표시).\r\n * {@code 'SELF'} — 나만 삭제 (내 화면에서만 숨김, 상대방은 원본 유지).\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * // 모두에게 삭제\r\n * await chat.deleteMessage('room-id', 'msg-id');\r\n *\r\n * @example\r\n * // 나만 삭제\r\n * await chat.deleteMessage('room-id', 'msg-id', 'SELF');\r\n */\r\n async deleteMessage(roomId, messageId, deleteType = 'ALL') {\r\n return this.apiClient.post(`/api/v1/chat-messages/${roomId}/messages/${messageId}/delete`, {\r\n deleteType\r\n });\r\n }\r\n\r\n /**\r\n * 메시지 수정.\r\n *\r\n * <p>본인 메시지만 수정 가능. 수정 후 '수정됨' 라벨이 표시됩니다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 메시지 ID\r\n * @param {string} message - 수정할 내용 (max 5000자)\r\n * @returns {Promise<Object>}\r\n *\r\n * @example\r\n * await chat.editMessage('room-id', 'msg-id', '수정된 내용입니다');\r\n */\r\n async editMessage(roomId, messageId, message) {\r\n return this.apiClient.put(`/api/v1/chat-messages/${roomId}/messages/${messageId}`, {\r\n message\r\n });\r\n }\r\n\r\n /**\r\n * 서버의 {@code SendMessageService.hasTextMessage} mirror — null/공백 문자열을 모두 \"텍스트 없음\" 으로 취급.\r\n * <p>서버 기준 ({@code message != null && !message.isBlank()}) 과 동일해야 messageIds prediction 이 일치한다.</p>\r\n *\r\n * @private\r\n * @param {string|null|undefined} message\r\n * @returns {boolean}\r\n */\r\n _hasTextMessage(message) {\r\n return typeof message === 'string' && message.trim().length > 0;\r\n }\r\n\r\n /**\r\n * MIME 타입 → 서버 {@code ChatMessageType} 분류 (서버 {@code FileGroupingService.classifyFileType} mirror).\r\n * <p>서버 도메인 규칙과 동일해야 group count 예측이 일치한다. 서버 변경 시 본 메서드도 동기화 필요.</p>\r\n *\r\n * @private\r\n * @param {string} mimeType\r\n * @returns {string} 'IMAGE' | 'VIDEO' | 'AUDIO' | 'DOCUMENT' | 'FILE'\r\n */\r\n _classifyFileType(mimeType) {\r\n const mime = (mimeType || '').toLowerCase();\r\n if (mime.startsWith('image/')) return 'IMAGE';\r\n if (mime.startsWith('video/')) return 'VIDEO';\r\n if (mime.startsWith('audio/')) return 'AUDIO';\r\n if (mime.includes('pdf') || mime.includes('document')) return 'DOCUMENT';\r\n return 'FILE';\r\n }\r\n\r\n /**\r\n * 파일 그룹 수 예측 (서버 {@code FileGroupingService.groupFiles} mirror).\r\n * <p>{@code separateFiles=true} 면 파일 개수, {@code false} 면 타입별 distinct 개수.\r\n * File 객체 ({@code .type}) / FileMetaData 객체 ({@code .fileType}) 양쪽 입력 모두 처리.</p>\r\n *\r\n * @private\r\n * @param {Array<File|Blob|Object>} fileArray\r\n * @param {boolean} separateFiles\r\n * @returns {number}\r\n */\r\n _predictGroupCount(fileArray, separateFiles) {\r\n if (!fileArray || fileArray.length === 0) return 0;\r\n if (separateFiles) return fileArray.length;\r\n\r\n // 입력 순서 보존하면서 distinct 타입 카운트 (서버 LinkedHashMap 동작과 동일)\r\n const typesSeen = new Set();\r\n for (const file of fileArray) {\r\n const mime = file && (file.type || file.fileType) || '';\r\n typesSeen.add(this._classifyFileType(mime));\r\n }\r\n return typesSeen.size;\r\n }\r\n\r\n /**\r\n * 서버 저장 messageId 배열 예측 (서버 {@code SendMessageService} mirror).\r\n * <p>규칙 ({@code SendMessageService.java:102-106, 197}):\r\n * {@code expectedMessageCount = (hasMessage?1:0) + groupCount}, 1 이면 base, 2 이상이면\r\n * 텍스트=base / 파일=base#0..#(N-1).</p>\r\n *\r\n * @private\r\n * @param {string} baseMessageId\r\n * @param {number} groupCount\r\n * @param {boolean} hasMessage\r\n * @returns {string[]} 응답 messages 와 같은 순서\r\n */\r\n _predictMessageIds(baseMessageId, groupCount, hasMessage) {\r\n const expectedCount = (hasMessage ? 1 : 0) + groupCount;\r\n if (expectedCount === 0) return [];\r\n if (expectedCount === 1) return [baseMessageId];\r\n\r\n const ids = [];\r\n if (hasMessage) ids.push(baseMessageId);\r\n for (let i = 0; i < groupCount; i++) {\r\n ids.push(`${baseMessageId}#${i}`);\r\n }\r\n return ids;\r\n }\r\n\r\n /**\r\n * {@code SendMessageData} 페이로드로부터 messageIds 예측.\r\n * <p>{@link #sendMessageOptimistic} 가 사용 — {@code data.fileInfos} 가 있으면 그걸로\r\n * group count 예측, 없으면 텍스트 단건으로 처리.</p>\r\n *\r\n * @private\r\n */\r\n _predictMessageIdsFromData(baseMessageId, data) {\r\n const fileInfos = (data && data.fileInfos) || [];\r\n const separate = !data || data.separateFiles !== false;\r\n const groupCount = this._predictGroupCount(fileInfos, separate);\r\n const hasMessage = this._hasTextMessage(data && data.message);\r\n return this._predictMessageIds(baseMessageId, groupCount, hasMessage);\r\n }\r\n\r\n /**\r\n * 예측 messageIds 와 서버 응답을 비교하여 result 에 optimistic metadata 첨부.\r\n * <p>서버 분류 규칙 drift 를 런타임에 감지하기 위한 방어 로직. mismatch 발생 시\r\n * warn 로그 + 응답에 {@code optimistic.predictionMatched: false} 를 실어 호출자가\r\n * 프로그래밍적으로 fallback reconcile 을 결정할 수 있게 한다.</p>\r\n *\r\n * <p>길이 mismatch 와 같은 길이의 값/순서 mismatch 모두 동일하게 {@code predictionMatched: false}\r\n * 로 표시된다. 호출자는 README \"Drift 방어\" 섹션의 fallback 패턴을 사용하면 된다.</p>\r\n *\r\n * @private\r\n * @param {string[]} predictedMessageIds\r\n * @param {{duplicate: boolean, messages: Array<{messageId: string}>}} result\r\n * @returns {{duplicate: boolean, messages: Array, optimistic: {predictedMessageIds: string[], actualMessageIds: string[], predictionMatched: boolean}}}\r\n */\r\n _attachOptimisticMetadata(predictedMessageIds, result) {\r\n const actualMessageIds = (result && Array.isArray(result.messages))\r\n ? result.messages.map(m => (m && m.messageId) || null)\r\n : [];\r\n\r\n const predictionMatched =\r\n actualMessageIds.length === predictedMessageIds.length &&\r\n predictedMessageIds.every((id, i) => id === actualMessageIds[i]);\r\n\r\n if (!predictionMatched) {\r\n this.logger.warn('Optimistic messageId prediction mismatch — server may have changed grouping rules. Use result.optimistic.actualMessageIds to reconcile.', {\r\n predicted: predictedMessageIds,\r\n actual: actualMessageIds\r\n });\r\n }\r\n\r\n return {\r\n ...result,\r\n optimistic: {\r\n predictedMessageIds,\r\n actualMessageIds,\r\n predictionMatched\r\n }\r\n };\r\n }\r\n\r\n /**\r\n * 메시지 ID 생성 (UUID v4 형식)\r\n * @private\r\n * @returns {string}\r\n */\r\n _generateMessageId() {\r\n // crypto.randomUUID() 우선 사용 (높은 엔트로피), 미지원 환경 fallback\r\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\r\n return crypto.randomUUID();\r\n }\r\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\r\n const r = Math.random() * 16 | 0;\r\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\r\n return v.toString(16);\r\n });\r\n }\r\n\r\n /**\r\n * 서버 SuccessResponse 래퍼가 있으면 data 만 꺼내고, 아니면 원본 응답을 그대로 반환합니다.\r\n * data 가 null 이어도 그대로 null 을 반환해야 하므로 nullish coalescing 을 쓰지 않습니다.\r\n *\r\n * @private\r\n * @param {*} response\r\n * @returns {*}\r\n */\r\n _unwrapSuccessResponse(response) {\r\n return response\r\n && typeof response === 'object'\r\n && Object.prototype.hasOwnProperty.call(response, 'data')\r\n ? response.data\r\n : response;\r\n }\r\n\r\n /**\r\n * REST 메시지 응답의 타임스탬프를 epoch millis(number)로 정규화합니다 (in-place).\r\n *\r\n * <p>서버의 두 전달 경로가 서로 다르게 직렬화한다 — WebSocket 이벤트는 epoch number,\r\n * REST(목록/전송 응답)는 ISO 문자열(타임존 없는 LocalDateTime). 선언된 타입\r\n * ({@code ChatMessage.sentAt: number})으로 통일해 소비자 분기를 제거한다.</p>\r\n *\r\n * <p>⚠️ 한계: 서버 ISO 문자열에 타임존이 없어 브라우저 로컬 타임존으로 해석된다 —\r\n * 서버(KST)와 사용자 타임존이 다르면 오프셋만큼 어긋날 수 있다. 근본 해소는\r\n * 백엔드 직렬화 통일(후속 결정) 영역.</p>\r\n *\r\n * @private\r\n * @param {Object} message - REST 응답의 메시지 객체 (수정됨)\r\n * @returns {Object} 같은 객체\r\n */\r\n _normalizeMessageTimestamps(message) {\r\n if (!message || typeof message !== 'object') {\r\n return message;\r\n }\r\n message.sentAt = this._toEpochMillis(message.sentAt);\r\n message.editedAt = this._toEpochMillis(message.editedAt);\r\n if (message.replyTo && typeof message.replyTo === 'object') {\r\n message.replyTo.sentAt = this._toEpochMillis(message.replyTo.sentAt);\r\n }\r\n return message;\r\n }\r\n\r\n /**\r\n * ISO 문자열이면 epoch millis 로 변환, number/null/undefined 는 그대로 통과.\r\n * 파싱 불가 문자열은 원본 유지 (조용한 데이터 손실 방지).\r\n *\r\n * @private\r\n * @param {*} value\r\n * @returns {*}\r\n */\r\n _toEpochMillis(value) {\r\n if (typeof value !== 'string') {\r\n return value;\r\n }\r\n const parsed = Date.parse(value);\r\n return Number.isNaN(parsed) ? value : parsed;\r\n }\r\n\r\n // ==================== WebSocket 구독 ====================\r\n\r\n /**\r\n * 채팅방 구독.\r\n *\r\n * <p>구독 시 메시지, 읽음, 타이핑, 멤버 변경 이벤트를 자동으로 수신.\r\n * 멤버 변경은 roomList 구독 이벤트를 내부 변환하여\r\n * {@code memberJoined} / {@code memberLeft} 로 emit.</p>\r\n *\r\n * <p>자동 읽음 처리는 {@link #setActiveRoom} 으로 현재 보고 있는 방을 설정해야 동작합니다.\r\n * 구독만으로는 읽음 처리되지 않으며, 다른 방을 보고 있으면 안읽음 카운트가 유지됩니다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n * @returns {Promise<void>}\r\n *\r\n * @fires memberJoined - { roomId, members: [{ userId, nickname, profileUrl }], participantCount, timestamp }\r\n * @fires memberLeft - { roomId, members: [{ userId, nickname, profileUrl }], participantCount, timestamp }\r\n */\r\n async subscribeRoom(roomId) {\r\n if (this.subscribedRooms.has(roomId)) {\r\n this.logger.warn(`Already subscribed to room: ${roomId}`);\r\n return;\r\n }\r\n\r\n // 메시지 구독\r\n const chatDestination = WebSocketPaths.getChatDestination(roomId);\r\n await this.connectionManager.subscribe(chatDestination, (message) => {\r\n this._handleChatMessage(roomId, message);\r\n });\r\n\r\n // 읽음 이벤트 구독\r\n const readDestination = WebSocketPaths.getChatReadDestination(roomId);\r\n await this.connectionManager.subscribe(readDestination, (message) => {\r\n this._handleReadEvent(roomId, message);\r\n });\r\n\r\n // 타이핑 이벤트 구독\r\n const typingDestination = WebSocketPaths.getChatTypingDestination(roomId);\r\n await this.connectionManager.subscribe(typingDestination, (event) => {\r\n this._handleTypingEvent(roomId, event);\r\n });\r\n\r\n // AI 진행 표시(생각중/검색중/작성중) 구독 — typing 과 별개 채널(가산 UX). 구버전 SDK 는 미구독 → 기존 typing 만.\r\n const assistantStreamDestination = WebSocketPaths.getChatAssistantStreamDestination(roomId);\r\n await this.connectionManager.subscribe(assistantStreamDestination, (event) => {\r\n this._handleAssistantStreamEvent(roomId, event);\r\n });\r\n\r\n // 멤버 변경 이벤트 — roomList 이벤트를 방 수준으로 변환하여 자동 emit\r\n // 서버가 members 배열(입장/퇴장 사용자들) + participantCount(정확한 인원수) 포함\r\n const memberJoinedHandler = (event) => {\r\n if (event.roomId === roomId) {\r\n this.emit('memberJoined', {\r\n roomId,\r\n members: event.members || [],\r\n participantCount: event.activeParticipantCount,\r\n timestamp: event.timestamp\r\n });\r\n }\r\n };\r\n const memberLeftHandler = (event) => {\r\n if (event.roomId === roomId) {\r\n this.emit('memberLeft', {\r\n roomId,\r\n members: event.members || [],\r\n participantCount: event.activeParticipantCount,\r\n timestamp: event.timestamp\r\n });\r\n }\r\n };\r\n this.on('roomListJoined', memberJoinedHandler);\r\n this.on('roomListLeft', memberLeftHandler);\r\n\r\n this.subscribedRooms.set(roomId, {\r\n chatDestination,\r\n readDestination,\r\n typingDestination,\r\n assistantStreamDestination,\r\n memberJoinedHandler,\r\n memberLeftHandler,\r\n subscribedAt: new Date()\r\n });\r\n\r\n this.logger.info(`Subscribed to room: ${roomId}`);\r\n this.emit('roomSubscribed', { roomId });\r\n }\r\n\r\n /**\r\n * 채팅방 구독 해제\r\n * @param {string} roomId - 채팅방 ID\r\n */\r\n unsubscribeRoom(roomId) {\r\n const subscription = this.subscribedRooms.get(roomId);\r\n if (!subscription) {\r\n this.logger.warn(`Not subscribed to room: ${roomId}`);\r\n return;\r\n }\r\n\r\n this.connectionManager.unsubscribe(subscription.chatDestination);\r\n this.connectionManager.unsubscribe(subscription.readDestination);\r\n if (subscription.typingDestination) {\r\n this.connectionManager.unsubscribe(subscription.typingDestination);\r\n }\r\n if (subscription.assistantStreamDestination) {\r\n this.connectionManager.unsubscribe(subscription.assistantStreamDestination);\r\n }\r\n\r\n // 멤버 변경 리스너 해제\r\n if (subscription.memberJoinedHandler) {\r\n this.off('roomListJoined', subscription.memberJoinedHandler);\r\n }\r\n if (subscription.memberLeftHandler) {\r\n this.off('roomListLeft', subscription.memberLeftHandler);\r\n }\r\n\r\n // 타이핑 타이머 정리 (outgoing + incoming assistant 둘 다)\r\n this._clearTypingTimer(roomId);\r\n this._clearAssistantTypingTimer(roomId);\r\n // AI 진행 표시 상태/타이머 정리 (emit 없이 — 구독 해제 중)\r\n this._clearAssistantProgress(roomId, false);\r\n\r\n // 현재 보고 있는 방이면 activeRoom 해제\r\n if (this._activeRoomId === roomId) {\r\n this._activeRoomId = null;\r\n }\r\n\r\n this.subscribedRooms.delete(roomId);\r\n\r\n // 방별 dedup bucket 정리 — 장시간 세션에서 많은 방을 오갈 때 메모리 누수 차단.\r\n // 각 bucket 은 방당 최대 _maxSeenPerRoom(200) entries 라 개별은 작지만 방 수가 늘면 누적.\r\n this._seenChatMessageIdsByRoom.delete(roomId);\r\n this._seenRoomListMessageIdsByRoom.delete(roomId);\r\n\r\n this.logger.info(`Unsubscribed from room: ${roomId}`);\r\n this.emit('roomUnsubscribed', { roomId });\r\n }\r\n\r\n /**\r\n * 모든 채팅방 구독 해제\r\n */\r\n unsubscribeAllRooms() {\r\n this.subscribedRooms.forEach((_, roomId) => {\r\n this.unsubscribeRoom(roomId);\r\n });\r\n }\r\n\r\n /**\r\n * 현재 보고 있는 채팅방 설정.\r\n * 이 방에서 수신되는 다른 사용자의 메시지는 자동으로 읽음 처리됩니다.\r\n * 다른 방이나 리스트 화면으로 이동 시 {@link #clearActiveRoom} 호출.\r\n *\r\n * @param {string} roomId - 현재 보고 있는 채팅방 ID\r\n */\r\n setActiveRoom(roomId) {\r\n this._activeRoomId = roomId;\r\n this.logger.debug(`Active room set: ${roomId}`);\r\n }\r\n\r\n /**\r\n * 현재 보고 있는 채팅방 해제 (리스트 화면 등으로 이동 시).\r\n * 자동 읽음 처리가 중단됩니다.\r\n */\r\n clearActiveRoom() {\r\n this.logger.debug(`Active room cleared (was: ${this._activeRoomId})`);\r\n this._activeRoomId = null;\r\n }\r\n\r\n /**\r\n * 현재 보고 있는 채팅방 ID 반환.\r\n * @returns {string|null}\r\n */\r\n getActiveRoom() {\r\n return this._activeRoomId;\r\n }\r\n\r\n /**\r\n * 채팅방 리스트 실시간 업데이트 구독 (카톡 스타일).\r\n *\r\n * 리스트 화면에서 호출. 구독 후 다음 이벤트를 받을 수 있음:\r\n * - roomListUpdate — 모든 이벤트 (통합, eventType 으로 분기)\r\n * - roomListMessage — 새 메시지 수신 (MESSAGE_RECEIVED)\r\n * - roomListMessageDeleted — 현재 lastMessage 삭제 (MESSAGE_DELETED)\r\n * - roomListMessageUpdated — 현재 lastMessage 편집 (MESSAGE_UPDATED)\r\n * - roomListCreated — 새 방 생성 (ROOM_CREATED)\r\n * - roomListJoined — 방 입장 (ROOM_JOINED)\r\n * - roomListLeft — 방 퇴장 (ROOM_LEFT)\r\n * - roomListSelfLeft — 본인이 나간 경우 (리스트에서 제거 신호)\r\n *\r\n * @returns {Promise<void>}\r\n *\r\n * @example\r\n * await client.chat.subscribeRoomList();\r\n *\r\n * client.on('roomListMessage', (event) => {\r\n * // 해당 방을 리스트 최상단으로 이동\r\n * // event.actorId !== currentUserId 일 때만 unread +1\r\n * moveRoomToTop(event.roomId);\r\n * updateLastMessage(event.roomId, event.lastMessage, event.lastMessageAt);\r\n * if (event.actorId !== currentUserId) {\r\n * incrementUnread(event.roomId, event.unreadCountDelta);\r\n * }\r\n * });\r\n *\r\n * client.on('roomListSelfLeft', (event) => {\r\n * // 본인이 나간 방 → 리스트에서 제거\r\n * removeRoomFromList(event.roomId);\r\n * });\r\n */\r\n async subscribeRoomList() {\r\n if (this.roomListSubscribed) {\r\n this.logger.warn('Already subscribed to room list');\r\n return;\r\n }\r\n\r\n const destination = WebSocketPaths.ROOM_LIST_USER_DESTINATION;\r\n await this.connectionManager.subscribe(destination, (event) => {\r\n // noinspection JSCheckFunctionSignatures\r\n this._handleRoomListEvent(event);\r\n });\r\n\r\n this.roomListSubscribed = true;\r\n this.logger.info('Subscribed to room list');\r\n this.emit('roomListSubscribed', {});\r\n }\r\n\r\n /**\r\n * 채팅방 리스트 구독 해제.\r\n */\r\n unsubscribeRoomList() {\r\n if (!this.roomListSubscribed) {\r\n this.logger.warn('Not subscribed to room list');\r\n return;\r\n }\r\n\r\n this.connectionManager.unsubscribe(WebSocketPaths.ROOM_LIST_USER_DESTINATION);\r\n this.roomListSubscribed = false;\r\n\r\n this.logger.info('Unsubscribed from room list');\r\n this.emit('roomListUnsubscribed', {});\r\n }\r\n\r\n /**\r\n * 채팅방 리스트 구독 여부.\r\n * @returns {boolean}\r\n */\r\n isRoomListSubscribed() {\r\n return this.roomListSubscribed;\r\n }\r\n\r\n /**\r\n * 메시지 읽음 처리\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {string} messageId - 메시지 ID\r\n */\r\n markAsRead(roomId, messageId) {\r\n if (!this.connectionManager.isConnected()) {\r\n this.logger.warn('Cannot mark as read: not connected');\r\n return false;\r\n }\r\n\r\n return this.connectionManager.send(WebSocketPaths.CHAT_READ, {\r\n roomId,\r\n messageId\r\n });\r\n }\r\n\r\n // ==================== 이벤트 핸들러 ====================\r\n\r\n /**\r\n * 채팅 메시지 수신 처리.\r\n *\r\n * <p>서버는 동일 채널(/topic/chat/{roomId})로 신규/수정/삭제/리액션/OG 첨부\r\n * 이벤트를 모두 발행하고, 페이로드의 {@code eventType} 으로 구분한다.\r\n * 클라는 eventType 에 따라 별도 이벤트로 re-emit.</p>\r\n *\r\n * <ul>\r\n * <li>{@code MESSAGE_CREATED} → {@code message} + {@code newMessage} (상대 메시지면) + 자동 읽음</li>\r\n * <li>{@code MESSAGE_UPDATED} → {@code message} + {@code messageUpdated}</li>\r\n * <li>{@code MESSAGE_DELETED} → {@code message} + {@code messageDeleted}</li>\r\n * <li>{@code REACTION_CHANGED} → {@code message} + {@code reactionChanged}</li>\r\n * <li>{@code LINK_PREVIEW_ATTACHED} → {@code message} + {@code linkPreviewAttached}</li>\r\n * <li>{@code MESSAGE_TRANSLATED} → {@code message} + {@code messageTranslated} (translations 머지)</li>\r\n * </ul>\r\n *\r\n * <p>{@code message} 이벤트는 모든 케이스에서 발행되어 하위 호환을 유지한다 —\r\n * 기존 구독 코드는 messageId 로 merge 하면 계속 작동.</p>\r\n *\r\n * @private\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} message - 수신된 메시지 (eventType 필드 포함)\r\n */\r\n _handleChatMessage(roomId, message) {\r\n this.logger.debug(`Message received in room ${roomId} (${message.eventType}):`, message);\r\n\r\n // MESSAGE_CREATED 만 dedup 대상 — 다른 eventType (UPDATED/DELETED/REACTION/LINK_PREVIEW) 는\r\n // 같은 messageId 의 의도된 재발행이라 dedup 하면 안 됨.\r\n if (message.eventType === 'MESSAGE_CREATED'\r\n && this._shouldDedupMessage(this._seenChatMessageIdsByRoom, roomId, message.messageId)) {\r\n this.logger.debug(`Duplicate MESSAGE_CREATED suppressed: room=${roomId}, messageId=${message.messageId}`);\r\n return;\r\n }\r\n\r\n // 편집(MESSAGE_UPDATED) 시 서버가 번역을 무효화한다. payload 에 번역 필드가 없으면 generic emit 前에\r\n // 명시적 null 처리 — generic 'message' / 상위 'chatMessage' 소비자도 stale 번역을 즉시 지우도록 (다국어 §9).\r\n if (message.eventType === 'MESSAGE_UPDATED') {\r\n if (message.translations === undefined) message.translations = null;\r\n if (message.sourceLang === undefined) message.sourceLang = null;\r\n if (message.translationsOf === undefined) message.translationsOf = null;\r\n }\r\n\r\n // 모든 이벤트는 통합 이벤트로 pass-through (하위 호환)\r\n this.emit('message', { roomId, message });\r\n\r\n switch (message.eventType) {\r\n case 'MESSAGE_CREATED':\r\n // 어시 메시지 도착 = \"AI 응답 준비중\" 종료 신호. typing timer 즉시 해제 + typing=false emit.\r\n // 서버는 typing=false 를 발행하지 않고 client-side 가 책임 (assistant typing 설계).\r\n // userId / userName 은 서버 typing=true 발행과 정확히 일치하는 generic marker 사용 —\r\n // UI 가 roomId+userId 로 typing state 추적 시 true/false 가 같은 키로 매칭되어야 안 꺼지는\r\n // 케이스 방지. message.userId (페르소나 ID) 를 그대로 쓰면 키 불일치로 영구 표시 위험.\r\n if (message.senderType === 'ASSISTANT') {\r\n this._clearAssistantTypingTimer(roomId);\r\n this.emit('typing', {\r\n roomId,\r\n userId: this._assistantTypingUserId,\r\n userName: this._assistantTypingUserName,\r\n typing: false,\r\n senderType: 'ASSISTANT'\r\n });\r\n // AI 진행 표시도 해제 — 최종 메시지 도착 = 진행 종료(설계 §3.1·§4.1, 메시지=진실).\r\n // DONE 이 유실/지연돼도 메시지로 수렴. active:false emit.\r\n this._clearAssistantProgress(roomId, true, 'message');\r\n }\r\n\r\n // 상대방 메시지일 때만 newMessage + 자동 읽음 처리\r\n if (message.userId !== this.userId) {\r\n this.emit('newMessage', { roomId, message });\r\n\r\n // 현재 보고 있는 방에서만 자동 읽음 처리\r\n if (this._activeRoomId === roomId && message.messageId) {\r\n this.markAsRead(roomId, message.messageId);\r\n }\r\n }\r\n break;\r\n\r\n case 'MESSAGE_UPDATED':\r\n // 번역 stale 클리어(번역 필드 null 보정)는 generic emit 前에 이미 수행됨 (_handleChatMessage 상단).\r\n this.emit('messageUpdated', { roomId, message });\r\n break;\r\n\r\n case 'MESSAGE_DELETED':\r\n this.emit('messageDeleted', { roomId, message });\r\n break;\r\n\r\n case 'REACTION_CHANGED':\r\n this.emit('reactionChanged', { roomId, message });\r\n break;\r\n\r\n case 'LINK_PREVIEW_ATTACHED':\r\n this.emit('linkPreviewAttached', { roomId, message });\r\n break;\r\n\r\n case 'MESSAGE_TRANSLATED':\r\n // 서버가 비동기 번역 완료 후 패치 — message.translations/sourceLang 채워져 옴.\r\n // 소비자는 messageId 로 기존 메시지에 머지 (linkPreviewAttached 와 동일 패턴).\r\n this.emit('messageTranslated', { roomId, message });\r\n break;\r\n\r\n default:\r\n // 미지의 eventType — 구버전 서버(eventType 없음)와 호환 유지.\r\n // 안전장치로 기존 동작(신규 메시지 알림) 수행.\r\n this.logger.warn(`Unknown eventType \"${message.eventType}\" — falling back to legacy behavior`);\r\n if (message.userId !== this.userId) {\r\n this.emit('newMessage', { roomId, message });\r\n if (this._activeRoomId === roomId && message.messageId) {\r\n this.markAsRead(roomId, message.messageId);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 채팅방 리스트 업데이트 이벤트 수신 처리.\r\n * @private\r\n * @param {Object} event - 서버 RoomListEvent\r\n * @param {string} event.eventType - RoomListEventType\r\n * @param {string} event.roomId\r\n * @param {string} event.actorId - 이벤트 발생 사용자\r\n * @param {number} [event.unreadCountDelta]\r\n * @param {string} [event.lastMessage]\r\n * @param {string} [event.lastMessageType]\r\n * @param {string} [event.lastMessageAt]\r\n * @param {string} event.timestamp\r\n */\r\n _handleRoomListEvent(event) {\r\n this.logger.debug('Room list event:', event);\r\n\r\n // MESSAGE_RECEIVED 의 messageId 기반 dedup — chat WebSocket 측과 별도 키 공간.\r\n // 서버가 messageId 를 안 내려준 구버전 페이로드면 dedup 통과 (backward compatible).\r\n if (event.eventType === RoomListEventType.MESSAGE_RECEIVED\r\n && event.messageId\r\n && this._shouldDedupMessage(this._seenRoomListMessageIdsByRoom, event.roomId, event.messageId)) {\r\n this.logger.debug(`Duplicate roomList MESSAGE_RECEIVED suppressed: room=${event.roomId}, messageId=${event.messageId}`);\r\n return;\r\n }\r\n\r\n // 1. 통합 이벤트 (모든 타입)\r\n this.emit('roomListUpdate', event);\r\n\r\n // 2. 타입별 이벤트\r\n switch (event.eventType) {\r\n case RoomListEventType.MESSAGE_RECEIVED:\r\n this.emit('roomListMessage', event);\r\n break;\r\n case RoomListEventType.MESSAGE_DELETED:\r\n this.emit('roomListMessageDeleted', event);\r\n break;\r\n case RoomListEventType.MESSAGE_UPDATED:\r\n this.emit('roomListMessageUpdated', event);\r\n break;\r\n case RoomListEventType.ROOM_CREATED:\r\n this.emit('roomListCreated', event);\r\n break;\r\n case RoomListEventType.ROOM_JOINED:\r\n this.emit('roomListJoined', event);\r\n break;\r\n case RoomListEventType.ROOM_LEFT:\r\n this.emit('roomListLeft', event);\r\n // 본인이 나간 경우 편의 이벤트 (리스트에서 제거해야 함)\r\n if (event.actorId === this.userId) {\r\n this.emit('roomListSelfLeft', event);\r\n }\r\n break;\r\n case RoomListEventType.ROOM_KICKED:\r\n this.emit('roomListKicked', event);\r\n // 본인이 추방당한 경우 편의 이벤트 — UI 에서 \"추방되었습니다\" 표시 + 리스트 제거\r\n if (this._isSelfInMembers(event)) {\r\n this.emit('roomListSelfKicked', event);\r\n }\r\n break;\r\n case RoomListEventType.ROOM_BANNED:\r\n this.emit('roomListBanned', event);\r\n // 본인이 영구 차단당한 경우 편의 이벤트 — \"영구 차단되었습니다\" 표시 + 리스트 제거\r\n if (this._isSelfInMembers(event)) {\r\n this.emit('roomListSelfBanned', event);\r\n }\r\n break;\r\n case RoomListEventType.ROOM_UPDATED:\r\n this.emit('roomListRoomUpdated', event);\r\n break;\r\n case RoomListEventType.MESSAGE_RETENTION_CLEANUP:\r\n // noinspection JSUnresolvedReference\r\n this.emit('retentionCleanup', {\r\n roomId: event.roomId,\r\n cutoffTime: event.cutoffTime\r\n });\r\n break;\r\n default:\r\n this.logger.warn('Unknown room list event type:', event.eventType);\r\n }\r\n }\r\n\r\n /**\r\n * 읽음 이벤트 수신 처리\r\n * @private\r\n * @param {string} roomId - 채팅방 ID\r\n * @param {Object} event - 읽음 이벤트 (단일 또는 배치)\r\n */\r\n _handleReadEvent(roomId, event) {\r\n this.logger.debug(`Read event in room ${roomId}:`, event);\r\n\r\n const resolvedRoomId = event.roomId || roomId;\r\n\r\n // 배치 이벤트인 경우 (방 입장 시 일괄 읽음 처리)\r\n if (event.events && Array.isArray(event.events)) {\r\n event.events.forEach(e => {\r\n this.emit('messageRead', {\r\n roomId: resolvedRoomId,\r\n messageId: e.messageId,\r\n userId: event.userId,\r\n remainingUnreadCount: e.remainingUnreadCount\r\n });\r\n });\r\n }\r\n // 단일 이벤트인 경우 (실시간 개별 읽음 처리)\r\n else {\r\n this.emit('messageRead', {\r\n roomId: resolvedRoomId,\r\n messageId: event.messageId,\r\n userId: event.userId,\r\n remainingUnreadCount: event.remainingUnreadCount\r\n });\r\n }\r\n }\r\n\r\n // ==================== 타이핑 표시 ====================\r\n\r\n /**\r\n * 타이핑 시작 알림.\r\n *\r\n * <p>호출할 때마다 내부 타이머가 리셋됩니다 (debounce).\r\n * 3초간 추가 호출이 없으면 자동으로 {@link #stopTyping} 이 호출됩니다.\r\n * 메시지 전송 ({@link #sendMessage}) 시에도 자동 중단됩니다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n *\r\n * @example\r\n * // input 이벤트에 연결\r\n * inputField.addEventListener('input', () => {\r\n * client.chat.startTyping('room-id');\r\n * });\r\n */\r\n startTyping(roomId) {\r\n if (!this.connectionManager.isConnected()) return;\r\n\r\n const existingTimer = this._typingTimers.get(roomId);\r\n\r\n // throttle: 이미 typing 상태면 stop 타이머만 리셋 (STOMP 재전송 안 함)\r\n if (existingTimer) {\r\n clearTimeout(existingTimer);\r\n } else {\r\n // 최초 또는 stop 이후 첫 호출에만 STOMP 전송\r\n this.connectionManager.send(WebSocketPaths.CHAT_TYPING, {\r\n roomId,\r\n typing: true\r\n });\r\n }\r\n\r\n // 3초 후 자동 stopTyping\r\n const timer = setTimeout(() => {\r\n this.stopTyping(roomId);\r\n }, 3000);\r\n\r\n this._typingTimers.set(roomId, timer);\r\n }\r\n\r\n /**\r\n * 타이핑 중단 알림.\r\n *\r\n * <p>직접 호출하거나, {@link #startTyping} 의 3초 타이머 만료 시,\r\n * 또는 메시지 전송 시 자동 호출됩니다.</p>\r\n *\r\n * @param {string} roomId - 채팅방 ID\r\n */\r\n stopTyping(roomId) {\r\n this._clearTypingTimer(roomId);\r\n\r\n if (!this.connectionManager.isConnected()) return;\r\n\r\n this.connectionManager.send(WebSocketPaths.CHAT_TYPING, {\r\n roomId,\r\n typing: false\r\n });\r\n }\r\n\r\n /**\r\n * 이벤트 members 배열에 본인이 포함돼 있는지 확인.\r\n * <p>ROOM_KICKED / ROOM_BANNED 이벤트에서 \"내가 추방당했는가\" 판별용. actorId (추방한 방장) 가 아니라\r\n * members 배열(추방된 사용자) 을 보는 이유는, 같은 이벤트를 방장 본인도 수신하기 때문.</p>\r\n * @private\r\n */\r\n _isSelfInMembers(event) {\r\n return Array.isArray(event.members)\r\n && event.members.some(m => m && m.userId === this.userId);\r\n }\r\n\r\n /**\r\n * 타이핑 이벤트 수신 처리.\r\n * <p>본인 이벤트는 필터링하여 emit 하지 않음.</p>\r\n * @private\r\n */\r\n _handleTypingEvent(roomId, event) {\r\n // 본인 이벤트 필터 — USER 타이핑만 적용 (어시는 본인 userId 매칭 불가, generic marker 사용)\r\n if (event.senderType !== 'ASSISTANT' && event.userId === this.userId) return;\r\n\r\n // 어시 typing=true 수신 시 client-side timeout 시작 — 서버 측 typing=false 발행이 없으므로\r\n // (1) 어시 메시지 도착 (_handleChatMessage) 또는 (2) 본 timeout 만료 중 먼저 도달 시 typing=false emit.\r\n if (event.senderType === 'ASSISTANT' && event.typing === true) {\r\n this._startAssistantTypingTimer(roomId);\r\n }\r\n\r\n this.emit('typing', {\r\n roomId,\r\n userId: event.userId,\r\n userName: event.userName,\r\n typing: event.typing,\r\n senderType: event.senderType || 'USER' // 구버전 서버 호환 — senderType 없으면 USER\r\n });\r\n }\r\n\r\n /**\r\n * 어시스턴트 typing 자동 해제 타이머 시작/재시작.\r\n * 같은 방에 이미 timer 있으면 reset (새로운 어시 응답 라운드).\r\n * @private\r\n */\r\n _startAssistantTypingTimer(roomId) {\r\n this._clearAssistantTypingTimer(roomId);\r\n const timer = setTimeout(() => {\r\n this._assistantTypingTimers.delete(roomId);\r\n // timeout 만료 = LLM 지연 또는 서버 실패 — UI 가 영구 \"답변 준비중\" 에 갇히지 않도록 해제 emit.\r\n // userId / userName 은 generic marker 일관 (typing=true 발행과 매칭 — P1 fix).\r\n this.emit('typing', {\r\n roomId,\r\n userId: this._assistantTypingUserId,\r\n userName: this._assistantTypingUserName,\r\n typing: false,\r\n senderType: 'ASSISTANT'\r\n });\r\n this.logger.debug(`Assistant typing auto-cleared (timeout): room=${roomId}`);\r\n }, this._assistantTypingTimeoutMs);\r\n this._assistantTypingTimers.set(roomId, timer);\r\n }\r\n\r\n /**\r\n * 어시스턴트 typing 자동 해제 타이머 취소.\r\n * 어시 메시지 도착 시 즉시 호출 (timeout 도달 전 해제).\r\n * @private\r\n */\r\n _clearAssistantTypingTimer(roomId) {\r\n const timer = this._assistantTypingTimers.get(roomId);\r\n if (timer) {\r\n clearTimeout(timer);\r\n this._assistantTypingTimers.delete(roomId);\r\n }\r\n }\r\n\r\n /**\r\n * 타이핑 타이머 정리.\r\n * @private\r\n */\r\n _clearTypingTimer(roomId) {\r\n const timer = this._typingTimers.get(roomId);\r\n if (timer) {\r\n clearTimeout(timer);\r\n this._typingTimers.delete(roomId);\r\n }\r\n }\r\n\r\n /**\r\n * AI 진행 표시(assistant-stream) 이벤트 수신 처리 — PHASE(단계)/DELTA(토큰 청크)/DONE(종료) 분기.\r\n *\r\n * <p>typing 과 별개의 가산 레이어 — UI 는 {@code active:true} 면 {@code text}(토큰 스트림 누적) 우선,\r\n * 없으면 {@code label}(단계) 표시, {@code active:false} 면 해제. PHASE→DELTA 흐름으로 \"작성 중\" 라벨이\r\n * 점진 렌더 텍스트로 자연 전환된다. 서버가 typing 도 이중발행하므로 구버전 SDK 는 본 채널 미구독 → 기존\r\n * typing 만으로 정상 동작(graceful).</p>\r\n * @private\r\n */\r\n _handleAssistantStreamEvent(roomId, event) {\r\n if (!event || !event.type) return;\r\n\r\n if (event.type === 'PHASE') {\r\n const phase = event.phase || null;\r\n const label = (phase && this._assistantPhaseLabels[phase]) || null;\r\n // 같은 stream 의 PHASE 재발행(B-3 synthesis heartbeat 등)이면 누적 텍스트 보존, 새 stream 이면 리셋.\r\n const prev = this._assistantProgress.get(roomId);\r\n const text = (prev && prev.streamId === event.streamId) ? (prev.text || '') : '';\r\n // 진행 상태 갱신 + TTL 재무장(heartbeat 재수신마다 연장). 같은 방의 이전 타이머는 reset.\r\n this._startAssistantProgressTimer(roomId, event, { phase, text });\r\n this.emit('assistantProgress', {\r\n roomId,\r\n streamId: event.streamId || null,\r\n personaId: event.personaId || null,\r\n phase,\r\n label,\r\n active: true,\r\n text: text || null\r\n });\r\n return;\r\n }\r\n\r\n if (event.type === 'DELTA') {\r\n // 토큰 청크(B) — 누적 텍스트에 이어붙여 점진 렌더용으로 emit. WRITING 단계에서 흐른다.\r\n const prev = this._assistantProgress.get(roomId);\r\n // stale DELTA 무시 — 이전 stream 의 늦은 토큰이 새 stream 표시를 오염시키지 않게(DONE 가드와 동형).\r\n if (prev?.streamId && event.streamId && prev.streamId !== event.streamId) {\r\n this.logger.debug(`Stale assistant DELTA ignored: room=${roomId}, delta=${event.streamId}, active=${prev.streamId}`);\r\n return;\r\n }\r\n const phase = (prev && prev.phase) || 'WRITING';\r\n const text = (prev ? (prev.text || '') : '') + (event.delta || '');\r\n this._startAssistantProgressTimer(roomId, event, { phase, text });\r\n this.emit('assistantProgress', {\r\n roomId,\r\n streamId: event.streamId || null,\r\n personaId: event.personaId || null,\r\n phase,\r\n label: this._assistantPhaseLabels[phase] || null,\r\n active: true,\r\n text, // 지금까지 누적된 전체 텍스트(버블 렌더용)\r\n delta: event.delta || null, // 이번 청크(append 렌더 선호 시)\r\n seq: typeof event.seq === 'number' ? event.seq : null\r\n });\r\n return;\r\n }\r\n\r\n if (event.type === 'DONE') {\r\n // 종료(성공·실패 공통) — 상태/타이머 해제 + active:false emit(status/messageId 동봉, 권위 신호).\r\n const state = this._assistantProgress.get(roomId);\r\n // 늦게 도착한 이전 stream 의 DONE 무시 — 다음 stream 의 PHASE 이후 도착 시 새 진행 표시를 끄는 race 차단.\r\n // 메시지/assistant-stream 채널은 ordering 이 묶여 있지 않다. streamId 가 둘 다 있고 다를 때만 stale 판정\r\n // (UI 계약이 \"active 만 보면 됨\"이므로 SDK 가 stale false 를 막는다).\r\n if (state?.streamId && event.streamId && state.streamId !== event.streamId) {\r\n this.logger.debug(`Stale assistant DONE ignored: room=${roomId}, done=${event.streamId}, active=${state.streamId}`);\r\n return;\r\n }\r\n // 메시지 도착으로 먼저 해제됐어도 DONE 은 한 번 더 발행될 수 있으나 active:false 는 idempotent(설계 §4.1).\r\n if (state && state.timer) clearTimeout(state.timer);\r\n this._assistantProgress.delete(roomId);\r\n this.emit('assistantProgress', {\r\n roomId,\r\n streamId: event.streamId || null,\r\n personaId: event.personaId || null,\r\n phase: null,\r\n label: null,\r\n active: false,\r\n status: event.status || null,\r\n messageId: event.messageId || null\r\n });\r\n }\r\n // 그 외 미지 type 은 무시(forward-compat).\r\n }\r\n\r\n /**\r\n * AI 진행 상태 갱신 + TTL 타이머 재무장 — {@code expiresAt}(epochMillis) 기반, 누락/이상 시 fallback.\r\n * {@code fields}={phase, text}(PHASE/DELTA 가 해소해 전달 — DELTA 는 직전 phase 유지 + 누적 text).\r\n * 만료 = DONE·메시지 유실로 진행이 끊긴 것 → UI 가 영구 표시에 갇히지 않게 {@code active:false} 자동 emit.\r\n * @private\r\n */\r\n _startAssistantProgressTimer(roomId, event, fields) {\r\n const prev = this._assistantProgress.get(roomId);\r\n if (prev && prev.timer) clearTimeout(prev.timer);\r\n\r\n let ttl = this._assistantProgressFallbackTtlMs;\r\n if (typeof event.expiresAt === 'number') {\r\n // 비정상값 방어 — [5s, 120s] 로 clamp(과거 timestamp / 과대 TTL 모두 차단).\r\n ttl = Math.min(120000, Math.max(5000, event.expiresAt - Date.now()));\r\n }\r\n const timer = setTimeout(() => {\r\n this._clearAssistantProgress(roomId, true, 'timeout');\r\n this.logger.debug(`Assistant progress auto-cleared (timeout): room=${roomId}`);\r\n }, ttl);\r\n this._assistantProgress.set(roomId, {\r\n streamId: event.streamId || null,\r\n personaId: event.personaId || null,\r\n phase: fields.phase,\r\n text: fields.text || '',\r\n timer\r\n });\r\n }\r\n\r\n /**\r\n * AI 진행 표시 상태/타이머 해제. {@code emit=true} 면 {@code active:false} 를 발행(메시지 도착/타임아웃 해제용).\r\n * 구독 해제 시엔 {@code emit=false} 로 조용히 정리.\r\n * @private\r\n */\r\n _clearAssistantProgress(roomId, emit, reason) {\r\n const state = this._assistantProgress.get(roomId);\r\n if (!state) return;\r\n if (state.timer) clearTimeout(state.timer);\r\n this._assistantProgress.delete(roomId);\r\n if (emit) {\r\n this.emit('assistantProgress', {\r\n roomId,\r\n streamId: state.streamId || null,\r\n personaId: state.personaId || null,\r\n phase: null,\r\n label: null,\r\n active: false,\r\n reason: reason || null\r\n });\r\n }\r\n }\r\n\r\n // ==================== 유틸리티 ====================\r\n\r\n /**\r\n * 구독 중인 채팅방 목록\r\n * @returns {string[]}\r\n */\r\n getSubscribedRooms() {\r\n return Array.from(this.subscribedRooms.keys());\r\n }\r\n\r\n /**\r\n * 채팅방 구독 여부 확인\r\n * @param {string} roomId\r\n * @returns {boolean}\r\n */\r\n isSubscribed(roomId) {\r\n return this.subscribedRooms.has(roomId);\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리.\r\n *\r\n * <p>{@link #unsubscribeRoom} 에서 방별 타이머를 정리하지만, 구독 목록에 없는\r\n * roomId 의 debounce 타이머가 남아 있을 수 있어 Map 전체를 명시적으로 비운다.</p>\r\n */\r\n destroy() {\r\n this.unsubscribeAllRooms();\r\n if (this.roomListSubscribed) {\r\n this.unsubscribeRoomList();\r\n }\r\n\r\n this._typingTimers.forEach((timer) => clearTimeout(timer));\r\n this._typingTimers.clear();\r\n\r\n // 어시 typing 수신 타이머도 전체 cleanup — unsubscribeRoom 이 방별로 정리하지만 구독 목록에\r\n // 없는 roomId 의 타이머가 남아 있을 수 있어 명시 정리 (메모리 누수 차단).\r\n this._assistantTypingTimers.forEach((timer) => clearTimeout(timer));\r\n this._assistantTypingTimers.clear();\r\n\r\n // AI 진행 표시 상태/타이머도 전체 cleanup (구독 외 경로로 남은 roomId 방어).\r\n this._assistantProgress.forEach((state) => state.timer && clearTimeout(state.timer));\r\n this._assistantProgress.clear();\r\n\r\n // dedup bucket 전체 clear — unsubscribeRoom 이 방별로 정리하지만 구독 외 경로로 누적된 bucket\r\n // (예: 구독 안 한 방의 push/listener) 이 있을 수 있어 안전하게 명시 정리.\r\n this._seenChatMessageIdsByRoom.clear();\r\n this._seenRoomListMessageIdsByRoom.clear();\r\n\r\n this.removeAllListeners();\r\n this.logger.info('ChatClient destroyed');\r\n }\r\n}\r\n\r\nexport default ChatClient;\r\n","/**\r\n * MediaStreamManager\r\n * 로컬 미디어 스트림 관리 (카메라/마이크/화면공유)\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport { ErrorTypes, LogLevel } from '../constants.js';\r\n\r\nclass MediaStreamManager extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options = {}) {\r\n super();\r\n\r\n this.localStream = null;\r\n this.screenStream = null;\r\n this.videoEnabled = true;\r\n this.audioEnabled = true;\r\n\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'MediaStreamManager');\r\n this._deviceChangeHandler = null;\r\n }\r\n\r\n /**\r\n * 사용자 미디어 가져오기\r\n * @param {Object} constraints - 미디어 제약조건\r\n * @returns {Promise<MediaStream>}\r\n */\r\n async getUserMedia(constraints = { video: true, audio: true }) {\r\n try {\r\n this.localStream = await navigator.mediaDevices.getUserMedia(constraints);\r\n this.videoEnabled = constraints.video !== false;\r\n this.audioEnabled = constraints.audio !== false;\r\n\r\n this.emit('streamStarted', { stream: this.localStream });\r\n this.logger.info('User media stream started');\r\n return this.localStream;\r\n } catch (error) {\r\n this.logger.error('Failed to get user media:', error);\r\n this.emit('error', {\r\n type: ErrorTypes.MEDIA_ACCESS_DENIED,\r\n message: this._getMediaErrorMessage(error),\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 미디어 에러 메시지 변환\r\n * @private\r\n * @param {Error} error - 에러 객체\r\n * @returns {string} 변환된 에러 메시지\r\n */\r\n _getMediaErrorMessage(error) {\r\n switch (error.name) {\r\n case 'NotAllowedError':\r\n return 'Camera/Microphone permission denied';\r\n case 'NotFoundError':\r\n return 'Camera/Microphone not found';\r\n case 'NotReadableError':\r\n return 'Camera/Microphone is already in use';\r\n case 'OverconstrainedError':\r\n return 'Camera/Microphone constraints cannot be satisfied';\r\n case 'AbortError':\r\n return 'Media access aborted';\r\n default:\r\n return error.message || 'Unknown media error';\r\n }\r\n }\r\n\r\n /**\r\n * 화면 공유 가져오기\r\n * @param {Object} [options] - 화면 공유 옵션\r\n * @returns {Promise<MediaStream>}\r\n */\r\n async getDisplayMedia(options = { video: true, audio: false }) {\r\n try {\r\n this.screenStream = await navigator.mediaDevices.getDisplayMedia(options);\r\n\r\n // 화면 공유 종료 감지\r\n this.screenStream.getVideoTracks()[0].onended = () => {\r\n this.emit('screenShareEnded', {});\r\n this.screenStream = null;\r\n };\r\n\r\n this.emit('screenShareStarted', { stream: this.screenStream });\r\n this.logger.info('Screen share stream started');\r\n return this.screenStream;\r\n } catch (error) {\r\n this.logger.error('Failed to get display media:', error);\r\n this.emit('error', {\r\n type: ErrorTypes.SCREEN_SHARE_DENIED,\r\n message: error.name === 'NotAllowedError'\r\n ? 'Screen share permission denied'\r\n : error.message,\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 비디오 토글\r\n * @returns {boolean} 현재 상태\r\n */\r\n toggleVideo() {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return this.videoEnabled;\r\n }\r\n\r\n const videoTracks = this.localStream.getVideoTracks();\r\n if (videoTracks.length === 0) {\r\n this.logger.warn('No video track available');\r\n return this.videoEnabled;\r\n }\r\n\r\n this.videoEnabled = !this.videoEnabled;\r\n videoTracks.forEach(track => {\r\n track.enabled = this.videoEnabled;\r\n });\r\n\r\n this.emit('videoToggled', { enabled: this.videoEnabled });\r\n this.logger.debug(`Video toggled: ${this.videoEnabled}`);\r\n return this.videoEnabled;\r\n }\r\n\r\n /**\r\n * 오디오 토글\r\n * @returns {boolean} 현재 상태\r\n */\r\n toggleAudio() {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return this.audioEnabled;\r\n }\r\n\r\n const audioTracks = this.localStream.getAudioTracks();\r\n if (audioTracks.length === 0) {\r\n this.logger.warn('No audio track available');\r\n return this.audioEnabled;\r\n }\r\n\r\n this.audioEnabled = !this.audioEnabled;\r\n audioTracks.forEach(track => {\r\n track.enabled = this.audioEnabled;\r\n });\r\n\r\n this.emit('audioToggled', { enabled: this.audioEnabled });\r\n this.logger.debug(`Audio toggled: ${this.audioEnabled}`);\r\n return this.audioEnabled;\r\n }\r\n\r\n /**\r\n * 비디오 상태 설정\r\n * @param {boolean} enabled\r\n */\r\n setVideoEnabled(enabled) {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return;\r\n }\r\n\r\n const videoTracks = this.localStream.getVideoTracks();\r\n videoTracks.forEach(track => {\r\n track.enabled = enabled;\r\n });\r\n this.videoEnabled = enabled;\r\n this.emit('videoToggled', { enabled });\r\n }\r\n\r\n /**\r\n * 오디오 상태 설정\r\n * @param {boolean} enabled\r\n */\r\n setAudioEnabled(enabled) {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return;\r\n }\r\n\r\n const audioTracks = this.localStream.getAudioTracks();\r\n audioTracks.forEach(track => {\r\n track.enabled = enabled;\r\n });\r\n this.audioEnabled = enabled;\r\n this.emit('audioToggled', { enabled });\r\n }\r\n\r\n /**\r\n * 로컬 스트림 가져오기\r\n * @returns {MediaStream|null}\r\n */\r\n getLocalStream() {\r\n return this.localStream;\r\n }\r\n\r\n /**\r\n * 화면 공유 스트림 가져오기\r\n * @returns {MediaStream|null}\r\n */\r\n getScreenStream() {\r\n return this.screenStream;\r\n }\r\n\r\n /**\r\n * 현재 비디오 상태\r\n * @returns {boolean}\r\n */\r\n isVideoEnabled() {\r\n return this.videoEnabled;\r\n }\r\n\r\n /**\r\n * 현재 오디오 상태\r\n * @returns {boolean}\r\n */\r\n isAudioEnabled() {\r\n return this.audioEnabled;\r\n }\r\n\r\n /**\r\n * 모든 트랙 중지\r\n */\r\n stopAll() {\r\n if (this.localStream) {\r\n this.localStream.getTracks().forEach(track => track.stop());\r\n this.localStream = null;\r\n this.emit('streamStopped', {});\r\n this.logger.info('All media tracks stopped');\r\n }\r\n\r\n if (this.screenStream) {\r\n this.screenStream.getTracks().forEach(track => track.stop());\r\n this.screenStream = null;\r\n }\r\n }\r\n\r\n /**\r\n * 특정 트랙 교체 (내부용 - switchDevice에서 호출)\r\n * @private\r\n * @param {MediaStreamTrack} oldTrack\r\n * @param {MediaStreamTrack} newTrack\r\n */\r\n replaceTrack(oldTrack, newTrack) {\r\n if (!this.localStream) {\r\n this.logger.warn('No local stream available');\r\n return;\r\n }\r\n\r\n this.localStream.removeTrack(oldTrack);\r\n this.localStream.addTrack(newTrack);\r\n oldTrack.stop();\r\n\r\n this.emit('trackReplaced', { oldTrack, newTrack });\r\n this.logger.debug(`Track replaced: ${oldTrack.kind}`);\r\n }\r\n\r\n /**\r\n * 특정 종류의 트랙 가져오기\r\n * @param {string} kind - 'video' or 'audio'\r\n * @returns {MediaStreamTrack|null}\r\n */\r\n getTrack(kind) {\r\n if (!this.localStream) return null;\r\n\r\n const tracks = kind === 'video'\r\n ? this.localStream.getVideoTracks()\r\n : this.localStream.getAudioTracks();\r\n\r\n return tracks.length > 0 ? tracks[0] : null;\r\n }\r\n\r\n /**\r\n * 디바이스 목록 가져오기\r\n * @returns {Promise<Object>}\r\n */\r\n async getDevices() {\r\n try {\r\n const devices = await navigator.mediaDevices.enumerateDevices();\r\n\r\n return {\r\n videoInputs: devices.filter(d => d.kind === 'videoinput'),\r\n audioInputs: devices.filter(d => d.kind === 'audioinput'),\r\n audioOutputs: devices.filter(d => d.kind === 'audiooutput')\r\n };\r\n } catch (error) {\r\n this.logger.error('Failed to enumerate devices:', error);\r\n this.emit('error', {\r\n type: ErrorTypes.ENUMERATE_DEVICES_FAILED,\r\n message: error.message,\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 특정 디바이스로 전환\r\n * @param {string} deviceId - 디바이스 ID\r\n * @param {string} kind - 'video' or 'audio'\r\n * @returns {Promise<MediaStreamTrack>}\r\n * @throws {Error} 스트림이 없거나 디바이스 전환 실패 시\r\n */\r\n async switchDevice(deviceId, kind) {\r\n if (!this.localStream) {\r\n throw new Error('No local stream available');\r\n }\r\n\r\n try {\r\n const constraints = kind === 'video'\r\n ? { video: { deviceId: { exact: deviceId } }, audio: false }\r\n : { video: false, audio: { deviceId: { exact: deviceId } } };\r\n\r\n const newStream = await navigator.mediaDevices.getUserMedia(constraints);\r\n const newTrack = newStream.getTracks()[0];\r\n\r\n const oldTrack = kind === 'video'\r\n ? this.localStream.getVideoTracks()[0]\r\n : this.localStream.getAudioTracks()[0];\r\n\r\n if (oldTrack) {\r\n this.replaceTrack(oldTrack, newTrack);\r\n\r\n this.emit('deviceSwitched', {\r\n kind,\r\n deviceId,\r\n newTrack\r\n });\r\n\r\n this.logger.info(`${kind} device switched to ${deviceId}`);\r\n }\r\n\r\n return newTrack;\r\n\r\n } catch (error) {\r\n this.logger.error(`Failed to switch ${kind} device:`, error);\r\n this.emit('error', {\r\n type: ErrorTypes.DEVICE_SWITCH_FAILED,\r\n message: error.message,\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 디바이스 변경 감지 시작\r\n */\r\n startDeviceChangeDetection() {\r\n if (this._deviceChangeHandler) {\r\n return;\r\n }\r\n\r\n this._deviceChangeHandler = async () => {\r\n try {\r\n const devices = await this.getDevices();\r\n this.emit('deviceChange', devices);\r\n this.logger.debug('Device change detected');\r\n } catch (error) {\r\n this.logger.warn('Failed to get devices on change:', error);\r\n }\r\n };\r\n\r\n navigator.mediaDevices.addEventListener('devicechange', this._deviceChangeHandler);\r\n this.logger.debug('Device change detection started');\r\n }\r\n\r\n /**\r\n * 디바이스 변경 감지 중지\r\n */\r\n stopDeviceChangeDetection() {\r\n if (this._deviceChangeHandler) {\r\n navigator.mediaDevices.removeEventListener('devicechange', this._deviceChangeHandler);\r\n this._deviceChangeHandler = null;\r\n this.logger.debug('Device change detection stopped');\r\n }\r\n }\r\n\r\n /**\r\n * 스트림 정보\r\n * @returns {Object}\r\n */\r\n getStreamInfo() {\r\n if (!this.localStream) {\r\n return {\r\n hasStream: false,\r\n videoEnabled: this.videoEnabled,\r\n audioEnabled: this.audioEnabled,\r\n tracks: []\r\n };\r\n }\r\n\r\n return {\r\n hasStream: true,\r\n videoEnabled: this.videoEnabled,\r\n audioEnabled: this.audioEnabled,\r\n tracks: this.localStream.getTracks().map(track => ({\r\n kind: track.kind,\r\n id: track.id,\r\n label: track.label,\r\n enabled: track.enabled,\r\n readyState: track.readyState,\r\n muted: track.muted\r\n }))\r\n };\r\n }\r\n\r\n /**\r\n * 비디오 해상도 변경\r\n * @param {Object} constraints - { width, height, frameRate }\r\n */\r\n async applyVideoConstraints(constraints) {\r\n if (!this.localStream) {\r\n throw new Error('No local stream available');\r\n }\r\n\r\n const videoTrack = this.localStream.getVideoTracks()[0];\r\n if (!videoTrack) {\r\n throw new Error('No video track available');\r\n }\r\n\r\n try {\r\n await videoTrack.applyConstraints(constraints);\r\n this.emit('videoConstraintsApplied', { constraints });\r\n this.logger.info('Video constraints applied:', constraints);\r\n } catch (error) {\r\n this.logger.error('Failed to apply video constraints:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 현재 비디오 설정 가져오기\r\n * @returns {MediaTrackSettings|null}\r\n */\r\n getVideoSettings() {\r\n if (!this.localStream) return null;\r\n\r\n const videoTrack = this.localStream.getVideoTracks()[0];\r\n return videoTrack ? videoTrack.getSettings() : null;\r\n }\r\n\r\n /**\r\n * 현재 오디오 설정 가져오기\r\n * @returns {MediaTrackSettings|null}\r\n */\r\n getAudioSettings() {\r\n if (!this.localStream) return null;\r\n\r\n const audioTrack = this.localStream.getAudioTracks()[0];\r\n return audioTrack ? audioTrack.getSettings() : null;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리\r\n */\r\n destroy() {\r\n this.stopAll();\r\n this.stopDeviceChangeDetection();\r\n this.removeAllListeners();\r\n this.logger.info('MediaStreamManager destroyed');\r\n }\r\n}\r\n\r\nexport default MediaStreamManager;\r\n","/**\r\n * PeerConnectionManager\r\n * WebRTC Peer 연결 관리 (Perfect Negotiation 패턴)\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport { ErrorTypes, DefaultConfig, LogLevel } from '../constants.js';\r\n\r\nclass PeerConnectionManager extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {Object[]} [options.iceServers] - ICE 서버 설정\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options = {}) {\r\n super();\r\n\r\n this.iceServers = options.iceServers || DefaultConfig.iceServers;\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'PeerConnectionManager');\r\n\r\n // 피어 연결 맵: peerId -> { connection, polite, makingOffer, ignoreOffer }\r\n this.peers = new Map();\r\n\r\n // 로컬 스트림\r\n this.localStream = null;\r\n }\r\n\r\n /**\r\n * 로컬 스트림 설정\r\n * @param {MediaStream} stream\r\n */\r\n setLocalStream(stream) {\r\n this.localStream = stream;\r\n\r\n // 기존 연결에 트랙 추가\r\n this.peers.forEach((peer) => {\r\n this._addTracksToConnection(peer.connection);\r\n });\r\n }\r\n\r\n /**\r\n * ICE 서버 설정\r\n * @param {Object[]} servers\r\n */\r\n setIceServers(servers) {\r\n this.iceServers = servers;\r\n }\r\n\r\n /**\r\n * 피어 연결 생성\r\n * @param {string} peerId - 피어 ID\r\n * @param {boolean} polite - Polite 피어 여부 (Perfect Negotiation)\r\n * @param {Object} [options] - 추가 옵션\r\n * @param {boolean} [options.skipAutoNegotiation=false] - 자동 negotiation 건너뛰기\r\n * @returns {RTCPeerConnection}\r\n */\r\n createPeerConnection(peerId, polite = false, options = {}) {\r\n if (this.peers.has(peerId)) {\r\n this.logger.warn(`Peer connection already exists: ${peerId}`);\r\n return this.peers.get(peerId).connection;\r\n }\r\n\r\n const config = {\r\n iceServers: this.iceServers,\r\n iceCandidatePoolSize: 10\r\n };\r\n\r\n const connection = new RTCPeerConnection(config);\r\n\r\n const peerState = {\r\n connection,\r\n polite,\r\n makingOffer: false,\r\n ignoreOffer: false,\r\n isSettingRemoteAnswerPending: false,\r\n skipAutoNegotiation: options.skipAutoNegotiation || false\r\n };\r\n\r\n this.peers.set(peerId, peerState);\r\n\r\n // 이벤트 핸들러 설정\r\n this._setupConnectionHandlers(peerId, connection, peerState);\r\n\r\n // 로컬 트랙 추가\r\n if (this.localStream) {\r\n this._addTracksToConnection(connection);\r\n }\r\n\r\n this.logger.info(`Peer connection created: ${peerId} (polite: ${polite}, skipAutoNegotiation: ${peerState.skipAutoNegotiation})`);\r\n return connection;\r\n }\r\n\r\n /**\r\n * 연결 이벤트 핸들러 설정\r\n * @private\r\n * @param {string} peerId - 피어 ID\r\n * @param {RTCPeerConnection} connection - 피어 연결\r\n * @param {Object} peerState - 피어 상태 객체\r\n */\r\n _setupConnectionHandlers(peerId, connection, peerState) {\r\n // ICE Candidate 이벤트\r\n connection.onicecandidate = (event) => {\r\n if (event.candidate) {\r\n this.emit('iceCandidate', {\r\n peerId,\r\n candidate: event.candidate\r\n });\r\n }\r\n };\r\n\r\n // ICE 연결 상태 변경\r\n connection.oniceconnectionstatechange = () => {\r\n this.logger.debug(`ICE connection state (${peerId}): ${connection.iceConnectionState}`);\r\n\r\n this.emit('iceConnectionStateChange', {\r\n peerId,\r\n state: connection.iceConnectionState\r\n });\r\n\r\n if (connection.iceConnectionState === 'failed') {\r\n this.emit('error', {\r\n type: ErrorTypes.ICE_CONNECTION_FAILED,\r\n peerId,\r\n message: 'ICE connection failed'\r\n });\r\n }\r\n };\r\n\r\n // 연결 상태 변경\r\n connection.onconnectionstatechange = () => {\r\n this.logger.debug(`Connection state (${peerId}): ${connection.connectionState}`);\r\n\r\n this.emit('connectionStateChange', {\r\n peerId,\r\n state: connection.connectionState\r\n });\r\n\r\n if (connection.connectionState === 'connected') {\r\n this.emit('peerConnected', { peerId });\r\n } else if (connection.connectionState === 'disconnected' || connection.connectionState === 'failed') {\r\n this.emit('peerDisconnected', { peerId });\r\n }\r\n };\r\n\r\n // 협상 필요 (Perfect Negotiation)\r\n connection.onnegotiationneeded = async () => {\r\n // 1:1 통화에서 수동으로 offer를 생성할 때는 자동 negotiation 건너뛰기\r\n if (peerState.skipAutoNegotiation) {\r\n this.logger.debug(`Skipping auto negotiation for ${peerId} (manual offer will be sent)`);\r\n return;\r\n }\r\n\r\n try {\r\n peerState.makingOffer = true;\r\n await connection.setLocalDescription();\r\n\r\n this.emit('negotiationNeeded', {\r\n peerId,\r\n description: connection.localDescription\r\n });\r\n\r\n } catch (error) {\r\n this.logger.error(`Negotiation error (${peerId}):`, error);\r\n } finally {\r\n peerState.makingOffer = false;\r\n }\r\n };\r\n\r\n // 원격 트랙 수신\r\n connection.ontrack = (event) => {\r\n this.logger.info(`Remote track received from ${peerId}:`, event.track.kind);\r\n\r\n this.emit('remoteTrack', {\r\n peerId,\r\n track: event.track,\r\n streams: event.streams\r\n });\r\n };\r\n\r\n // 데이터 채널 수신\r\n connection.ondatachannel = (event) => {\r\n this.logger.info(`Data channel received from ${peerId}`);\r\n this.emit('dataChannel', {\r\n peerId,\r\n channel: event.channel\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * 연결에 트랙 추가\r\n * @private\r\n * @param {RTCPeerConnection} connection - 피어 연결\r\n */\r\n _addTracksToConnection(connection) {\r\n if (!this.localStream) return;\r\n\r\n const existingSenders = connection.getSenders();\r\n\r\n this.localStream.getTracks().forEach(track => {\r\n // 이미 추가된 트랙인지 확인\r\n const existingSender = existingSenders.find(sender =>\r\n sender.track && sender.track.kind === track.kind\r\n );\r\n\r\n if (!existingSender) {\r\n connection.addTrack(track, this.localStream);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Offer 생성 및 전송\r\n * @param {string} peerId\r\n * @returns {Promise<RTCSessionDescription>}\r\n */\r\n async createOffer(peerId) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n throw new Error(`Peer not found: ${peerId}`);\r\n }\r\n\r\n try {\r\n const offer = await peer.connection.createOffer();\r\n await peer.connection.setLocalDescription(offer);\r\n\r\n this.logger.debug(`Offer created for ${peerId}`);\r\n return peer.connection.localDescription;\r\n\r\n } catch (error) {\r\n this.logger.error(`Failed to create offer for ${peerId}:`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Answer 생성\r\n * @param {string} peerId\r\n * @returns {Promise<RTCSessionDescription>}\r\n */\r\n async createAnswer(peerId) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n throw new Error(`Peer not found: ${peerId}`);\r\n }\r\n\r\n try {\r\n const answer = await peer.connection.createAnswer();\r\n await peer.connection.setLocalDescription(answer);\r\n\r\n this.logger.debug(`Answer created for ${peerId}`);\r\n return peer.connection.localDescription;\r\n\r\n } catch (error) {\r\n this.logger.error(`Failed to create answer for ${peerId}:`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 원격 Description 설정 (Perfect Negotiation)\r\n * @param {string} peerId\r\n * @param {RTCSessionDescription} description\r\n */\r\n async handleRemoteDescription(peerId, description) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n throw new Error(`Peer not found: ${peerId}`);\r\n }\r\n\r\n const { connection, polite, makingOffer } = peer;\r\n\r\n // Perfect Negotiation: offer 충돌 감지\r\n const offerCollision = description.type === 'offer' &&\r\n (makingOffer || connection.signalingState !== 'stable');\r\n\r\n // Impolite는 충돌 시 상대 offer 무시\r\n peer.ignoreOffer = !polite && offerCollision;\r\n\r\n if (peer.ignoreOffer) {\r\n this.logger.debug(`Ignoring offer collision from ${peerId} (I am impolite)`);\r\n return;\r\n }\r\n\r\n try {\r\n // Perfect Negotiation: Polite가 충돌 시 자신의 offer를 roll back\r\n if (offerCollision && polite) {\r\n this.logger.debug(`Offer collision detected, rolling back local offer for ${peerId} (I am polite)`);\r\n await connection.setLocalDescription({ type: 'rollback' });\r\n }\r\n\r\n peer.isSettingRemoteAnswerPending = description.type === 'answer';\r\n await connection.setRemoteDescription(description);\r\n peer.isSettingRemoteAnswerPending = false;\r\n\r\n this.logger.debug(`Remote ${description.type} set for ${peerId}`);\r\n\r\n // Offer를 받았으면 Answer 생성\r\n if (description.type === 'offer') {\r\n const answer = await this.createAnswer(peerId);\r\n this.emit('answerCreated', { peerId, answer });\r\n }\r\n\r\n } catch (error) {\r\n this.logger.error(`Failed to set remote description for ${peerId}:`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * ICE Candidate 추가\r\n * @param {string} peerId\r\n * @param {RTCIceCandidate} candidate\r\n * @returns {Promise<boolean>} 추가 성공 여부 (false면 나중에 다시 시도 필요)\r\n */\r\n async addIceCandidate(peerId, candidate) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n this.logger.warn(`Peer not found for ICE candidate: ${peerId}`);\r\n return false;\r\n }\r\n\r\n // remoteDescription이 설정되지 않았으면 추가 불가\r\n if (!peer.connection.remoteDescription) {\r\n this.logger.debug(`Remote description not set yet for ${peerId}, ICE candidate queued`);\r\n return false;\r\n }\r\n\r\n try {\r\n await peer.connection.addIceCandidate(candidate);\r\n this.logger.debug(`ICE candidate added for ${peerId}`);\r\n return true;\r\n } catch (error) {\r\n if (!peer.ignoreOffer) {\r\n this.logger.error(`Failed to add ICE candidate for ${peerId}:`, error);\r\n }\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 특정 피어 연결 가져오기\r\n * @param {string} peerId\r\n * @returns {RTCPeerConnection|null}\r\n */\r\n getPeerConnection(peerId) {\r\n const peer = this.peers.get(peerId);\r\n return peer ? peer.connection : null;\r\n }\r\n\r\n /**\r\n * 모든 피어 ID 목록\r\n * @returns {string[]}\r\n */\r\n getPeerIds() {\r\n return Array.from(this.peers.keys());\r\n }\r\n\r\n /**\r\n * 피어 연결 종료\r\n * @param {string} peerId\r\n */\r\n closePeerConnection(peerId) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n return;\r\n }\r\n\r\n peer.connection.close();\r\n this.peers.delete(peerId);\r\n\r\n this.emit('peerClosed', { peerId });\r\n this.logger.info(`Peer connection closed: ${peerId}`);\r\n }\r\n\r\n /**\r\n * 모든 피어 연결 종료\r\n */\r\n closeAllPeerConnections() {\r\n this.peers.forEach((peer, peerId) => {\r\n peer.connection.close();\r\n this.logger.debug(`Peer connection closed: ${peerId}`);\r\n });\r\n this.peers.clear();\r\n this.emit('allPeersClosed', {});\r\n }\r\n\r\n /**\r\n * 트랙 교체\r\n * @param {MediaStreamTrack} oldTrack - 교체할 기존 트랙\r\n * @param {MediaStreamTrack} newTrack - 새 트랙\r\n * @returns {Promise<void>}\r\n */\r\n async replaceTrack(oldTrack, newTrack) {\r\n const promises = [];\r\n\r\n this.peers.forEach((peer, peerId) => {\r\n const sender = peer.connection.getSenders().find(s =>\r\n s.track && s.track.kind === oldTrack.kind\r\n );\r\n\r\n if (sender) {\r\n promises.push(\r\n sender.replaceTrack(newTrack)\r\n .then(() => this.logger.debug(`Track replaced for ${peerId}`))\r\n .catch(error => this.logger.error(`Failed to replace track for ${peerId}:`, error))\r\n );\r\n }\r\n });\r\n\r\n await Promise.all(promises);\r\n }\r\n\r\n /**\r\n * 연결 통계 가져오기\r\n * @param {string} peerId\r\n * @returns {Promise<RTCStatsReport|null>}\r\n */\r\n async getStats(peerId) {\r\n const peer = this.peers.get(peerId);\r\n if (!peer) {\r\n return null;\r\n }\r\n\r\n return peer.connection.getStats();\r\n }\r\n\r\n /**\r\n * 모든 연결 상태 요약\r\n * @returns {Object}\r\n */\r\n getConnectionSummary() {\r\n const summary = {};\r\n\r\n this.peers.forEach((peer, peerId) => {\r\n summary[peerId] = {\r\n connectionState: peer.connection.connectionState,\r\n iceConnectionState: peer.connection.iceConnectionState,\r\n signalingState: peer.connection.signalingState,\r\n polite: peer.polite\r\n };\r\n });\r\n\r\n return summary;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리\r\n */\r\n destroy() {\r\n this.closeAllPeerConnections();\r\n this.localStream = null;\r\n this.removeAllListeners();\r\n this.logger.info('PeerConnectionManager destroyed');\r\n }\r\n}\r\n\r\nexport default PeerConnectionManager;\r\n","/**\r\n * WebRTCClient\r\n * WebRTC 통화 기능 클라이언트\r\n */\r\n\r\nimport EventEmitter from '../utils/EventEmitter.js';\r\nimport Logger from '../utils/Logger.js';\r\nimport MediaStreamManager from './MediaStreamManager.js';\r\nimport PeerConnectionManager from './PeerConnectionManager.js';\r\nimport { WebSocketPaths, SignalTypes, ErrorTypes, DefaultConfig, LogLevel } from '../constants.js';\r\n\r\nclass WebRTCClient extends EventEmitter {\r\n /**\r\n * @param {Object} options\r\n * @param {Object} options.connectionManager - ConnectionManager 인스턴스\r\n * @param {Object} options.apiClient - ApiClient 인스턴스\r\n * @param {string} options.userId - 사용자 ID\r\n * @param {Object[]} [options.iceServers] - ICE 서버 설정\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n super();\r\n\r\n this.connectionManager = options.connectionManager;\r\n this.apiClient = options.apiClient;\r\n this.userId = options.userId;\r\n this.logLevel = options.logLevel || LogLevel.WARN;\r\n\r\n this.logger = new Logger(this.logLevel, 'WebRTCClient');\r\n\r\n // 미디어 및 피어 연결 매니저\r\n this.mediaManager = new MediaStreamManager({ logLevel: this.logLevel });\r\n this.peerManager = new PeerConnectionManager({\r\n iceServers: options.iceServers || DefaultConfig.iceServers,\r\n logLevel: this.logLevel\r\n });\r\n\r\n // 현재 통화 상태\r\n this.currentRoom = null;\r\n this.isGroupCall = false;\r\n this.participants = new Map();\r\n\r\n // ICE 서버 초기화 상태\r\n this._iceServersFetched = false;\r\n\r\n // 1:1 통화 대기 상태 (CALL_REQUEST 전송 후 CALL_ACCEPT 대기)\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n\r\n // ICE candidate 큐 (peer connection 생성 전에 도착한 candidate 저장)\r\n this._pendingIceCandidates = new Map();\r\n\r\n // 이벤트 연결\r\n this._setupEventHandlers();\r\n }\r\n\r\n /**\r\n * ICE 서버 설정 초기화 (TURN credentials 가져오기)\r\n * 통화 시작 전 호출하면 더 빠른 연결 가능\r\n * @returns {Promise<void>}\r\n */\r\n async initializeIceServers() {\r\n if (this._iceServersFetched) {\r\n return;\r\n }\r\n\r\n try {\r\n const data = await this.getTurnCredentials();\r\n if (data && data.iceServers) {\r\n this.peerManager.setIceServers(data.iceServers);\r\n this._iceServersFetched = true;\r\n this.logger.info('ICE servers fetched successfully');\r\n }\r\n } catch (error) {\r\n this.logger.warn('Failed to fetch TURN credentials, using fallback ICE servers:', error.message);\r\n this._setFallbackIceServers();\r\n }\r\n }\r\n\r\n /**\r\n * 기본 ICE 서버 설정 (Fallback)\r\n * @private\r\n */\r\n _setFallbackIceServers() {\r\n this.peerManager.setIceServers([\r\n { urls: 'stun:stun.l.google.com:19302' },\r\n { urls: 'stun:stun1.l.google.com:19302' }\r\n ]);\r\n }\r\n\r\n /**\r\n * 이벤트 핸들러 설정\r\n * @private\r\n */\r\n _setupEventHandlers() {\r\n // 미디어 이벤트\r\n this.mediaManager.on('streamStarted', (data) => this.emit('localStreamStarted', data));\r\n this.mediaManager.on('streamStopped', (data) => this.emit('localStreamStopped', data));\r\n this.mediaManager.on('videoToggled', () => this._sendMediaState());\r\n this.mediaManager.on('audioToggled', () => this._sendMediaState());\r\n this.mediaManager.on('screenShareStarted', (data) => this.emit('screenShareStarted', data));\r\n this.mediaManager.on('screenShareEnded', (data) => this.emit('screenShareEnded', data));\r\n this.mediaManager.on('error', (error) => this.emit('error', error));\r\n\r\n // 피어 연결 이벤트\r\n this.peerManager.on('remoteTrack', (data) => this.emit('remoteTrack', data));\r\n this.peerManager.on('peerConnected', (data) => this.emit('peerConnected', data));\r\n this.peerManager.on('peerDisconnected', (data) => this.emit('peerDisconnected', data));\r\n this.peerManager.on('peerClosed', (data) => this.emit('peerClosed', data));\r\n this.peerManager.on('error', (error) => this.emit('error', error));\r\n\r\n // ICE Candidate 전송\r\n this.peerManager.on('iceCandidate', ({ peerId, candidate }) => {\r\n this._sendSignal({\r\n type: SignalTypes.ICE_CANDIDATE,\r\n receiverId: peerId,\r\n data: { candidate }\r\n });\r\n });\r\n\r\n // Negotiation (Offer 전송)\r\n this.peerManager.on('negotiationNeeded', ({ peerId, description }) => {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_OFFER,\r\n receiverId: peerId,\r\n data: { sdp: description }\r\n });\r\n });\r\n\r\n // Answer 전송\r\n this.peerManager.on('answerCreated', ({ peerId, answer }) => {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_ANSWER,\r\n receiverId: peerId,\r\n data: { sdp: answer }\r\n });\r\n });\r\n }\r\n\r\n // ==================== REST API ====================\r\n\r\n /**\r\n * TURN 서버 인증 정보 가져오기\r\n * @returns {Promise<Object>}\r\n */\r\n async getTurnCredentials() {\r\n const response = await this.apiClient.get('/api/v1/webrtc/turn-credentials');\r\n return response.data;\r\n }\r\n\r\n /**\r\n * 통화방 생성\r\n * @public\r\n * @param {Object} data\r\n * @param {string} data.roomName - 방 이름\r\n * @param {boolean} data.isGroup - 그룹 통화 여부\r\n * @param {string} [data.chatRoomId] - 연결할 채팅방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async createCallRoom(data) {\r\n return this.apiClient.post('/api/v1/webrtc/rooms', data);\r\n }\r\n\r\n /**\r\n * 통화방 정보 조회\r\n * @public\r\n * @param {string} roomId - 통화방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async getCallRoom(roomId) {\r\n return this.apiClient.get(`/api/v1/webrtc/rooms/${roomId}`);\r\n }\r\n\r\n /**\r\n * 통화방 참여\r\n * @public\r\n * @param {string} roomId - 통화방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async joinCallRoomApi(roomId) {\r\n return this.apiClient.post(`/api/v1/webrtc/rooms/${roomId}/join`);\r\n }\r\n\r\n /**\r\n * 통화방 나가기\r\n * @public\r\n * @param {string} roomId - 통화방 ID\r\n * @returns {Promise<Object>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async leaveCallRoomApi(roomId) {\r\n return this.apiClient.delete(`/api/v1/webrtc/rooms/${roomId}/leave`);\r\n }\r\n\r\n // ==================== 통화 수신 대기 ====================\r\n\r\n /**\r\n * 통화 수신 대기 활성화\r\n * connect() 후 자동 호출되지만, 수동으로도 호출 가능\r\n * 개인 WebRTC 채널(/user/queue/webrtc)을 구독하여 초대 알림 수신\r\n * @public\r\n * @returns {Promise<void>}\r\n */\r\n async enableIncomingCalls() {\r\n if (this._incomingCallsEnabled) {\r\n this.logger.debug('Incoming calls already enabled');\r\n return;\r\n }\r\n\r\n await this._subscribeToDirectSignaling();\r\n this._incomingCallsEnabled = true;\r\n this.logger.info('Incoming calls enabled - now listening for call invitations');\r\n }\r\n\r\n /**\r\n * 통화 수신 대기 비활성화\r\n * @public\r\n */\r\n disableIncomingCalls() {\r\n if (!this._incomingCallsEnabled) {\r\n return;\r\n }\r\n\r\n const directDestination = WebSocketPaths.getWebRTCUserDestination();\r\n this.connectionManager.unsubscribe(directDestination);\r\n this._incomingCallsEnabled = false;\r\n this.logger.info('Incoming calls disabled');\r\n }\r\n\r\n /**\r\n * 통화 수신 대기 상태 확인\r\n * @public\r\n * @returns {boolean}\r\n */\r\n isIncomingCallsEnabled() {\r\n return this._incomingCallsEnabled || false;\r\n }\r\n\r\n // ==================== 통화 기능 ====================\r\n\r\n /**\r\n * 통화 시작 (미디어 및 구독)\r\n * @param {Object} options\r\n * @param {string} options.roomId - 통화방 ID\r\n * @param {boolean} [options.isGroup=false] - 그룹 통화 여부\r\n * @param {Object} [options.mediaConstraints] - 미디어 제약조건\r\n * @returns {Promise<MediaStream>}\r\n */\r\n async startCall(options) {\r\n const { roomId, isGroup = false, mediaConstraints = { video: true, audio: true } } = options;\r\n\r\n try {\r\n // TURN 서버 설정 초기화\r\n await this.initializeIceServers();\r\n\r\n // 로컬 미디어 획득\r\n const localStream = await this.mediaManager.getUserMedia(mediaConstraints);\r\n this.peerManager.setLocalStream(localStream);\r\n\r\n // 현재 방 설정\r\n this.currentRoom = roomId;\r\n this.isGroupCall = isGroup;\r\n\r\n // WebSocket 구독\r\n await this._subscribeToSignaling(roomId, isGroup);\r\n\r\n // 입장 시그널 전송\r\n this._sendSignal({\r\n type: SignalTypes.JOIN_ROOM,\r\n roomId\r\n });\r\n\r\n this.logger.info(`Call started in room: ${roomId}`);\r\n this.emit('callStarted', { roomId, isGroup, localStream });\r\n\r\n return localStream;\r\n\r\n } catch (error) {\r\n this.logger.error('Failed to start call:', error);\r\n this.emit('error', {\r\n type: ErrorTypes.PEER_CONNECTION_FAILED,\r\n message: error.message,\r\n error\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 1:1 통화 요청\r\n * @param {string} targetUserId - 상대방 사용자 ID\r\n * @param {Object} [mediaConstraints] - 미디어 제약조건\r\n */\r\n async callUser(targetUserId, mediaConstraints = { video: true, audio: true }) {\r\n try {\r\n // TURN 서버 설정 초기화\r\n await this.initializeIceServers();\r\n\r\n // 로컬 미디어 획득\r\n const localStream = await this.mediaManager.getUserMedia(mediaConstraints);\r\n this.peerManager.setLocalStream(localStream);\r\n\r\n // 1:1 통화 구독\r\n await this._subscribeToDirectSignaling();\r\n\r\n // CALL_ACCEPT 대기 상태 설정\r\n // PeerConnection은 CALL_ACCEPT를 받은 후에 생성하여 자동 negotiation 방지\r\n this._waitingForCallAccept = true;\r\n this._pendingCallTarget = targetUserId;\r\n\r\n // 통화 요청 시그널\r\n this._sendSignal({\r\n type: SignalTypes.CALL_REQUEST,\r\n receiverId: targetUserId\r\n });\r\n\r\n this.currentRoom = null;\r\n this.isGroupCall = false;\r\n\r\n this.emit('callRequested', { targetUserId, localStream });\r\n\r\n } catch (error) {\r\n this.logger.error('Failed to call user:', error);\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 통화 수락\r\n * @param {string} callerId - 발신자 ID\r\n * @param {Object} [mediaConstraints] - 미디어 제약조건\r\n */\r\n async acceptCall(callerId, mediaConstraints = { video: true, audio: true }) {\r\n try {\r\n // TURN 서버 설정 초기화\r\n await this.initializeIceServers();\r\n\r\n // 로컬 미디어 획득 (수신자도 미디어 필요)\r\n const localStream = await this.mediaManager.getUserMedia(mediaConstraints);\r\n this.peerManager.setLocalStream(localStream);\r\n\r\n // 피어 연결 생성 (Polite - 수신자)\r\n // skipAutoNegotiation: true - 수신자는 offer를 받아서 answer를 보내므로 자동 negotiation 불필요\r\n this.peerManager.createPeerConnection(callerId, true, { skipAutoNegotiation: true });\r\n\r\n // 수락 시그널\r\n this._sendSignal({\r\n type: SignalTypes.CALL_ACCEPT,\r\n receiverId: callerId\r\n });\r\n\r\n this.emit('callAccepted', { callerId });\r\n\r\n } catch (error) {\r\n this.logger.error('Failed to accept call:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 통화 거절\r\n * @param {string} callerId - 발신자 ID\r\n */\r\n rejectCall(callerId) {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_REJECT,\r\n receiverId: callerId\r\n });\r\n\r\n this.emit('callRejected', { callerId });\r\n }\r\n\r\n /**\r\n * 통화 취소 (발신자가 수신자 응답 전 취소)\r\n */\r\n cancelCall() {\r\n if (this._waitingForCallAccept && this._pendingCallTarget) {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_CANCEL,\r\n receiverId: this._pendingCallTarget\r\n });\r\n\r\n this.emit('callCancelled', { targetUserId: this._pendingCallTarget });\r\n\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n\r\n // 미디어 정리\r\n this.mediaManager.stopAll();\r\n }\r\n }\r\n\r\n /**\r\n * 통화 종료\r\n */\r\n endCall() {\r\n // 대기 중인 통화가 있으면 취소 처리\r\n if (this._waitingForCallAccept && this._pendingCallTarget) {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_CANCEL,\r\n receiverId: this._pendingCallTarget\r\n });\r\n }\r\n\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n\r\n if (this.currentRoom) {\r\n // 퇴장 시그널 전송\r\n this._sendSignal({\r\n type: SignalTypes.LEAVE_ROOM,\r\n roomId: this.currentRoom\r\n });\r\n\r\n // 구독 해제\r\n this._unsubscribeFromSignaling();\r\n }\r\n\r\n // 모든 피어에게 종료 시그널\r\n this.peerManager.getPeerIds().forEach(peerId => {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_END,\r\n receiverId: peerId\r\n });\r\n });\r\n\r\n // 정리\r\n this.peerManager.closeAllPeerConnections();\r\n this.mediaManager.stopAll();\r\n this.participants.clear();\r\n this._pendingIceCandidates.clear();\r\n\r\n const roomId = this.currentRoom;\r\n this.currentRoom = null;\r\n this.isGroupCall = false;\r\n\r\n this.emit('callEnded', { roomId });\r\n this.logger.info('Call ended');\r\n }\r\n\r\n // ==================== 미디어 제어 ====================\r\n\r\n /**\r\n * 비디오 토글\r\n * @returns {boolean}\r\n */\r\n toggleVideo() {\r\n return this.mediaManager.toggleVideo();\r\n }\r\n\r\n /**\r\n * 오디오 토글\r\n * @returns {boolean}\r\n */\r\n toggleAudio() {\r\n return this.mediaManager.toggleAudio();\r\n }\r\n\r\n /**\r\n * 화면 공유 시작\r\n * @returns {Promise<MediaStream>}\r\n */\r\n async startScreenShare() {\r\n const screenStream = await this.mediaManager.getDisplayMedia();\r\n\r\n // 피어들에게 화면 공유 트랙 전송\r\n const videoTrack = screenStream.getVideoTracks()[0];\r\n const localVideoTrack = this.mediaManager.getTrack('video');\r\n\r\n if (localVideoTrack && videoTrack) {\r\n await this.peerManager.replaceTrack(localVideoTrack, videoTrack);\r\n }\r\n\r\n // 화면 공유 종료 시 원복 — 복원 시점의 현재 카메라 트랙 사용 (공유 중 switchDevice 대응)\r\n videoTrack.onended = async () => {\r\n const currentVideoTrack = this.mediaManager.getTrack('video');\r\n if (currentVideoTrack) {\r\n await this.peerManager.replaceTrack(videoTrack, currentVideoTrack);\r\n }\r\n this.emit('screenShareEnded', {});\r\n };\r\n\r\n return screenStream;\r\n }\r\n\r\n /**\r\n * 화면 공유 중지\r\n */\r\n stopScreenShare() {\r\n const screenStream = this.mediaManager.getScreenStream();\r\n if (screenStream) {\r\n screenStream.getTracks().forEach(track => track.stop());\r\n }\r\n }\r\n\r\n /**\r\n * 로컬 스트림 가져오기\r\n * @public\r\n * @returns {MediaStream|null}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n getLocalStream() {\r\n return this.mediaManager.getLocalStream();\r\n }\r\n\r\n /**\r\n * 디바이스 목록 가져오기\r\n * @returns {Promise<Object>}\r\n */\r\n async getDevices() {\r\n return this.mediaManager.getDevices();\r\n }\r\n\r\n /**\r\n * 디바이스 전환\r\n * @param {string} deviceId\r\n * @param {string} kind - 'video' or 'audio'\r\n */\r\n async switchDevice(deviceId, kind) {\r\n // switchDevice가 localStream을 교체하므로, 교체 전 기존 트랙을 먼저 저장\r\n const oldTrack = this.mediaManager.getTrack(kind);\r\n const newTrack = await this.mediaManager.switchDevice(deviceId, kind);\r\n\r\n // 피어들에게 트랙 교체\r\n if (oldTrack) {\r\n await this.peerManager.replaceTrack(oldTrack, newTrack);\r\n }\r\n\r\n return newTrack;\r\n }\r\n\r\n /**\r\n * 비디오 상태 직접 설정\r\n * @public\r\n * @param {boolean} enabled\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n setVideoEnabled(enabled) {\r\n this.mediaManager.setVideoEnabled(enabled);\r\n this._sendMediaState();\r\n }\r\n\r\n /**\r\n * 오디오 상태 직접 설정\r\n * @public\r\n * @param {boolean} enabled\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n setAudioEnabled(enabled) {\r\n this.mediaManager.setAudioEnabled(enabled);\r\n this._sendMediaState();\r\n }\r\n\r\n /**\r\n * 디바이스 변경 감지 시작\r\n * @public\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n startDeviceChangeDetection() {\r\n this.mediaManager.startDeviceChangeDetection();\r\n this.mediaManager.on('deviceChange', (devices) => {\r\n this.emit('deviceChange', devices);\r\n });\r\n }\r\n\r\n /**\r\n * 디바이스 변경 감지 중지\r\n * @public\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n stopDeviceChangeDetection() {\r\n this.mediaManager.stopDeviceChangeDetection();\r\n }\r\n\r\n /**\r\n * 비디오 해상도/프레임레이트 변경\r\n * @public\r\n * @param {Object} constraints - { width, height, frameRate }\r\n * @returns {Promise<void>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async applyVideoConstraints(constraints) {\r\n return this.mediaManager.applyVideoConstraints(constraints);\r\n }\r\n\r\n /**\r\n * 현재 비디오 설정 가져오기\r\n * @public\r\n * @returns {MediaTrackSettings|null}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n getVideoSettings() {\r\n return this.mediaManager.getVideoSettings();\r\n }\r\n\r\n /**\r\n * 현재 오디오 설정 가져오기\r\n * @public\r\n * @returns {MediaTrackSettings|null}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n getAudioSettings() {\r\n return this.mediaManager.getAudioSettings();\r\n }\r\n\r\n // ==================== 시그널링 ====================\r\n\r\n /**\r\n * 시그널링 구독\r\n * @private\r\n */\r\n async _subscribeToSignaling(roomId, isGroup) {\r\n if (isGroup) {\r\n // 그룹 통화: /topic/webrtc/{roomId}\r\n const destination = WebSocketPaths.getWebRTCDestination(roomId);\r\n await this.connectionManager.subscribe(destination, (message) => {\r\n this._handleSignal(message);\r\n });\r\n }\r\n\r\n // 1:1 또는 그룹 모두에서 개인 큐 구독\r\n await this._subscribeToDirectSignaling();\r\n }\r\n\r\n /**\r\n * 1:1 시그널링 구독\r\n * @private\r\n */\r\n async _subscribeToDirectSignaling() {\r\n const destination = WebSocketPaths.getWebRTCUserDestination();\r\n await this.connectionManager.subscribe(destination, (message) => {\r\n this._handleSignal(message);\r\n });\r\n }\r\n\r\n /**\r\n * 시그널링 구독 해제\r\n * @private\r\n */\r\n _unsubscribeFromSignaling() {\r\n if (this.currentRoom && this.isGroupCall) {\r\n const destination = WebSocketPaths.getWebRTCDestination(this.currentRoom);\r\n this.connectionManager.unsubscribe(destination);\r\n }\r\n\r\n // 수신 대기가 활성화된 경우 direct 채널 구독 유지 (그룹 통화 종료 후에도 수신 가능)\r\n if (!this._incomingCallsEnabled) {\r\n const directDestination = WebSocketPaths.getWebRTCUserDestination();\r\n this.connectionManager.unsubscribe(directDestination);\r\n }\r\n }\r\n\r\n /**\r\n * 시그널 전송\r\n * @private\r\n */\r\n _sendSignal(signal) {\r\n const payload = {\r\n type: signal.type,\r\n roomId: signal.roomId || this.currentRoom,\r\n receiverId: signal.receiverId,\r\n data: signal.data\r\n };\r\n\r\n this.connectionManager.send(WebSocketPaths.WEBRTC_SIGNAL, payload);\r\n this.logger.debug('Signal sent:', payload);\r\n }\r\n\r\n /**\r\n * 미디어 상태 전송\r\n * @private\r\n */\r\n _sendMediaState() {\r\n if (!this.currentRoom && this.peerManager.getPeerIds().length === 0) {\r\n return;\r\n }\r\n\r\n const mediaState = {\r\n videoEnabled: this.mediaManager.isVideoEnabled(),\r\n audioEnabled: this.mediaManager.isAudioEnabled()\r\n };\r\n\r\n this._sendSignal({\r\n type: SignalTypes.VIDEO_STATE_CHANGED,\r\n data: mediaState\r\n });\r\n\r\n this.emit('mediaStateChanged', mediaState);\r\n }\r\n\r\n /**\r\n * 시그널 처리\r\n * @private\r\n */\r\n async _handleSignal(message) {\r\n const { type, senderId, data } = message;\r\n\r\n // 자신의 시그널 무시\r\n if (senderId === this.userId) {\r\n return;\r\n }\r\n\r\n this.logger.debug(`Signal received: ${type} from ${senderId}`);\r\n\r\n switch (type) {\r\n case SignalTypes.JOIN_ROOM:\r\n case SignalTypes.PEER_JOINED:\r\n await this._handleUserJoined(senderId);\r\n break;\r\n\r\n case SignalTypes.LEAVE_ROOM:\r\n case SignalTypes.PEER_LEFT:\r\n this._handleUserLeft(senderId);\r\n break;\r\n\r\n case SignalTypes.CALL_OFFER:\r\n await this._handleOffer(senderId, data.sdp);\r\n break;\r\n\r\n case SignalTypes.CALL_ANSWER:\r\n await this._handleAnswer(senderId, data.sdp);\r\n break;\r\n\r\n case SignalTypes.ICE_CANDIDATE:\r\n await this._handleIceCandidate(senderId, data.candidate);\r\n break;\r\n\r\n case SignalTypes.VIDEO_STATE_CHANGED:\r\n case SignalTypes.AUDIO_STATE_CHANGED:\r\n this._handleMediaState(senderId, data);\r\n break;\r\n\r\n case SignalTypes.CALL_REQUEST:\r\n // 통화 중이거나 통화 요청 대기 중이면 BUSY 응답\r\n if (this.isInCall() || this._waitingForCallAccept) {\r\n this._sendSignal({\r\n type: SignalTypes.CALL_BUSY,\r\n receiverId: senderId\r\n });\r\n this.emit('incomingCallWhileBusy', { callerId: senderId });\r\n } else {\r\n this.emit('incomingCall', { callerId: senderId });\r\n }\r\n break;\r\n\r\n case SignalTypes.CALL_BUSY:\r\n // 상대방이 통화 중\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n this.mediaManager.stopAll();\r\n this.emit('callBusy', { userId: senderId });\r\n break;\r\n\r\n case SignalTypes.CALL_INVITATION:\r\n // 그룹 통화 초대 (createCallRoom으로 초대받은 경우)\r\n this.emit('callInvitation', {\r\n callRoomId: data?.callRoomId || message.roomId,\r\n title: data?.title,\r\n hostUserId: data?.hostUserId || senderId,\r\n maxParticipants: data?.maxParticipants,\r\n createdAt: data?.createdAt\r\n });\r\n break;\r\n\r\n case SignalTypes.CALL_ACCEPT:\r\n await this._handleCallAccepted(senderId);\r\n break;\r\n\r\n case SignalTypes.CALL_REJECT:\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n // 발신자 미디어 정리 (거절당했으므로 통화 불가)\r\n this.mediaManager.stopAll();\r\n this.emit('callRejected', { userId: senderId });\r\n break;\r\n\r\n case SignalTypes.CALL_CANCEL:\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n this.emit('callCancelled', { userId: senderId });\r\n break;\r\n\r\n case SignalTypes.CALL_END:\r\n this._handleCallEnded(senderId);\r\n break;\r\n\r\n default:\r\n this.logger.warn(`Unknown signal type: ${type}`);\r\n }\r\n }\r\n\r\n /**\r\n * 사용자 입장 처리\r\n * @private\r\n */\r\n async _handleUserJoined(userId) {\r\n this.participants.set(userId, { joinedAt: new Date() });\r\n\r\n // userId 비교로 Polite/Impolite 결정 (작은 쪽이 Polite)\r\n // Perfect Negotiation에서 충돌 시 한 쪽만 양보하도록 보장\r\n const polite = this.userId < userId;\r\n this.peerManager.createPeerConnection(userId, polite);\r\n\r\n this.emit('userJoined', { userId });\r\n this.logger.info(`User joined: ${userId} (I am ${polite ? 'polite' : 'impolite'})`);\r\n }\r\n\r\n /**\r\n * 사용자 퇴장 처리\r\n * @private\r\n */\r\n _handleUserLeft(userId) {\r\n this.participants.delete(userId);\r\n this.peerManager.closePeerConnection(userId);\r\n\r\n this.emit('userLeft', { userId });\r\n this.logger.info(`User left: ${userId}`);\r\n }\r\n\r\n /**\r\n * Offer 처리\r\n * @private\r\n */\r\n async _handleOffer(senderId, sdp) {\r\n // 피어 연결이 없으면 생성\r\n if (!this.peerManager.getPeerConnection(senderId)) {\r\n // userId 비교로 Polite/Impolite 결정\r\n const polite = this.userId < senderId;\r\n // skipAutoNegotiation: true - offer를 먼저 처리한 후 answer 전송\r\n // 트랙 추가 시 자동 negotiation이 발생하면 offer 충돌 발생\r\n this.peerManager.createPeerConnection(senderId, polite, { skipAutoNegotiation: true });\r\n }\r\n\r\n await this.peerManager.handleRemoteDescription(senderId, sdp);\r\n\r\n // remoteDescription 설정 후 대기 중인 ICE candidates 처리\r\n await this._processPendingIceCandidates(senderId);\r\n }\r\n\r\n /**\r\n * Answer 처리\r\n * @private\r\n */\r\n async _handleAnswer(senderId, sdp) {\r\n await this.peerManager.handleRemoteDescription(senderId, sdp);\r\n // remoteDescription 설정 후 대기 중인 ICE candidates 처리\r\n await this._processPendingIceCandidates(senderId);\r\n }\r\n\r\n /**\r\n * ICE Candidate 처리\r\n * @private\r\n */\r\n async _handleIceCandidate(senderId, candidate) {\r\n // peer connection이 아직 없으면 큐에 저장\r\n if (!this.peerManager.getPeerConnection(senderId)) {\r\n this._queueIceCandidate(senderId, candidate);\r\n return;\r\n }\r\n\r\n // addIceCandidate가 false 반환하면 (remoteDescription 미설정) 큐에 저장\r\n const added = await this.peerManager.addIceCandidate(senderId, new RTCIceCandidate(candidate));\r\n if (!added) {\r\n this._queueIceCandidate(senderId, candidate);\r\n }\r\n }\r\n\r\n /**\r\n * ICE candidate를 큐에 저장\r\n * @private\r\n */\r\n _queueIceCandidate(peerId, candidate) {\r\n if (!this._pendingIceCandidates.has(peerId)) {\r\n this._pendingIceCandidates.set(peerId, []);\r\n }\r\n this._pendingIceCandidates.get(peerId).push(candidate);\r\n this.logger.debug(`ICE candidate queued for ${peerId}`);\r\n }\r\n\r\n /**\r\n * 대기 중인 ICE candidate 처리\r\n * @private\r\n */\r\n async _processPendingIceCandidates(peerId) {\r\n const candidates = this._pendingIceCandidates.get(peerId);\r\n if (!candidates || candidates.length === 0) {\r\n return;\r\n }\r\n\r\n this.logger.debug(`Processing ${candidates.length} pending ICE candidates for ${peerId}`);\r\n\r\n for (const candidate of candidates) {\r\n await this.peerManager.addIceCandidate(peerId, new RTCIceCandidate(candidate));\r\n }\r\n\r\n this._pendingIceCandidates.delete(peerId);\r\n }\r\n\r\n /**\r\n * 미디어 상태 처리\r\n * @private\r\n */\r\n _handleMediaState(userId, state) {\r\n this.emit('participantMediaState', {\r\n userId,\r\n videoEnabled: state.videoEnabled,\r\n audioEnabled: state.audioEnabled\r\n });\r\n }\r\n\r\n /**\r\n * 통화 수락 처리 (발신자 측에서 CALL_ACCEPT 수신 시)\r\n * @private\r\n */\r\n async _handleCallAccepted(userId) {\r\n // 대기 상태 초기화\r\n this._waitingForCallAccept = false;\r\n this._pendingCallTarget = null;\r\n\r\n // 이제 PeerConnection 생성 (Impolite - 발신자)\r\n // skipAutoNegotiation: true - 트랙 추가 시 자동 negotiation 건너뛰고 수동으로 offer 전송\r\n this.peerManager.createPeerConnection(userId, false, { skipAutoNegotiation: true });\r\n\r\n // Offer 생성 및 전송\r\n const offer = await this.peerManager.createOffer(userId);\r\n this._sendSignal({\r\n type: SignalTypes.CALL_OFFER,\r\n receiverId: userId,\r\n data: { sdp: offer }\r\n });\r\n\r\n this.emit('callAccepted', { userId });\r\n }\r\n\r\n /**\r\n * 통화 종료 처리\r\n * @private\r\n */\r\n _handleCallEnded(userId) {\r\n this.peerManager.closePeerConnection(userId);\r\n this.participants.delete(userId);\r\n\r\n this.emit('participantLeft', { userId });\r\n\r\n // 모든 참여자가 나갔으면 통화 종료\r\n if (this.peerManager.getPeerIds().length === 0) {\r\n this.endCall();\r\n }\r\n }\r\n\r\n // ==================== 유틸리티 ====================\r\n\r\n /**\r\n * 현재 통화 중인지 확인\r\n * @returns {boolean}\r\n */\r\n isInCall() {\r\n return this.currentRoom !== null || this.peerManager.getPeerIds().length > 0;\r\n }\r\n\r\n /**\r\n * 현재 통화방 ID\r\n * @returns {string|null}\r\n */\r\n getCurrentRoom() {\r\n return this.currentRoom;\r\n }\r\n\r\n /**\r\n * 참여자 목록\r\n * @returns {string[]}\r\n */\r\n getParticipants() {\r\n return Array.from(this.participants.keys());\r\n }\r\n\r\n /**\r\n * 연결 상태 요약\r\n * @public\r\n * @returns {Object}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n getConnectionSummary() {\r\n return this.peerManager.getConnectionSummary();\r\n }\r\n\r\n /**\r\n * 특정 피어의 연결 통계 가져오기 (디버깅/모니터링용)\r\n * @public\r\n * @param {string} peerId - 피어 ID\r\n * @returns {Promise<RTCStatsReport|null>}\r\n */\r\n // noinspection JSUnusedGlobalSymbols\r\n async getStats(peerId) {\r\n return this.peerManager.getStats(peerId);\r\n }\r\n\r\n /**\r\n * 미디어 상태\r\n * @returns {Object}\r\n */\r\n getMediaState() {\r\n return {\r\n videoEnabled: this.mediaManager.isVideoEnabled(),\r\n audioEnabled: this.mediaManager.isAudioEnabled(),\r\n streamInfo: this.mediaManager.getStreamInfo()\r\n };\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n this.mediaManager.setLogLevel(level);\r\n this.peerManager.setLogLevel(level);\r\n }\r\n\r\n /**\r\n * 리소스 정리\r\n */\r\n destroy() {\r\n this.endCall();\r\n this.mediaManager.destroy();\r\n this.peerManager.destroy();\r\n this.removeAllListeners();\r\n this.logger.info('WebRTCClient destroyed');\r\n }\r\n}\r\n\r\nexport default WebRTCClient;\r\n","/**\r\n * PushManager\r\n * FCM 웹 푸시 알림 관리 (Firebase 초기화, 서비스 워커, 권한, 토큰)\r\n */\r\n\r\nimport Logger from '../utils/Logger.js';\r\nimport { LogLevel } from '../constants.js';\r\n\r\n/**\r\n * 푸시 활성화 실패 사유 분류.\r\n *\r\n * <p>호출자가 {@code error.message} 문자열 매칭 대신 {@code error.code} 로 분기할 수 있도록\r\n * 표준화된 enum 값을 제공한다.</p>\r\n *\r\n * <ul>\r\n * <li>{@code UNSUPPORTED_BROWSER} — Notification / Service Worker 미지원 환경</li>\r\n * <li>{@code FIREBASE_NOT_INSTALLED} — firebase 패키지 동적 import 실패</li>\r\n * <li>{@code SW_REGISTER_FAILED} — 서비스 워커 등록 실패 (firebase-messaging-sw.js 누락 등)</li>\r\n * <li>{@code PERMISSION_DENIED} — 브라우저 알림 권한 거부 (이미 거부 상태 또는 이번 요청에서 거부)</li>\r\n * <li>{@code TOKEN_FAILED} — FCM 토큰 발급 실패</li>\r\n * <li>{@code SERVER_REGISTER_FAILED} — TalkFlow 서버에 디바이스 토큰 등록 실패</li>\r\n * </ul>\r\n */\r\nexport const PushErrorCode = Object.freeze({\r\n UNSUPPORTED_BROWSER: 'UNSUPPORTED_BROWSER',\r\n FIREBASE_NOT_INSTALLED: 'FIREBASE_NOT_INSTALLED',\r\n SW_REGISTER_FAILED: 'SW_REGISTER_FAILED',\r\n PERMISSION_DENIED: 'PERMISSION_DENIED',\r\n TOKEN_FAILED: 'TOKEN_FAILED',\r\n SERVER_REGISTER_FAILED: 'SERVER_REGISTER_FAILED'\r\n});\r\n\r\n/**\r\n * 푸시 활성화 과정에서 발생한 분류된 에러.\r\n *\r\n * <p>일반 {@link Error} 와 달리 {@code code} 필드로 분류되어 있어\r\n * 호출자가 사유별로 다른 UX (권한 거부 → 설정 가이드, 토큰 실패 → 재시도 등) 를 적용할 수 있다.</p>\r\n */\r\nexport class PushError extends Error {\r\n /**\r\n * @param {string} code - {@link PushErrorCode} 값 중 하나\r\n * @param {string} message - 사람이 읽는 메시지\r\n * @param {Error} [cause] - 원인 에러 (네트워크 실패 등)\r\n */\r\n constructor(code, message, cause) {\r\n super(message);\r\n this.name = 'PushError';\r\n this.code = code;\r\n if (cause !== undefined) {\r\n this.cause = cause;\r\n }\r\n }\r\n}\r\n\r\n// TalkFlow 기본 Firebase 설정 (고객별 분리 시 서버에서 내려받는 구조로 확장 가능)\r\nconst DEFAULT_FIREBASE_CONFIG = {\r\n apiKey: \"AIzaSyB8VhRg6rSvUI7K3Ua7h6sBpLvaQGmIkRc\",\r\n authDomain: \"chatting-c3e5d.firebaseapp.com\",\r\n projectId: \"chatting-c3e5d\",\r\n storageBucket: \"chatting-c3e5d.firebasestorage.app\",\r\n messagingSenderId: \"1020496565673\",\r\n appId: \"1:1020496565673:web:5039167257fd83f5ce20b8\"\r\n};\r\n\r\nconst DEFAULT_VAPID_KEY = 'BGP8qaSm1ntjWd4n9pc0lX_rw4BmMxm9u4pvRIANCitbmYaV0iy-gn05suTKBo88kUBejxdxM8sb6x2nt3avu8c';\r\nconst PENDING_NAVIGATION_DB_NAME = 'talkflowPush';\r\nconst PENDING_NAVIGATION_STORE_NAME = 'pendingNavigation';\r\n// SW 와 동일한 key 규칙 사용 — 멀티테넌트에서 다른 projectId 의 pending 을 건드리지 않도록 한다.\r\nconst PENDING_NAVIGATION_KEY_PREFIX = 'pending-room';\r\nconst PENDING_NAVIGATION_LEGACY_KEY = 'pending-room';\r\n\r\n// deviceId 저장 — 같은 IndexedDB 의 별도 store 사용 (XSS 방어 + 기존 인프라 재사용).\r\n// DB 버전은 2 로 bump — v1 에서 pendingNavigation 만 있던 상태에서 deviceInfo store 추가.\r\nconst DEVICE_STORE_NAME = 'deviceInfo';\r\nconst DEVICE_ID_KEY = 'device-id';\r\nconst DEVICE_ID_DB_VERSION = 2;\r\n// 기존 localStorage 에 저장된 deviceId 를 IndexedDB 로 옮긴 뒤 제거하기 위한 legacy key.\r\nconst LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY = 'talkflow_device_id';\r\n\r\nfunction buildPendingNavigationKey(projectId) {\r\n if (projectId && typeof projectId === 'string' && projectId.trim() !== '') {\r\n return PENDING_NAVIGATION_KEY_PREFIX + ':' + projectId;\r\n }\r\n return PENDING_NAVIGATION_LEGACY_KEY;\r\n}\r\n\r\nfunction openPendingNavigationDb() {\r\n if (typeof indexedDB === 'undefined') {\r\n return Promise.resolve(null);\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const request = indexedDB.open(PENDING_NAVIGATION_DB_NAME, DEVICE_ID_DB_VERSION);\r\n\r\n request.onupgradeneeded = () => {\r\n const db = request.result;\r\n // v1 → v2: deviceInfo store 추가.\r\n // 기존 pendingNavigation store 는 그대로 유지 (contains 체크로 멱등).\r\n if (!db.objectStoreNames.contains(PENDING_NAVIGATION_STORE_NAME)) {\r\n db.createObjectStore(PENDING_NAVIGATION_STORE_NAME, { keyPath: 'id' });\r\n }\r\n if (!db.objectStoreNames.contains(DEVICE_STORE_NAME)) {\r\n db.createObjectStore(DEVICE_STORE_NAME, { keyPath: 'id' });\r\n }\r\n };\r\n\r\n request.onsuccess = () => resolve(request.result);\r\n request.onerror = () => reject(request.error || new Error('IndexedDB open failed'));\r\n });\r\n}\r\n\r\n/**\r\n * IndexedDB 에서 deviceId 를 읽는다.\r\n * @returns {Promise<string|null>} 저장된 deviceId, 없으면 null. IndexedDB 사용 불가 시 null.\r\n */\r\nfunction readDeviceIdFromIndexedDb() {\r\n return openPendingNavigationDb().then(db => {\r\n if (!db) return null;\r\n return new Promise((resolve, reject) => {\r\n const tx = db.transaction(DEVICE_STORE_NAME, 'readonly');\r\n const store = tx.objectStore(DEVICE_STORE_NAME);\r\n const request = store.get(DEVICE_ID_KEY);\r\n request.onsuccess = () => {\r\n const record = request.result;\r\n resolve(record && record.value ? record.value : null);\r\n };\r\n request.onerror = () => reject(request.error || new Error('IndexedDB read failed'));\r\n tx.oncomplete = () => db.close();\r\n tx.onerror = () => db.close();\r\n tx.onabort = () => db.close();\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * IndexedDB 에 deviceId 를 저장한다.\r\n *\r\n * <p><b>반환값 의미</b>:</p>\r\n * <ul>\r\n * <li>{@code true} — transaction 이 complete 까지 성공적으로 완료됨.</li>\r\n * <li>{@code false} — open / transaction 실패, IDB 사용 불가, 기타 예외.</li>\r\n * </ul>\r\n *\r\n * <p>호출자는 반환값이 {@code true} 일 때만 \"영속성 확보\" 로 간주해야 한다.\r\n * 특히 legacy localStorage 정리는 read-back 검증까지 거친 뒤 수행.</p>\r\n *\r\n * @param {string} deviceId\r\n * @returns {Promise<boolean>}\r\n */\r\nfunction writeDeviceIdToIndexedDb(deviceId) {\r\n return openPendingNavigationDb().then(db => {\r\n if (!db) return false;\r\n return new Promise(resolve => {\r\n try {\r\n const tx = db.transaction(DEVICE_STORE_NAME, 'readwrite');\r\n tx.objectStore(DEVICE_STORE_NAME).put({ id: DEVICE_ID_KEY, value: deviceId });\r\n tx.oncomplete = () => { db.close(); resolve(true); };\r\n tx.onerror = () => { db.close(); resolve(false); };\r\n tx.onabort = () => { db.close(); resolve(false); };\r\n } catch (e) {\r\n try { db.close(); } catch (_) { /* ignore */ }\r\n resolve(false);\r\n }\r\n });\r\n }).catch(() => false);\r\n}\r\n\r\n/**\r\n * 신규 deviceId 생성.\r\n */\r\nfunction generateDeviceId() {\r\n const random = (typeof crypto !== 'undefined' && crypto.randomUUID)\r\n ? crypto.randomUUID()\r\n : Date.now().toString(36) + Math.random().toString(36).substring(2);\r\n return 'web-' + random;\r\n}\r\n\r\nasync function consumePendingRoomFromIndexedDB(projectId) {\r\n const db = await openPendingNavigationDb();\r\n if (!db) {\r\n return null;\r\n }\r\n\r\n // 1차: projectId 별 key 를 조회한다.\r\n // 2차 (mixed rollout 호환): projectId 가 있지만 1차 miss 인 경우,\r\n // legacy 전역 key 도 fallback 으로 조회한다. 서버 배포 전 또는 구버전 SW 에서 발생한\r\n // projectId 없는 payload 는 legacy key (pending-room) 에 저장되어 있을 수 있다.\r\n // legacy record 는 반드시 record.projectId 가 비어있어야 안전하게 소비한다 —\r\n // 다른 프로젝트의 값이 섞일 여지를 원천 차단.\r\n const primaryKey = buildPendingNavigationKey(projectId);\r\n const shouldTryLegacyFallback = !!projectId && primaryKey !== PENDING_NAVIGATION_LEGACY_KEY;\r\n\r\n return new Promise((resolve, reject) => {\r\n const transaction = db.transaction(PENDING_NAVIGATION_STORE_NAME, 'readwrite');\r\n const store = transaction.objectStore(PENDING_NAVIGATION_STORE_NAME);\r\n\r\n let pendingRoomId = null;\r\n let finalizedKey = null;\r\n\r\n const primaryRequest = store.get(primaryKey);\r\n\r\n primaryRequest.onsuccess = () => {\r\n const record = primaryRequest.result;\r\n // 저장된 record 의 projectId 와 요청한 projectId 가 일치할 때만 소비.\r\n // key 분리가 정상 작동하면 이 체크는 리던던트이지만, 레거시 전역 key 로 저장된\r\n // 레코드를 다른 프로젝트가 실수로 소비하는 것을 막기 위한 방어.\r\n if (record && record.roomId) {\r\n const recordProjectId = record.projectId || null;\r\n const requestedProjectId = projectId || null;\r\n if (recordProjectId === requestedProjectId) {\r\n pendingRoomId = record.roomId;\r\n finalizedKey = primaryKey;\r\n store.delete(primaryKey);\r\n return;\r\n }\r\n }\r\n\r\n // 1차 miss — legacy fallback 시도 (mixed rollout 호환 경로).\r\n if (!shouldTryLegacyFallback) {\r\n return;\r\n }\r\n\r\n const legacyRequest = store.get(PENDING_NAVIGATION_LEGACY_KEY);\r\n legacyRequest.onsuccess = () => {\r\n const legacyRecord = legacyRequest.result;\r\n // legacy record 는 projectId 가 null/undefined 인 경우에만 안전하게 소비.\r\n // SW v1 또는 구버전 서버가 projectId 없이 저장한 경우에 해당한다.\r\n if (\r\n legacyRecord &&\r\n legacyRecord.roomId &&\r\n !legacyRecord.projectId\r\n ) {\r\n pendingRoomId = legacyRecord.roomId;\r\n finalizedKey = PENDING_NAVIGATION_LEGACY_KEY;\r\n store.delete(PENDING_NAVIGATION_LEGACY_KEY);\r\n }\r\n };\r\n // legacyRequest.onerror 는 별도 처리하지 않는다 — primary 가 성공했다면 transaction 은\r\n // 이미 유효하고, 실패하면 transaction.onerror 로 넘어간다.\r\n };\r\n\r\n primaryRequest.onerror = () => {\r\n reject(primaryRequest.error || new Error('IndexedDB read failed'));\r\n };\r\n\r\n transaction.oncomplete = () => {\r\n db.close();\r\n // finalizedKey 를 쓰진 않지만, 추후 디버깅/로그 확장 시 어느 key 를 소비했는지 구분 가능.\r\n void finalizedKey;\r\n resolve(pendingRoomId);\r\n };\r\n\r\n transaction.onerror = () => {\r\n db.close();\r\n reject(transaction.error || new Error('IndexedDB transaction failed'));\r\n };\r\n\r\n transaction.onabort = () => {\r\n db.close();\r\n reject(transaction.error || new Error('IndexedDB transaction aborted'));\r\n };\r\n });\r\n}\r\n\r\nclass PushManager {\r\n /**\r\n * @param {Object} options\r\n * @param {Object} options.apiClient - ApiClient 인스턴스\r\n * @param {string} [options.projectId] - 프로젝트 ID (멀티테넌트 환경에서 알림 라우팅 격리용)\r\n * @param {Object} [options.firebaseConfig] - Firebase 설정 (기본: TalkFlow 설정)\r\n * @param {string} [options.vapidKey] - VAPID Key (기본: TalkFlow 키)\r\n * @param {string} [options.serviceWorkerPath] - 서비스 워커 경로 (기본: '/firebase-messaging-sw.js')\r\n * @param {number} [options.logLevel] - 로그 레벨\r\n */\r\n constructor(options) {\r\n this.apiClient = options.apiClient;\r\n this.projectId = options.projectId || null;\r\n this.firebaseConfig = options.firebaseConfig || DEFAULT_FIREBASE_CONFIG;\r\n this.vapidKey = options.vapidKey || DEFAULT_VAPID_KEY;\r\n this.serviceWorkerPath = options.serviceWorkerPath || '/firebase-messaging-sw.js';\r\n this.logger = new Logger(options.logLevel || LogLevel.WARN, 'PushManager');\r\n\r\n this._messaging = null;\r\n this._currentToken = null;\r\n this._enabled = false;\r\n this._enablePromise = null;\r\n this._foregroundMessageUnsubscribe = null;\r\n this._swRegistration = null;\r\n this._projectRegisteredToSW = false;\r\n this._visibilityHandler = null;\r\n this._deviceIdCache = null; // IndexedDB 조회 결과 메모리 캐시 (세션 단위)\r\n }\r\n\r\n /**\r\n * 저장된 pending roomId 를 소비한다.\r\n *\r\n * @param {string} [projectId] - 소비할 projectId. 지정 시 해당 projectId 로 저장된 pending 만 소비.\r\n * 미지정 시 레거시 전역 key 만 조회 (구버전 호환).\r\n * @returns {Promise<string|null>}\r\n */\r\n static async consumePendingRoom(projectId) {\r\n return consumePendingRoomFromIndexedDB(projectId);\r\n }\r\n\r\n /**\r\n * 현재 브라우저의 푸시 권한 상태를 조회한다.\r\n *\r\n * <p>{@link PushManager} 인스턴스가 없어도 (즉, {@code enablePushNotifications()} 호출 전) 호출 가능하다.\r\n * 호출자가 enable 호출 <b>전에</b> UI 분기를 결정할 때 사용한다.</p>\r\n *\r\n * <p>반환값:</p>\r\n * <ul>\r\n * <li>{@code 'granted'} — 이미 허용. enable 호출 시 권한창 안 뜨고 토큰 발급으로 즉시 진행.</li>\r\n * <li>{@code 'default'} — 아직 묻지 않음. enable 호출 시 권한창 표시.</li>\r\n * <li>{@code 'denied'} — 거부됨. enable 호출 시 즉시 PERMISSION_DENIED 로 실패 (브라우저 정책상 권한창 안 뜸).</li>\r\n * <li>{@code 'unsupported'} — Notification 또는 Service Worker 미지원 환경 (Safari iOS 등).</li>\r\n * </ul>\r\n *\r\n * @returns {'granted'|'denied'|'default'|'unsupported'}\r\n */\r\n static getPermissionState() {\r\n if (typeof window === 'undefined' || typeof Notification === 'undefined') {\r\n return 'unsupported';\r\n }\r\n if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {\r\n return 'unsupported';\r\n }\r\n return Notification.permission; // 'granted' | 'denied' | 'default'\r\n }\r\n\r\n /**\r\n * 푸시 알림 활성화.\r\n * Firebase 초기화 → 서비스 워커 등록 → 권한 요청 → 토큰 획득 → 서버 등록.\r\n *\r\n * <p>토큰은 SDK 내부에서만 관리되므로 호출자는 반환값을 받을 필요가 없습니다.\r\n * 상태 확인은 {@link #isEnabled} 로, 에러는 throw 로 전달됩니다.</p>\r\n *\r\n * @returns {Promise<void>}\r\n * @throws {Error} 브라우저 미지원, 권한 거부, Firebase 미설치 등\r\n */\r\n async enable() {\r\n if (this._enabled) {\r\n this.logger.debug('푸시 알림이 이미 활성화되어 있습니다.');\r\n return;\r\n }\r\n\r\n if (this._enablePromise) {\r\n this.logger.debug('푸시 알림 활성화가 이미 진행 중입니다.');\r\n return this._enablePromise;\r\n }\r\n\r\n this._enablePromise = this._enableInternal();\r\n\r\n try {\r\n return await this._enablePromise;\r\n } finally {\r\n this._enablePromise = null;\r\n }\r\n }\r\n\r\n async _enableInternal() {\r\n // 브라우저 환경 체크\r\n if (typeof window === 'undefined' || !('Notification' in window)) {\r\n throw new PushError(\r\n PushErrorCode.UNSUPPORTED_BROWSER,\r\n '푸시 알림은 브라우저 환경에서만 사용 가능합니다'\r\n );\r\n }\r\n\r\n if (!('serviceWorker' in navigator)) {\r\n throw new PushError(\r\n PushErrorCode.UNSUPPORTED_BROWSER,\r\n '이 브라우저는 서비스 워커를 지원하지 않습니다'\r\n );\r\n }\r\n\r\n // 권한이 이미 거부된 상태면 requestPermission() 은 즉시 'denied' 를 반환하고\r\n // 권한창은 뜨지 않는다. 명확한 분류 에러로 즉시 throw 하여 호출자가\r\n // \"브라우저 설정에서 풀어달라\" 가이드를 띄울 수 있도록 한다.\r\n if (Notification.permission === 'denied') {\r\n throw new PushError(\r\n PushErrorCode.PERMISSION_DENIED,\r\n '알림 권한이 이미 거부되어 있습니다. 브라우저 사이트 설정에서 허용해주세요.'\r\n );\r\n }\r\n\r\n // Firebase 동적 로드\r\n let firebaseApp, firebaseMessaging;\r\n try {\r\n firebaseApp = await import('firebase/app');\r\n firebaseMessaging = await import('firebase/messaging');\r\n } catch (e) {\r\n throw new PushError(\r\n PushErrorCode.FIREBASE_NOT_INSTALLED,\r\n 'firebase 패키지가 설치되지 않았습니다. npm install firebase 를 실행하세요.',\r\n e\r\n );\r\n }\r\n\r\n // Firebase 초기화 (이미 초기화된 경우 기존 앱 사용)\r\n // 고객 앱의 기존 Firebase와 충돌 방지 — named app으로 분리\r\n let app;\r\n try {\r\n app = firebaseApp.getApp('talkflow');\r\n } catch {\r\n app = firebaseApp.initializeApp(this.firebaseConfig, 'talkflow');\r\n }\r\n\r\n // 서비스 워커 등록\r\n const registration = await this._registerServiceWorker();\r\n\r\n // 알림 권한 요청\r\n // - 'default' 상태: 브라우저가 권한창을 표시하고 사용자 응답을 대기\r\n // - 'granted' 상태: 즉시 'granted' 반환 (창 안 뜸)\r\n // - 'denied' 상태: 위 가드에서 이미 throw 했으므로 여기 도달 불가\r\n const permission = await Notification.requestPermission();\r\n if (permission !== 'granted') {\r\n throw new PushError(\r\n PushErrorCode.PERMISSION_DENIED,\r\n '알림 권한이 거부되었습니다. 브라우저 설정에서 허용해주세요.'\r\n );\r\n }\r\n\r\n // FCM 토큰 획득\r\n this._messaging = firebaseMessaging.getMessaging(app);\r\n\r\n const tokenOptions = { serviceWorkerRegistration: registration };\r\n if (this.vapidKey) {\r\n tokenOptions.vapidKey = this.vapidKey;\r\n }\r\n\r\n try {\r\n this._currentToken = await firebaseMessaging.getToken(this._messaging, tokenOptions);\r\n } catch (e) {\r\n throw new PushError(\r\n PushErrorCode.TOKEN_FAILED,\r\n 'FCM 토큰 획득 중 오류 발생: ' + (e?.message || e),\r\n e\r\n );\r\n }\r\n\r\n if (!this._currentToken) {\r\n throw new PushError(\r\n PushErrorCode.TOKEN_FAILED,\r\n 'FCM 토큰 획득 실패 (응답이 비어있음)'\r\n );\r\n }\r\n\r\n // 서버에 토큰 등록\r\n await this._registerTokenToServer(this._currentToken);\r\n\r\n // 포그라운드 메시지 수신 핸들러\r\n if (this._foregroundMessageUnsubscribe) {\r\n this._foregroundMessageUnsubscribe();\r\n }\r\n\r\n this._foregroundMessageUnsubscribe = firebaseMessaging.onMessage(this._messaging, (payload) => {\r\n this.logger.debug('포그라운드 푸시 수신:', payload);\r\n this._onForegroundMessage(payload);\r\n });\r\n\r\n // SW 등록 객체를 보관 — 이후 projectId 재등록에 사용한다.\r\n this._swRegistration = registration;\r\n\r\n // SW 에 projectId 등록.\r\n // 멀티테넌트 환경에서 notificationclick 시 SW 가 \"어느 창이 어느 project 인지\"를 알아야\r\n // 다른 프로젝트 창에 NAVIGATE_TO_ROOM 을 보내지 않는다.\r\n await this._registerProjectToSW();\r\n\r\n // SW terminate 로 Map 이 소실될 수 있으므로 visibility 복귀 시 재등록.\r\n this._installVisibilityReRegister();\r\n\r\n // SW 업데이트 체크 (CDN handler 갱신 전파)\r\n try {\r\n await registration.update();\r\n } catch (e) {\r\n this.logger.debug('SW 업데이트 체크 실패 (무시):', e.message);\r\n }\r\n\r\n // SW 버전 감지 — 구버전 경고 용도로만 사용.\r\n // 이 값으로 서버/SDK 가 기능 분기(image 지원 여부 등)를 판단하면 안 된다.\r\n const swVersion = await this._getSWVersion(registration);\r\n if (!swVersion) {\r\n this.logger.warn(\r\n '[TalkFlow] 서비스 워커가 구버전(v1)입니다. ' +\r\n '이미지 미리보기 등 리치 알림 기능을 사용하려면 firebase-messaging-sw.js 를 ' +\r\n 'v2 로 업데이트하세요. 가이드: https://docs.talkflow.ai/push/sw-upgrade'\r\n );\r\n } else {\r\n this.logger.debug('SW 버전:', swVersion);\r\n }\r\n\r\n this._enabled = true;\r\n this.logger.info('푸시 알림 활성화 완료');\r\n }\r\n\r\n /**\r\n * 서비스 워커 등록.\r\n * updateViaCache: 'none' 으로 등록하여 registration.update() 시\r\n * importScripts 로 로드하는 CDN handler 까지 캐시 없이 갱신되도록 한다.\r\n * https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register\r\n * @private\r\n */\r\n async _registerServiceWorker() {\r\n try {\r\n const registration = await navigator.serviceWorker.register(\r\n this.serviceWorkerPath,\r\n { updateViaCache: 'none' }\r\n );\r\n this.logger.debug('서비스 워커 등록 완료:', this.serviceWorkerPath);\r\n return registration;\r\n } catch (error) {\r\n throw new PushError(\r\n PushErrorCode.SW_REGISTER_FAILED,\r\n `서비스 워커 등록 실패: ${this.serviceWorkerPath}\\n` +\r\n '프로젝트 public 폴더에 firebase-messaging-sw.js 파일을 배치하세요.',\r\n error\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * 서버에 디바이스 토큰 등록\r\n * @private\r\n */\r\n async _registerTokenToServer(token) {\r\n try {\r\n const deviceId = await this._getDeviceId();\r\n await this.apiClient.post('/api/v1/push/devices', {\r\n deviceToken: token,\r\n deviceType: 'WEB',\r\n deviceId\r\n });\r\n this.logger.info('디바이스 토큰 서버 등록 완료');\r\n } catch (error) {\r\n this.logger.error('디바이스 토큰 서버 등록 실패:', error);\r\n // PushError 가 아닌 경우 (네트워크 / 서버 5xx 등) SERVER_REGISTER_FAILED 로 wrap.\r\n // 이미 PushError 면 그대로 전파 (분류 보존).\r\n if (error instanceof PushError) {\r\n throw error;\r\n }\r\n throw new PushError(\r\n PushErrorCode.SERVER_REGISTER_FAILED,\r\n '디바이스 토큰 서버 등록 실패: ' + (error?.message || error),\r\n error\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * 브라우저 고유 식별자 조회/생성 (IndexedDB 기반).\r\n *\r\n * <p>순서:</p>\r\n * <ol>\r\n * <li>IndexedDB 에서 기존 deviceId 조회</li>\r\n * <li>없으면 localStorage 에 있는 legacy 값 있는지 확인 (기존 SDK 버전 설치 이력)</li>\r\n * <li>legacy 있으면 IndexedDB 로 이관 + localStorage 키 제거</li>\r\n * <li>둘 다 없으면 신규 생성 + IndexedDB 저장</li>\r\n * </ol>\r\n *\r\n * <p>IndexedDB 사용 불가 환경 (private mode 등) 에서는 in-memory fallback — 세션 단위\r\n * 새 deviceId 사용. 완벽한 영속성은 없지만 기본 기능은 유지.</p>\r\n *\r\n * @private\r\n * @returns {Promise<string>}\r\n */\r\n async _getDeviceId() {\r\n // 메모리 캐시 — 동일 세션 내 반복 호출 방지.\r\n if (this._deviceIdCache) {\r\n return this._deviceIdCache;\r\n }\r\n\r\n try {\r\n // 1) IndexedDB 에서 조회\r\n let deviceId = await readDeviceIdFromIndexedDb();\r\n\r\n // 2) 없으면 localStorage legacy 값 확인 + 이관\r\n if (!deviceId && typeof localStorage !== 'undefined') {\r\n const legacy = localStorage.getItem(LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY);\r\n if (legacy) {\r\n deviceId = legacy;\r\n // IDB 저장 성공 + read-back 검증 둘 다 통과한 경우에만 localStorage 정리.\r\n // 실패하거나 검증 miss 면 legacy 값 보존해 다음 방문에 재시도 가능.\r\n const saved = await writeDeviceIdToIndexedDb(deviceId);\r\n if (saved) {\r\n const readBack = await readDeviceIdFromIndexedDb();\r\n if (readBack === deviceId) {\r\n try {\r\n localStorage.removeItem(LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY);\r\n this.logger.debug('deviceId migrated from localStorage to IndexedDB');\r\n } catch (_) { /* quota/private mode 예외 무시 — legacy 남아도 다음에 재이관 시도 */ }\r\n } else {\r\n // 저장 tx 는 complete 됐는데 read-back 은 다르게 나오는 극단 케이스.\r\n // localStorage 보존 — 다음 방문에 재시도.\r\n this.logger.warn('deviceId IDB read-back 불일치, localStorage 보존');\r\n }\r\n } else {\r\n this.logger.warn('deviceId IDB 저장 실패 (private mode / quota 등), localStorage 보존');\r\n }\r\n }\r\n }\r\n\r\n // 3) 그래도 없으면 신규 생성 + 저장 시도.\r\n // 저장 실패해도 in-memory 캐시로 세션 단위 유효하므로 푸시 등록은 진행됨.\r\n // (영속화 실패는 다음 방문에 새 deviceId 생성 → 서버 쪽 device 매핑 갱신은 불가피)\r\n if (!deviceId) {\r\n deviceId = generateDeviceId();\r\n const saved = await writeDeviceIdToIndexedDb(deviceId);\r\n if (!saved) {\r\n this.logger.warn('신규 deviceId IDB 저장 실패, 이번 세션만 유효');\r\n }\r\n }\r\n\r\n this._deviceIdCache = deviceId;\r\n return deviceId;\r\n } catch (error) {\r\n // IndexedDB 장애 (open / read / transaction throw 등) 시 fallback.\r\n // IDB 가 현재 실패하므로 localStorage 를 삭제하지 않는다 — 다음 방문에 IDB 회복되면\r\n // try 블록의 이관 경로가 다시 시도됨.\r\n this.logger.warn('IndexedDB deviceId 조회/저장 실패, fallback:', error);\r\n if (!this._deviceIdCache) {\r\n // 1) localStorage legacy 우선 확인 — 기존 사용자 device 연속성 유지.\r\n // IDB 가 실패해도 localStorage 가 읽히면 기존 deviceId 재사용.\r\n let fallbackId = null;\r\n if (typeof localStorage !== 'undefined') {\r\n try {\r\n fallbackId = localStorage.getItem(LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY);\r\n } catch (_) { /* private mode 등 — localStorage 도 접근 불가 */ }\r\n }\r\n // 2) legacy 도 없으면 신규 생성 (in-memory 단일 세션).\r\n this._deviceIdCache = fallbackId || generateDeviceId();\r\n }\r\n return this._deviceIdCache;\r\n }\r\n }\r\n\r\n /**\r\n * 포그라운드 메시지 수신 콜백.\r\n * TalkFlowClient에서 오버라이드하여 activeRoom 기반 suppress 처리.\r\n * @param {Object} payload - FCM 메시지 페이로드\r\n */\r\n _onForegroundMessage(payload) {\r\n // TalkFlowClient에서 오버라이드\r\n }\r\n\r\n /**\r\n * 서비스 워커가 cold start 시 저장한 pending roomId 를 소비하고 삭제한다.\r\n * 앱 부팅 시 호출하여 알림 클릭으로 열려야 할 방으로 라우팅할 때 사용한다.\r\n *\r\n * <p>내부 projectId 가 설정되어 있으면 해당 프로젝트의 pending 만 소비한다.\r\n * 다른 프로젝트의 pending 은 건드리지 않아 그 프로젝트가 나중에 꺼낼 수 있다.</p>\r\n *\r\n * @returns {Promise<string|null>} pending roomId 또는 null\r\n */\r\n async consumePendingRoom() {\r\n return PushManager.consumePendingRoom(this.projectId);\r\n }\r\n\r\n /**\r\n * 현재 푸시 상태를 정리한다.\r\n * 로그아웃 또는 다른 사용자로 전환할 때 foreground listener 와 활성화 상태를 초기화한다.\r\n * SW 에 등록된 projectId 도 해제한다.\r\n */\r\n reset() {\r\n if (this._foregroundMessageUnsubscribe) {\r\n try {\r\n this._foregroundMessageUnsubscribe();\r\n } catch (error) {\r\n this.logger.debug('포그라운드 푸시 리스너 해제 실패 (무시):', error?.message || error);\r\n }\r\n }\r\n\r\n // visibility 재등록 핸들러 제거 — reset 이후 새 enable 전까지 자동 재등록이 일어나지 않도록.\r\n this._uninstallVisibilityReRegister();\r\n\r\n // SW 의 projectId 등록도 해제 (best-effort).\r\n this._unregisterProjectFromSW();\r\n\r\n this._foregroundMessageUnsubscribe = null;\r\n this._enablePromise = null;\r\n this._messaging = null;\r\n this._currentToken = null;\r\n this._enabled = false;\r\n this._swRegistration = null;\r\n this._projectRegisteredToSW = false;\r\n }\r\n\r\n /**\r\n * 활성 SW 에 현재 projectId 를 등록한다.\r\n * SW 는 Client.id → projectId Map 을 유지해 notificationclick 시 올바른 창을 선택한다.\r\n * @private\r\n */\r\n async _registerProjectToSW() {\r\n if (!this.projectId) {\r\n return;\r\n }\r\n try {\r\n const readyRegistration = await navigator.serviceWorker.ready;\r\n const sw =\r\n readyRegistration.active ||\r\n (this._swRegistration && this._swRegistration.active);\r\n if (!sw) {\r\n this.logger.debug('SW active worker 없음 — projectId 등록 스킵');\r\n return;\r\n }\r\n sw.postMessage({ type: 'TALKFLOW_REGISTER_PROJECT', projectId: this.projectId });\r\n this._projectRegisteredToSW = true;\r\n } catch (error) {\r\n this.logger.debug('SW projectId 등록 실패 (무시):', error?.message || error);\r\n }\r\n }\r\n\r\n /**\r\n * SW 에 등록된 projectId 해제 (best-effort).\r\n * reset 시 호출되며, navigator.serviceWorker 접근이 실패해도 예외를 삼킨다.\r\n * @private\r\n */\r\n _unregisterProjectFromSW() {\r\n if (!this._projectRegisteredToSW) {\r\n return;\r\n }\r\n try {\r\n if (typeof navigator === 'undefined' || !navigator.serviceWorker) {\r\n return;\r\n }\r\n const controller = navigator.serviceWorker.controller;\r\n if (!controller) {\r\n return;\r\n }\r\n controller.postMessage({ type: 'TALKFLOW_UNREGISTER_PROJECT' });\r\n } catch (error) {\r\n this.logger.debug('SW projectId 해제 실패 (무시):', error?.message || error);\r\n }\r\n }\r\n\r\n /**\r\n * visibility 복귀 시 SW 에 projectId 를 재등록한다.\r\n * SW 는 idle 시 terminate 되며 이때 clientProjectMap 이 소실되므로,\r\n * 창이 다시 visible 상태가 될 때 방어적으로 재등록한다.\r\n * @private\r\n */\r\n _installVisibilityReRegister() {\r\n if (typeof document === 'undefined' || this._visibilityHandler) {\r\n return;\r\n }\r\n this._visibilityHandler = () => {\r\n if (document.visibilityState === 'visible' && this._enabled && this.projectId) {\r\n // best-effort — 재등록 실패는 다음 visibility 이벤트에 다시 시도된다.\r\n this._registerProjectToSW().catch(() => {});\r\n }\r\n };\r\n document.addEventListener('visibilitychange', this._visibilityHandler);\r\n }\r\n\r\n /**\r\n * @private\r\n */\r\n _uninstallVisibilityReRegister() {\r\n if (typeof document === 'undefined' || !this._visibilityHandler) {\r\n return;\r\n }\r\n try {\r\n document.removeEventListener('visibilitychange', this._visibilityHandler);\r\n } catch (error) {\r\n // 무시\r\n }\r\n this._visibilityHandler = null;\r\n }\r\n\r\n // noinspection JSUnusedGlobalSymbols\r\n /**\r\n * 현재 FCM 토큰\r\n * @returns {string|null}\r\n */\r\n getToken() {\r\n return this._currentToken;\r\n }\r\n\r\n /**\r\n * 푸시 활성화 여부\r\n * @returns {boolean}\r\n */\r\n isEnabled() {\r\n return this._enabled;\r\n }\r\n\r\n /**\r\n * 현재 사용자의 등록된 디바이스 목록 조회 (푸시 on/off UI 용).\r\n *\r\n * <p>서버가 내려주는 필드: {@code deviceId}, {@code deviceType}, {@code enabled}, {@code createdAt}.\r\n * {@code deviceToken} 은 보안상 응답에 포함되지 않는다.</p>\r\n *\r\n * @returns {Promise<Array<{deviceId: string, deviceType: string, enabled: boolean, createdAt: string}>>}\r\n */\r\n async getMyDevices() {\r\n const response = await this.apiClient.get('/api/v1/push/devices/me');\r\n return response && typeof response === 'object' && 'data' in response\r\n ? response.data\r\n : response;\r\n }\r\n\r\n /**\r\n * 특정 디바이스의 푸시 수신 여부 토글.\r\n *\r\n * <p>본인 소유 디바이스만 수정 가능 (서버가 {@code (projectId, userId, deviceId)} 조합으로 검증).\r\n * 소유자 불일치 또는 미등록 디바이스 시 서버가 404 로 응답 → 호출자에 throw.</p>\r\n *\r\n * @param {string} deviceId - 대상 디바이스 ID (현재 디바이스면 {@link #setCurrentDeviceEnabled} 사용 권장)\r\n * @param {boolean} enabled - true 면 수신, false 면 수신 중단\r\n * @returns {Promise<void>}\r\n */\r\n async setDeviceEnabled(deviceId, enabled) {\r\n await this.apiClient.patch('/api/v1/push/devices/me/enabled', {\r\n deviceId,\r\n enabled\r\n });\r\n this.logger.info(`디바이스 푸시 enabled=${enabled}: deviceId=${deviceId}`);\r\n }\r\n\r\n /**\r\n * 현재 디바이스의 푸시 수신 여부 토글 (편의 메서드).\r\n *\r\n * <p>SDK 가 관리하는 현재 브라우저의 deviceId 로 {@link #setDeviceEnabled} 를 호출한다.\r\n * {@link #enable} 이 선행되지 않아도 호출 가능 — 서버에 디바이스가 등록돼 있으면 동작.</p>\r\n *\r\n * @param {boolean} enabled\r\n * @returns {Promise<void>}\r\n */\r\n async setCurrentDeviceEnabled(enabled) {\r\n const deviceId = await this._getDeviceId();\r\n await this.setDeviceEnabled(deviceId, enabled);\r\n }\r\n\r\n /**\r\n * 등록된 서비스 워커의 TalkFlow 버전을 조회.\r\n *\r\n * navigator.serviceWorker.ready 를 기다려 active worker 가 보장된 상태에서,\r\n * readyRegistration → register() 반환 registration 양쪽의\r\n * waiting → installing → active 순으로 SW 를 찾는다.\r\n * v1→v2 교체 직후 새 SW 가 waiting/installing 상태에 있을 때\r\n * 구 active(v1) 대신 신규(v2)를 우선 감지하여 거짓 경고를 방지한다.\r\n * 같은 scope 에서는 두 객체가 동일 참조이지만, 타이밍 차이에 대한 방어 코드.\r\n *\r\n * v1(구버전) SW 는 메시지 핸들러가 없으므로 2초 timeout 후 null 반환.\r\n *\r\n * @param {ServiceWorkerRegistration} registration - register() 가 반환한 등록 객체\r\n * @returns {Promise<string|null>} 버전 문자열 또는 null (구버전/미응답)\r\n * @private\r\n */\r\n async _getSWVersion(registration) {\r\n try {\r\n const readyRegistration = await navigator.serviceWorker.ready;\r\n\r\n const sw =\r\n readyRegistration.waiting ||\r\n readyRegistration.installing ||\r\n readyRegistration.active ||\r\n registration.waiting ||\r\n registration.installing ||\r\n registration.active;\r\n if (!sw) return null;\r\n\r\n return new Promise((resolve) => {\r\n const timeout = setTimeout(() => resolve(null), 2000);\r\n\r\n const channel = new MessageChannel();\r\n channel.port1.onmessage = (event) => {\r\n clearTimeout(timeout);\r\n resolve(event.data?.version || null);\r\n };\r\n\r\n try {\r\n sw.postMessage({ type: 'TALKFLOW_SW_VERSION' }, [channel.port2]);\r\n } catch (e) {\r\n clearTimeout(timeout);\r\n resolve(null);\r\n }\r\n });\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level\r\n */\r\n setLogLevel(level) {\r\n this.logger.setLevel(level);\r\n }\r\n}\r\n\r\nexport default PushManager;\r\n","const CONNECTION_EVENT_MAP = [\r\n ['connected', 'connected'],\r\n ['disconnected', 'disconnected'],\r\n ['reconnecting', 'reconnecting'],\r\n ['error', 'connectionError']\r\n];\r\n\r\nconst CHAT_EVENT_MAP = [\r\n ['message', 'chatMessage'],\r\n ['newMessage', 'newChatMessage'],\r\n ['messageUpdated', 'messageUpdated'],\r\n ['messageDeleted', 'messageDeleted'],\r\n ['reactionChanged', 'reactionChanged'],\r\n ['linkPreviewAttached', 'linkPreviewAttached'],\r\n ['messageTranslated', 'messageTranslated'],\r\n ['messageRead', 'messageRead'],\r\n ['typing', 'typing'],\r\n ['assistantProgress', 'assistantProgress'],\r\n ['memberJoined', 'memberJoined'],\r\n ['memberLeft', 'memberLeft'],\r\n ['roomSubscribed', 'roomSubscribed'],\r\n ['roomUnsubscribed', 'roomUnsubscribed'],\r\n ['roomListSubscribed', 'roomListSubscribed'],\r\n ['roomListUnsubscribed', 'roomListUnsubscribed'],\r\n ['roomListUpdate', 'roomListUpdate'],\r\n ['roomListMessage', 'roomListMessage'],\r\n ['roomListCreated', 'roomListCreated'],\r\n ['roomListJoined', 'roomListJoined'],\r\n ['roomListLeft', 'roomListLeft'],\r\n ['roomListSelfLeft', 'roomListSelfLeft'],\r\n ['roomListKicked', 'roomListKicked'],\r\n ['roomListSelfKicked', 'roomListSelfKicked'],\r\n ['roomListBanned', 'roomListBanned'],\r\n ['roomListSelfBanned', 'roomListSelfBanned'],\r\n ['roomListRoomUpdated', 'roomListRoomUpdated'],\r\n ['retentionCleanup', 'retentionCleanup']\r\n];\r\n\r\nconst WEBRTC_EVENT_MAP = [\r\n ['localStreamStarted', 'localStreamStarted'],\r\n ['localStreamStopped', 'localStreamStopped'],\r\n ['remoteTrack', 'remoteTrack'],\r\n ['screenShareStarted', 'screenShareStarted'],\r\n ['screenShareEnded', 'screenShareEnded'],\r\n ['deviceChange', 'deviceChange'],\r\n ['mediaStateChanged', 'mediaStateChanged'],\r\n ['callStarted', 'callStarted'],\r\n ['callEnded', 'callEnded'],\r\n ['callRequested', 'callRequested'],\r\n ['callAccepted', 'callAccepted'],\r\n ['callRejected', 'callRejected'],\r\n ['callCancelled', 'callCancelled'],\r\n ['callBusy', 'callBusy'],\r\n ['incomingCall', 'incomingCall'],\r\n ['incomingCallWhileBusy', 'incomingCallWhileBusy'],\r\n ['callInvitation', 'callInvitation'],\r\n ['userJoined', 'userJoined'],\r\n ['userLeft', 'userLeft'],\r\n ['participantLeft', 'participantLeft'],\r\n ['participantMediaState', 'participantMediaState'],\r\n ['peerConnected', 'peerConnected'],\r\n ['peerDisconnected', 'peerDisconnected'],\r\n ['peerClosed', 'peerClosed'],\r\n ['error', 'webrtcError']\r\n];\r\n\r\nfunction forwardMappedEvents(source, target, eventMap) {\r\n eventMap.forEach(([sourceEvent, targetEvent]) => {\r\n source.on(sourceEvent, (data) => {\r\n target.emit(targetEvent, data);\r\n });\r\n });\r\n}\r\n\r\nexport function setupEventForwarding(client) {\r\n if (!client.connectionManager) {\r\n return;\r\n }\r\n\r\n client.connectionManager.on('stateChange', ({ state, prevState }) => {\r\n client._state = state;\r\n client.emit('stateChange', { state, prevState });\r\n });\r\n\r\n forwardMappedEvents(client.connectionManager, client, CONNECTION_EVENT_MAP);\r\n\r\n if (client.chat) {\r\n forwardMappedEvents(client.chat, client, CHAT_EVENT_MAP);\r\n }\r\n\r\n if (client.webrtc) {\r\n forwardMappedEvents(client.webrtc, client, WEBRTC_EVENT_MAP);\r\n }\r\n}\r\n","const CHAT_DELEGATE_METHODS = [\r\n 'getRooms',\r\n 'getRoom',\r\n 'getRoomInfo',\r\n 'setMyRoomLanguage',\r\n 'createOneToOneRoom',\r\n 'createGroupRoom',\r\n 'joinGroupRoom',\r\n 'leaveRoom',\r\n 'updateGroupRoom',\r\n 'inviteToGroupRoom',\r\n 'kickMember',\r\n 'banMember',\r\n 'unbanMember',\r\n 'getBannedMembers',\r\n 'getAvailableGroupRooms',\r\n 'getAllGroupRooms',\r\n 'enterRoom',\r\n 'getMessages',\r\n 'fetchLinkPreview',\r\n 'sendMessage',\r\n 'sendMessageOptimistic',\r\n 'sendTextMessage',\r\n 'sendTextMessageOptimistic',\r\n 'sendReply',\r\n 'sendReplyOptimistic',\r\n 'uploadFile',\r\n 'sendFileMessage',\r\n 'sendFileMessageOptimistic',\r\n 'editMessage',\r\n 'deleteMessage',\r\n 'markAsRead',\r\n 'pinMessage',\r\n 'unpinMessage',\r\n 'toggleReaction',\r\n 'subscribeRoom',\r\n 'unsubscribeRoom',\r\n 'unsubscribeAllRooms',\r\n 'setActiveRoom',\r\n 'clearActiveRoom',\r\n 'getActiveRoom',\r\n 'subscribeRoomList',\r\n 'unsubscribeRoomList',\r\n 'isRoomListSubscribed',\r\n 'getSubscribedRooms',\r\n 'isSubscribed',\r\n 'startTyping',\r\n 'stopTyping',\r\n 'getAssistants',\r\n 'getRoomAiMeta',\r\n 'rateAssistantMessage',\r\n 'summarizeWithAssistant',\r\n 'translateWithAssistant',\r\n 'getRoomPmPrompt',\r\n 'upsertRoomPmPrompt',\r\n 'activateRoomPmPrompt',\r\n 'deactivateRoomPmPrompt',\r\n 'getRoomPmPromptVersions',\r\n 'activateRoomPmPromptVersion',\r\n 'previewRoomPmPrompt'\r\n];\r\n\r\nconst WEBRTC_DELEGATE_METHODS = [\r\n 'initializeIceServers',\r\n 'getTurnCredentials',\r\n 'createCallRoom',\r\n 'getCallRoom',\r\n 'joinCallRoomApi',\r\n 'leaveCallRoomApi',\r\n 'enableIncomingCalls',\r\n 'disableIncomingCalls',\r\n 'isIncomingCallsEnabled',\r\n 'startCall',\r\n 'callUser',\r\n 'acceptCall',\r\n 'rejectCall',\r\n 'cancelCall',\r\n 'endCall',\r\n 'toggleVideo',\r\n 'toggleAudio',\r\n 'setVideoEnabled',\r\n 'setAudioEnabled',\r\n 'startScreenShare',\r\n 'stopScreenShare',\r\n 'getLocalStream',\r\n 'getDevices',\r\n 'switchDevice',\r\n 'startDeviceChangeDetection',\r\n 'stopDeviceChangeDetection',\r\n 'applyVideoConstraints',\r\n 'getVideoSettings',\r\n 'getAudioSettings',\r\n 'isInCall',\r\n 'getCurrentRoom',\r\n 'getParticipants',\r\n 'getMediaState',\r\n 'getConnectionSummary',\r\n 'getStats'\r\n];\r\n\r\nfunction defineDelegates(prototype, targetKey, methods) {\r\n methods.forEach((method) => {\r\n prototype[method] = function delegatedMethod(...args) {\r\n return this[targetKey][method](...args);\r\n };\r\n });\r\n}\r\n\r\nexport function applyDelegatedMethods(TalkFlowClient) {\r\n defineDelegates(TalkFlowClient.prototype, 'chat', CHAT_DELEGATE_METHODS);\r\n defineDelegates(TalkFlowClient.prototype, 'webrtc', WEBRTC_DELEGATE_METHODS);\r\n}\r\n","/**\r\n * TalkFlowClient\r\n * 채팅 + WebRTC 통합 클라이언트\r\n */\r\n\r\nimport EventEmitter from './utils/EventEmitter.js';\r\nimport Logger from './utils/Logger.js';\r\nimport ApiClient from './utils/ApiClient.js';\r\nimport { extractUserIdFromJWT } from './utils/jwtUtils.js';\r\nimport { ConnectionState, ErrorTypes, DefaultConfig, LogLevel, Environment, getServerUrl } from './constants.js';\r\nimport { initializeSubClients, applySessionMethods } from './talkflow/session.js';\r\nimport { setupEventForwarding } from './talkflow/eventForwarding.js';\r\nimport { applyDelegatedMethods } from './talkflow/delegates.js';\r\n\r\n// noinspection JSUnresolvedReference\r\nclass TalkFlowClient extends EventEmitter {\r\n /**\r\n * TalkFlowClient 생성\r\n *\r\n * <p>JWT 토큰은 고객사 backend 에서 발급받아서 SDK 에 전달해야 합니다.\r\n * 브라우저에서 직접 JWT 를 발급하는 기능은 제공하지 않습니다 (보안상).\r\n * 자세한 내용은 README \"프로덕션 인증 플로우\" 섹션 참고.</p>\r\n *\r\n * @param {Object} options - 설정 옵션\r\n * @param {string} options.apiKey - Client API 키 (필수, 브라우저 노출 안전)\r\n * @param {string} options.projectId - 프로젝트 ID (필수)\r\n * @param {string} options.jwtToken - JWT 토큰 (필수, 고객사 backend 에서 발급)\r\n * @param {string} [options.env='production'] - 환경 ('development' | 'staging' | 'production')\r\n * @param {string} [options.serverUrl] - 서버 URL (직접 지정 시 env 무시)\r\n * @param {boolean} [options.useSockJS=true] - SockJS 사용 여부\r\n * @param {number} [options.reconnectDelay=5000] - 재연결 지연 (ms)\r\n * @param {number} [options.maxReconnectAttempts=10] - 최대 재연결 시도\r\n * @param {Object[]} [options.iceServers] - ICE 서버 설정\r\n * @param {boolean} [options.autoSubscribeRoomList=true] - connect() 시 채팅방 리스트 자동 구독 여부 (카톡 스타일)\r\n * @param {number} [options.logLevel=LogLevel.WARN] - 로그 레벨\r\n *\r\n * @example\r\n * // 1단계: 고객사 backend 가 talkflow JWT 를 발급받음 (Server API Key 사용)\r\n * // POST https://chat.apiorbit.net/api/v1/users/auth\r\n * // Headers: X-API-KEY: <SERVER_KEY>, X-PROJECT-ID: <project-id>\r\n * // Body: { userId: 'user-123', nickname: '홍길동' }\r\n * // Response: { accessToken, refreshToken }\r\n *\r\n * // 2단계: 고객사 backend 가 최종 사용자 브라우저에 JWT 전달 (자사 API 응답 등)\r\n *\r\n * // 3단계: 브라우저에서 SDK 초기화\r\n * const client = new TalkFlowClient({\r\n * apiKey: 'CLIENT_KEY', // 브라우저에 노출되는 Client Key\r\n * projectId: 'your-project-id',\r\n * jwtToken: receivedJwt, // 고객사 backend 로부터 받은 JWT\r\n * env: 'production'\r\n * });\r\n *\r\n * await client.connect();\r\n */\r\n constructor(options) {\r\n super();\r\n\r\n // 필수 옵션 검증\r\n this._validateOptions(options);\r\n\r\n // 환경에 따른 서버 URL 결정\r\n // serverUrl이 직접 지정되면 우선 사용, 아니면 env에 따라 자동 결정\r\n const env = options.env || DefaultConfig.environment;\r\n const serverUrl = (options.serverUrl || getServerUrl(env)).replace(/\\/$/, '');\r\n\r\n this.options = {\r\n serverUrl,\r\n env,\r\n apiKey: options.apiKey,\r\n projectId: options.projectId,\r\n jwtToken: options.jwtToken || null,\r\n useSockJS: options.useSockJS !== false,\r\n reconnectDelay: options.reconnectDelay || DefaultConfig.reconnectDelay,\r\n maxReconnectAttempts: options.maxReconnectAttempts || DefaultConfig.maxReconnectAttempts,\r\n iceServers: options.iceServers || DefaultConfig.iceServers,\r\n // connect() 시 채팅방 리스트 자동 구독 여부 (카톡 스타일).\r\n // 기본 true — 앱 어디서든 리스트 이벤트를 놓치지 않음 (배지/알림 등).\r\n // false 로 설정 시 수동으로 client.chat.subscribeRoomList() 호출 필요.\r\n autoSubscribeRoomList: options.autoSubscribeRoomList !== false,\r\n logLevel: options.logLevel !== undefined ? options.logLevel : LogLevel.WARN\r\n };\r\n\r\n // 로거 초기화\r\n this.logger = new Logger(this.options.logLevel, 'TalkFlowClient');\r\n\r\n // 상태\r\n this._initialized = false;\r\n this._state = ConnectionState.DISCONNECTED;\r\n\r\n // public 읽기 전용 surface 의 backing field.\r\n // types/index.d.ts 는 chat/webrtc/pushManager/userId 를 readonly 로 선언 —\r\n // 외부 계약(readonly) 과 내부 재할당 필요성을 동시에 만족시키기 위해 private\r\n // backing field 에 저장하고 동명 getter 로 공개한다.\r\n this._userId = null;\r\n\r\n // JWT가 있으면 userId 추출\r\n if (this.options.jwtToken) {\r\n this._userId = extractUserIdFromJWT(this.options.jwtToken);\r\n }\r\n\r\n // API 클라이언트 초기화 (JWT 없이도 동작)\r\n this.apiClient = new ApiClient({\r\n baseUrl: this.options.serverUrl,\r\n apiKey: this.options.apiKey,\r\n projectId: this.options.projectId,\r\n jwtToken: this.options.jwtToken,\r\n logLevel: this.options.logLevel\r\n });\r\n\r\n // ConnectionManager, ChatClient, WebRTCClient는 지연 초기화\r\n this.connectionManager = null;\r\n this._chat = null;\r\n this._webrtc = null;\r\n this._pushManager = null;\r\n this._pushEnablePromise = null;\r\n\r\n // JWT가 있으면 즉시 서브 클라이언트 초기화\r\n if (this.options.jwtToken && this._userId) {\r\n this._initializeSubClients();\r\n }\r\n\r\n this._initialized = true;\r\n this.logger.info('TalkFlowClient initialized', {\r\n userId: this._userId,\r\n hasToken: !!this.options.jwtToken\r\n });\r\n }\r\n\r\n // ==================== Public readonly surface (getter) ====================\r\n\r\n /** @returns {ChatClient|null} */\r\n get chat() { return this._chat; }\r\n\r\n /** @returns {WebRTCClient|null} */\r\n get webrtc() { return this._webrtc; }\r\n\r\n /** @returns {PushManager|null} */\r\n get pushManager() { return this._pushManager; }\r\n\r\n /** @returns {string|null} */\r\n get userId() { return this._userId; }\r\n\r\n /**\r\n * 옵션 검증\r\n * @private\r\n * @param {Object} options - 설정 옵션\r\n * @throws {Error} 필수 옵션이 없는 경우\r\n */\r\n _validateOptions(options) {\r\n if (!options) {\r\n throw new Error('Options are required');\r\n }\r\n if (!options.apiKey) {\r\n throw new Error('apiKey is required');\r\n }\r\n if (!options.projectId) {\r\n throw new Error('projectId is required');\r\n }\r\n\r\n // Server Key (sk-) 브라우저 사용 경고\r\n // - development: 경고만 (registerUser 빠른 테스트 허용)\r\n // - staging/production: 경고 + 강력 안내\r\n if (options.apiKey.startsWith('sk-')) {\r\n const env = options.env || 'production';\r\n if (env === 'development') {\r\n console.warn(\r\n '⚠️ [TalkFlow] Server Key (sk-) 를 개발 모드에서 사용 중입니다. ' +\r\n '프로덕션 배포 전에 반드시 Client Key (ck-) 로 교체하세요.'\r\n );\r\n } else {\r\n console.error(\r\n '🚨 [TalkFlow] Server Key (sk-) 가 비개발 환경 (' + env + ') 에서 감지되었습니다. ' +\r\n '보안 위험: Server Key 를 브라우저에 포함하면 공격자가 임의 사용자로 JWT 를 발급받을 수 있습니다. ' +\r\n '반드시 Client Key (ck-) 로 교체하세요. ' +\r\n '서버에서도 프로덕션 모드에서 Server Key 의 브라우저 호출을 차단합니다.'\r\n );\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 서브 클라이언트 초기화\r\n * @private\r\n */\r\n _initializeSubClients() {\r\n initializeSubClients(this);\r\n }\r\n\r\n /**\r\n * 이벤트 포워딩 설정\r\n * @private\r\n */\r\n _setupEventForwarding() {\r\n setupEventForwarding(this);\r\n }\r\n\r\n /**\r\n * 연결 상태 확인\r\n * @returns {boolean}\r\n */\r\n isConnected() {\r\n return this.connectionManager ? this.connectionManager.isConnected() : false;\r\n }\r\n\r\n /**\r\n * 현재 연결 상태\r\n * @returns {string}\r\n */\r\n getState() {\r\n return this._state;\r\n }\r\n\r\n /**\r\n * 토큰 보유 여부\r\n * @returns {boolean}\r\n */\r\n hasToken() {\r\n return !!this.options.jwtToken;\r\n }\r\n\r\n // ==================== 사용자 API ====================\r\n\r\n /**\r\n * 사용자 인증 (등록/로그인).\r\n * 인증 성공 시 반환된 JWT 토큰을 자동으로 설정합니다.\r\n *\r\n * <h3>⚠️ Server API Key 전용</h3>\r\n * <p>이 메서드는 JWT 를 발급하는 엔드포인트 ({@code POST /api/v1/users/auth}) 를 호출합니다.\r\n * <strong>프로덕션 환경에서는 Client API Key 로 이 메서드를 호출하면 서버가 403 을 반환합니다.</strong></p>\r\n *\r\n * <ul>\r\n * <li><strong>개발/프로토타입</strong>: Server Key 로 SDK 를 초기화한 뒤 이 메서드를 사용하면 빠르게 테스트 가능</li>\r\n * <li><strong>프로덕션</strong>: 고객사 backend 에서 Server Key 로 직접 {@code POST /api/v1/users/auth} 를 호출 →\r\n * JWT 를 받아 SDK 에 {@code setToken(jwt)} 으로 전달. 이 메서드를 브라우저에서 쓰지 마세요.</li>\r\n * </ul>\r\n *\r\n * <p>자세한 내용은 README 의 \"프로덕션 인증 플로우\" 섹션 참고.</p>\r\n *\r\n * @param {Object} userData\r\n * @param {string} userData.userId - 사용자 ID (필수, 고객사 회원 ID)\r\n * @param {string} userData.nickname - 닉네임 (필수, max 100자)\r\n * @param {string} [userData.profileImageUrl] - 프로필 이미지 URL (max 500자)\r\n * @param {Object} [userData.metadata] - 추가 메타데이터\r\n * @returns {Promise<Object>} 인증 결과 (토큰 포함)\r\n *\r\n * @example\r\n * // 개발 환경 — Server Key 로 빠른 테스트\r\n * const client = new TalkFlowClient({\r\n * apiKey: 'SERVER_KEY',\r\n * projectId: 'your-project-id',\r\n * env: 'development'\r\n * });\r\n * await client.registerUser({ userId: 'user-123', nickname: '홍길동' });\r\n * await client.connect();\r\n */\r\n async registerUser(userData) {\r\n // 다국어: navigator.language 자동 시드 — 호출자가 preferredLanguage 를 명시하지 않은 경우에만.\r\n // 서버 /users/auth 는 seed-if-absent (기존 값 비클로버) 라 매 인증마다 덮어쓰지 않아 안전.\r\n const payload = { ...userData };\r\n if (payload.preferredLanguage === undefined) {\r\n const detected = TalkFlowClient.detectBrowserLanguage();\r\n if (detected) {\r\n payload.preferredLanguage = detected;\r\n }\r\n }\r\n\r\n const response = await this.apiClient.post('/api/v1/users/auth', payload);\r\n\r\n if (response.data && response.data.accessToken) {\r\n await this.setToken(response.data.accessToken);\r\n this.logger.info('User authenticated, token auto-set');\r\n }\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * 사용자 정보 수정\r\n * @param {Object} userData\r\n * @param {string} [userData.nickname] - 닉네임 (max 100자)\r\n * @param {string} [userData.profileImageUrl] - 프로필 이미지 URL (max 500자)\r\n * @param {Object} [userData.metadata] - 메타데이터 (전체 교체)\r\n * @param {string|null} [userData.preferredLanguage] - 기본 선호 언어 (BCP-47 / 'OFF'(원문) / null). 다국어.\r\n * @returns {Promise<Object>}\r\n */\r\n async updateMyInfo(userData) {\r\n this._checkToken();\r\n return this.apiClient.put('/api/v1/users/update', userData);\r\n }\r\n\r\n /**\r\n * 내 기본 선호 언어 설정 (전 방 공통, 다국어). 명시 변경 — 방별 오버라이드는 {@link #setMyRoomLanguage}.\r\n * @param {string|null} language - BCP-47 코드(예 'ko','en') / 'OFF'(원문) / null·''(미설정)\r\n * @returns {Promise<Object>}\r\n */\r\n async setPreferredLanguage(language) {\r\n return this.updateMyInfo({ preferredLanguage: language });\r\n }\r\n\r\n /**\r\n * 브라우저 locale 감지 — {@code navigator.language} 를 BCP-47 소문자로 정규화. 비브라우저/미가용 시 null.\r\n * <p>{@link #registerUser} 자동 시드에 사용되며, 고객사 BFF 의 {@code /users/auth} 시드에도 쓸 수 있다.</p>\r\n * @returns {string|null}\r\n */\r\n static detectBrowserLanguage() {\r\n if (typeof navigator === 'undefined' || !navigator.language) {\r\n return null;\r\n }\r\n return navigator.language.trim().toLowerCase() || null;\r\n }\r\n\r\n /**\r\n * 표시 텍스트 헬퍼 (다국어) — 내 언어 번역이 있으면 그것, 없으면 원문.\r\n * <p>{@code myLang} 은 내 effectiveLanguage (방 participant.language ?? user.preferredLanguage).\r\n * {@code messageTranslated} 이벤트로 갱신된 message 에 적용.</p>\r\n * @param {Object} message - 메시지 (translations 맵 포함 가능)\r\n * @param {string} [myLang] - 내 언어 코드\r\n * @returns {string|null}\r\n */\r\n static displayText(message, myLang) {\r\n if (message && message.translations && myLang && message.translations[myLang]) {\r\n return message.translations[myLang];\r\n }\r\n return message ? message.content : null;\r\n }\r\n\r\n /**\r\n * 사용자 존재 여부 확인\r\n * @param {string} projectUserId - 프로젝트 사용자 ID\r\n * @returns {Promise<Object>}\r\n */\r\n async checkUserExists(projectUserId) {\r\n return this.apiClient.get(`/api/v1/users/${projectUserId}/exists`);\r\n }\r\n\r\n /**\r\n * 사용자 목록 조회\r\n * @param {Object} [params]\r\n * @param {number} [params.size=50] - 페이지 크기 (1-100)\r\n * @param {string} [params.lastId] - 마지막 사용자 ID\r\n * @param {number} [params.lastSortValue] - 마지막 정렬값\r\n * @returns {Promise<Object>}\r\n */\r\n async getUsers(params = {}) {\r\n this._checkToken();\r\n const { size = 50, lastId, lastSortValue } = params;\r\n return this.apiClient.get('/api/v1/users', { size, lastId, lastSortValue });\r\n }\r\n\r\n /**\r\n * 사용자 검색\r\n * @param {Object} params\r\n * @param {string} params.keyword - 검색 키워드 (필수, max 50자)\r\n * @param {number} [params.limit=20] - 결과 수 (1-50)\r\n * @returns {Promise<Object>}\r\n */\r\n async searchUsers(params) {\r\n this._checkToken();\r\n const { keyword, limit = 20 } = params;\r\n return this.apiClient.get('/api/v1/users/search', { keyword, limit });\r\n }\r\n\r\n /**\r\n * 토큰 필요 여부 확인\r\n * @private\r\n * @throws {Error} 토큰이 없는 경우\r\n */\r\n _checkToken() {\r\n if (!this.options.jwtToken) {\r\n throw new Error(\r\n 'JWT token is required for this operation. Obtain it from your backend ' +\r\n '(POST /api/v1/users/auth with Server API Key) and pass it via setToken(). ' +\r\n 'See README \"프로덕션 인증 플로우\".'\r\n );\r\n }\r\n }\r\n\r\n // ==================== 유틸리티 ====================\r\n\r\n /**\r\n * 현재 사용자 ID\r\n * @returns {string|null}\r\n */\r\n getUserId() {\r\n return this.userId;\r\n }\r\n\r\n /**\r\n * 초기화 상태 확인\r\n * @returns {boolean}\r\n */\r\n isInitialized() {\r\n return this._initialized;\r\n }\r\n\r\n /**\r\n * 서브 클라이언트 초기화 상태 확인\r\n * @returns {boolean}\r\n */\r\n isReady() {\r\n return this._initialized && !!this.connectionManager;\r\n }\r\n\r\n /**\r\n * 로그 레벨 설정\r\n * @param {number} level - LogLevel 값\r\n */\r\n setLogLevel(level) {\r\n this.options.logLevel = level;\r\n this.logger.setLevel(level);\r\n this.apiClient.logger?.setLevel(level);\r\n\r\n if (this.connectionManager) {\r\n this.connectionManager.setLogLevel(level);\r\n }\r\n if (this.chat) {\r\n this.chat.setLogLevel(level);\r\n }\r\n if (this.webrtc) {\r\n this.webrtc.setLogLevel(level);\r\n }\r\n }\r\n\r\n /**\r\n * 전체 상태 요약\r\n * @returns {Object}\r\n */\r\n getStatus() {\r\n return {\r\n initialized: this._initialized,\r\n ready: this.isReady(),\r\n hasToken: this.hasToken(),\r\n connectionState: this._state,\r\n isConnected: this.isConnected(),\r\n userId: this.userId,\r\n chat: this.chat ? {\r\n subscribedRooms: this.chat.getSubscribedRooms()\r\n } : null,\r\n webrtc: this.webrtc ? {\r\n isInCall: this.webrtc.isInCall(),\r\n currentRoom: this.webrtc.getCurrentRoom(),\r\n participants: this.webrtc.getParticipants(),\r\n mediaState: this.webrtc.getMediaState()\r\n } : null\r\n };\r\n }\r\n\r\n /**\r\n * 리소스 정리\r\n */\r\n async destroy() {\r\n await this.disconnect();\r\n\r\n if (this.chat) {\r\n this.chat.destroy();\r\n }\r\n if (this.webrtc) {\r\n this.webrtc.destroy();\r\n }\r\n if (this.connectionManager) {\r\n await this.connectionManager.destroy();\r\n }\r\n\r\n this.removeAllListeners();\r\n\r\n this._initialized = false;\r\n this.logger.info('TalkFlowClient destroyed');\r\n }\r\n}\r\n\r\n// 정적 속성으로 상수 노출\r\nTalkFlowClient.ConnectionState = ConnectionState;\r\nTalkFlowClient.ErrorTypes = ErrorTypes;\r\nTalkFlowClient.LogLevel = LogLevel;\r\nTalkFlowClient.Environment = Environment;\r\n\r\napplySessionMethods(TalkFlowClient);\r\napplyDelegatedMethods(TalkFlowClient);\r\n\r\nexport default TalkFlowClient;\r\n","import { validateAndParseJWT, isJWTExpired } from '../utils/jwtUtils.js';\r\nimport ConnectionManager from '../core/ConnectionManager.js';\r\nimport ChatClient from '../chat/ChatClient.js';\r\nimport WebRTCClient from '../webrtc/WebRTCClient.js';\r\nimport PushManager from '../push/PushManager.js';\r\n\r\nexport function initializeSubClients(client) {\r\n if (client.connectionManager) {\r\n return;\r\n }\r\n\r\n if (!client.userId) {\r\n throw new Error('userId is required to initialize sub-clients. Please set JWT token first.');\r\n }\r\n\r\n client.connectionManager = new ConnectionManager({\r\n serverUrl: client.options.serverUrl,\r\n jwtToken: client.options.jwtToken,\r\n apiKey: client.options.apiKey,\r\n projectId: client.options.projectId,\r\n useSockJS: client.options.useSockJS,\r\n reconnectDelay: client.options.reconnectDelay,\r\n maxReconnectAttempts: client.options.maxReconnectAttempts,\r\n logLevel: client.options.logLevel\r\n });\r\n\r\n client._chat = new ChatClient({\r\n connectionManager: client.connectionManager,\r\n apiClient: client.apiClient,\r\n userId: client._userId,\r\n logLevel: client.options.logLevel\r\n });\r\n\r\n client._webrtc = new WebRTCClient({\r\n connectionManager: client.connectionManager,\r\n apiClient: client.apiClient,\r\n userId: client._userId,\r\n iceServers: client.options.iceServers,\r\n logLevel: client.options.logLevel\r\n });\r\n\r\n client._setupEventForwarding();\r\n client.logger.debug('Sub-clients initialized');\r\n}\r\n\r\nexport function applySessionMethods(TalkFlowClient) {\r\n TalkFlowClient.prototype.connect = async function connect(jwt, options = {}) {\r\n const { enablePush = false } = options;\r\n\r\n if (jwt) {\r\n await this.setToken(jwt);\r\n }\r\n\r\n if (!this.options.jwtToken) {\r\n throw new Error(\r\n 'JWT token is required. Obtain it from your backend (which calls ' +\r\n 'POST /api/v1/users/auth with a Server API Key) and pass it to the SDK ' +\r\n 'via the jwtToken option, setToken(), or connect(jwt). See README \"프로덕션 인증 플로우\".'\r\n );\r\n }\r\n\r\n if (isJWTExpired(this.options.jwtToken)) {\r\n throw new Error('JWT token has expired. Please update the token before connecting.');\r\n }\r\n\r\n if (!this.connectionManager) {\r\n this._initializeSubClients();\r\n }\r\n\r\n await this.connectionManager.connect();\r\n\r\n if (this.webrtc) {\r\n await this.webrtc.enableIncomingCalls();\r\n }\r\n\r\n if (this.chat && this.options.autoSubscribeRoomList) {\r\n try {\r\n await this.chat.subscribeRoomList();\r\n } catch (error) {\r\n this.logger.warn('Failed to auto-subscribe room list (non-fatal):', error);\r\n }\r\n }\r\n\r\n if (enablePush) {\r\n this.enablePushNotifications();\r\n }\r\n\r\n this.logger.info('Connected to server');\r\n };\r\n\r\n TalkFlowClient.prototype.disconnect = async function disconnect() {\r\n if (!this.connectionManager) {\r\n return;\r\n }\r\n\r\n if (this.webrtc && this.webrtc.isInCall()) {\r\n this.webrtc.endCall();\r\n }\r\n\r\n if (this.webrtc) {\r\n this.webrtc.disableIncomingCalls();\r\n }\r\n\r\n if (this.chat) {\r\n this.chat.unsubscribeAllRooms();\r\n this.chat.unsubscribeRoomList();\r\n }\r\n\r\n await this.connectionManager.disconnect();\r\n this.logger.info('Disconnected from server');\r\n };\r\n\r\n TalkFlowClient.prototype.logout = async function logout() {\r\n try {\r\n await this.apiClient.post('/v1/auth/signout');\r\n this.logger.info('Logged out from server');\r\n } catch (error) {\r\n this.logger.warn('Server logout failed (proceeding with local cleanup):', error.message);\r\n }\r\n\r\n await this.disconnect();\r\n\r\n if (this.pushManager) {\r\n this.pushManager.reset();\r\n this._pushEnablePromise = null;\r\n }\r\n\r\n this.options.jwtToken = null;\r\n this._userId = null;\r\n this.apiClient.setJwtToken(null);\r\n\r\n this.emit('loggedOut');\r\n };\r\n\r\n /**\r\n * 웹 푸시 알림을 활성화한다.\r\n *\r\n * <p>전체 흐름: Firebase 초기화 → 서비스 워커 등록 → 권한 요청 → FCM 토큰 발급 → 서버 등록 →\r\n * 포그라운드 메시지 핸들러 등록 → SW 에 projectId 등록.</p>\r\n *\r\n * <h3>호출 패턴</h3>\r\n * <ul>\r\n * <li><b>자동 활성화 (비차단)</b> — {@code client.enablePushNotifications();} 처럼\r\n * await 없이 호출하면 fire-and-forget 으로 동작한다. 메인 흐름 (UI 렌더링 등) 을 차단하지 않는다.\r\n * 이 경우 결과는 {@code 'pushEnabled'} / {@code 'pushFailed'} 이벤트로 받는다.\r\n * <b>중요</b>: 이 패턴을 쓸 때는 호출 <b>전에</b> 이벤트 리스너를 등록해야 한다 — 그렇지 않으면\r\n * 즉시 emit 되는 실패 이벤트를 놓칠 수 있다.</li>\r\n * <li><b>사용자 액션 (await)</b> — 버튼 클릭 등 사용자 명시적 액션에서는\r\n * {@code const result = await client.enablePushNotifications();} 으로 결과를 즉시 받아서\r\n * UI 분기에 사용한다. throw 하지 않으므로 try/catch 불필요.</li>\r\n * </ul>\r\n *\r\n * <p>이 메서드는 throw 하지 않는다. 실패는 {@code { ok: false, reason, error }} 형태의 객체로 반환된다.\r\n * 따라서 fire-and-forget 으로 호출해도 unhandled rejection 이 발생하지 않는다.</p>\r\n *\r\n * @param {Object} [options]\r\n * @param {Object} [options.firebaseConfig] - 커스텀 Firebase 설정 (테스트/검증용)\r\n * @param {string} [options.vapidKey] - 커스텀 VAPID 키\r\n * @param {string} [options.serviceWorkerPath] - 서비스 워커 경로\r\n * @returns {Promise<{ok: true, alreadyEnabled?: boolean} | {ok: false, reason: string, error: Error}>}\r\n * 성공 시 {@code { ok: true }}, 실패 시 {@code { ok: false, reason, error }}.\r\n * {@code reason} 은 {@link PushErrorCode} 값 또는 {@code 'NO_TOKEN'}.\r\n */\r\n TalkFlowClient.prototype.enablePushNotifications = async function enablePushNotifications(options = {}) {\r\n // JWT 토큰 미설정 시 명확한 분류 에러로 통보 (throw 하지 않고 결과 객체 반환).\r\n if (!this.options.jwtToken) {\r\n const error = new Error('JWT token is required to enable push notifications.');\r\n this.emit('pushFailed', { reason: 'NO_TOKEN', error });\r\n return { ok: false, reason: 'NO_TOKEN', error };\r\n }\r\n\r\n if (!this.pushManager) {\r\n this._pushManager = new PushManager({\r\n apiClient: this.apiClient,\r\n projectId: this.options.projectId,\r\n firebaseConfig: options.firebaseConfig,\r\n vapidKey: options.vapidKey,\r\n serviceWorkerPath: options.serviceWorkerPath,\r\n logLevel: this.options.logLevel\r\n });\r\n\r\n // 포그라운드 메시지 콜백 — projectId 필터링 + activeRoom suppress + messageId dedup 후 client emit.\r\n // PushManager 자체는 EventEmitter 가 아니므로 콜백 오버라이드로 우회한다.\r\n //\r\n // dedup: 같은 messageId 의 푸시가 서버측 멱등 race 복구 / FCM 재전송 등으로 중복 도착할 수 있어\r\n // 5분 TTL Map 으로 한 번만 emit. setTimeout 으로 자동 cleanup.\r\n const PUSH_DEDUP_TTL_MS = 5 * 60 * 1000;\r\n this.pushManager._onForegroundMessage = (payload) => {\r\n const data = payload.data || {};\r\n\r\n if (data.projectId && data.projectId !== this.options.projectId) {\r\n this.logger.debug('다른 프로젝트 푸시 무시:', data.projectId);\r\n return;\r\n }\r\n\r\n const activeRoomId = this.chat?.getActiveRoom();\r\n\r\n if (activeRoomId && activeRoomId === data.roomId) {\r\n this.logger.debug('포그라운드 푸시 suppress (현재 방):', data.roomId);\r\n return;\r\n }\r\n\r\n // messageId 기반 dedup — 키가 없는 시스템 푸시 등은 통과.\r\n if (data.messageId) {\r\n if (!this._recentPushMessageIds) {\r\n this._recentPushMessageIds = new Map();\r\n }\r\n if (this._recentPushMessageIds.has(data.messageId)) {\r\n this.logger.debug('포그라운드 푸시 dedup:', data.messageId);\r\n return;\r\n }\r\n const timer = setTimeout(() => {\r\n this._recentPushMessageIds.delete(data.messageId);\r\n }, PUSH_DEDUP_TTL_MS);\r\n this._recentPushMessageIds.set(data.messageId, timer);\r\n }\r\n\r\n this.emit('pushNotification', {\r\n title: payload.notification?.title || data.title,\r\n body: payload.notification?.body || data.body,\r\n data\r\n });\r\n };\r\n }\r\n\r\n // 가드 1: 이미 활성화됨 — 중복 호출은 성공으로 간주.\r\n if (this.pushManager.isEnabled()) {\r\n this.logger.debug('웹 푸시가 이미 활성화되어 있어 중복 호출을 건너뜁니다.');\r\n return { ok: true, alreadyEnabled: true };\r\n }\r\n\r\n // 가드 2: 진행 중 — 같은 Promise 를 공유해 두 호출자가 동일 결과를 받도록 한다.\r\n if (this._pushEnablePromise) {\r\n this.logger.debug('웹 푸시 활성화가 이미 진행 중입니다.');\r\n return this._pushEnablePromise;\r\n }\r\n\r\n // 본 활성화 흐름 — throw 하지 않고 결과 객체로 통일.\r\n // try/catch 안에서 emit + return 모두 처리하여 fire-and-forget 호출자도 안전하게 한다.\r\n const promise = (async () => {\r\n try {\r\n await this.pushManager.enable();\r\n this.emit('pushEnabled');\r\n return { ok: true };\r\n } catch (error) {\r\n // PushError 면 분류 코드 사용, 아니면 'UNKNOWN'.\r\n const reason = (error && error.code) ? error.code : 'UNKNOWN';\r\n this.logger.warn('Failed to enable push notifications:', reason, error?.message);\r\n this.emit('pushFailed', { reason, error });\r\n return { ok: false, reason, error };\r\n }\r\n })();\r\n\r\n this._pushEnablePromise = promise;\r\n\r\n // self-clear — 본인이 현재 진행 중인 Promise 일 때만 null 처리 (race 방지).\r\n promise.finally(() => {\r\n if (this._pushEnablePromise === promise) {\r\n this._pushEnablePromise = null;\r\n }\r\n });\r\n\r\n return promise;\r\n };\r\n\r\n TalkFlowClient.prototype.consumePendingRoom = async function consumePendingRoom() {\r\n return PushManager.consumePendingRoom(this.options.projectId);\r\n };\r\n\r\n TalkFlowClient.prototype.setCurrentDeviceEnabled = function setCurrentDeviceEnabled(enabled) {\r\n return this.pushManager?.setCurrentDeviceEnabled(enabled);\r\n };\r\n\r\n TalkFlowClient.prototype.setDeviceEnabled = function setDeviceEnabled(deviceId, enabled) {\r\n return this.pushManager?.setDeviceEnabled(deviceId, enabled);\r\n };\r\n\r\n TalkFlowClient.prototype.getMyDevices = function getMyDevices() {\r\n return this.pushManager?.getMyDevices();\r\n };\r\n\r\n /**\r\n * 현재 브라우저의 푸시 권한 상태를 조회한다.\r\n *\r\n * <p>{@code enablePushNotifications()} 호출 <b>전에도</b> 사용 가능 — pushManager 인스턴스 불필요.\r\n * UI 분기 (granted → 자동 enable / default → 버튼 노출 / denied → 설정 가이드 / unsupported → 숨김) 에 사용한다.</p>\r\n *\r\n * @returns {'granted'|'denied'|'default'|'unsupported'}\r\n */\r\n TalkFlowClient.prototype.getPushPermissionState = function getPushPermissionState() {\r\n return PushManager.getPermissionState();\r\n };\r\n\r\n /**\r\n * SDK 내부 푸시 활성화 상태 (FCM 토큰 발급 + 서버 등록까지 완료 여부).\r\n *\r\n * <p>주의: 이 값은 SDK 인스턴스의 메모리 상태일 뿐, 브라우저 권한 상태와 다르다.\r\n * 페이지 새로고침 후에는 enable 호출 전까지 false. 권한 상태는 {@link #getPushPermissionState} 사용.</p>\r\n *\r\n * @returns {boolean}\r\n */\r\n TalkFlowClient.prototype.isPushEnabled = function isPushEnabled() {\r\n return this.pushManager?.isEnabled() ?? false;\r\n };\r\n\r\n /**\r\n * 현재 발급된 FCM 토큰. enable 호출 후에만 유효, 그 외에는 null.\r\n * @returns {string|null}\r\n */\r\n TalkFlowClient.prototype.getPushToken = function getPushToken() {\r\n return this.pushManager?.getToken() ?? null;\r\n };\r\n\r\n /**\r\n * 푸시 상태 초기화 — 포그라운드 리스너 / FCM 토큰 / SW projectId 등록을 해제한다.\r\n * 로그아웃 또는 사용자 전환 시 호출. (logout() 내부에서도 자동 호출되므로 일반 흐름에선 불필요.)\r\n */\r\n TalkFlowClient.prototype.resetPush = function resetPush() {\r\n this.pushManager?.reset();\r\n this._pushEnablePromise = null;\r\n };\r\n\r\n TalkFlowClient.prototype.setToken = async function setToken(token) {\r\n const { userId } = validateAndParseJWT(token, { validateExpiry: false });\r\n const needReinitialize = this.userId && this.userId !== userId;\r\n\r\n this.options.jwtToken = token;\r\n this._userId = userId;\r\n this.apiClient.setJwtToken(token);\r\n\r\n if (needReinitialize && this.connectionManager) {\r\n await this.disconnect();\r\n if (this.pushManager) {\r\n this.pushManager.reset();\r\n this._pushEnablePromise = null;\r\n }\r\n this.connectionManager = null;\r\n this._chat = null;\r\n this._webrtc = null;\r\n\r\n this._initializeSubClients();\r\n } else if (this.connectionManager) {\r\n this.connectionManager.updateToken(token);\r\n } else {\r\n this._initializeSubClients();\r\n }\r\n\r\n this.logger.info('JWT token set', { userId });\r\n this.emit('tokenSet', { userId });\r\n };\r\n\r\n TalkFlowClient.prototype.updateToken = async function updateToken(newToken) {\r\n await this.setToken(newToken);\r\n };\r\n}\r\n"],"names":["EventEmitter","constructor","this","_events","Map","on","event","listener","has","set","Set","get","add","off","once","onceWrapper","args","apply","_originalListener","listeners","l","delete","size","emit","data","forEach","error","console","removeAllListeners","clear","listenerCount","eventNames","Array","from","keys","ConnectionState","DISCONNECTED","CONNECTING","CONNECTED","RECONNECTING","ERROR","ErrorTypes","CONNECTION_FAILED","CONNECTION_LOST","CONNECTION_TIMEOUT","JWT_INVALID","JWT_EXPIRED","JWT_PARSE_FAILED","UNAUTHORIZED","API_ERROR","API_TIMEOUT","CHAT_ROOM_NOT_FOUND","CHAT_MESSAGE_FAILED","CHAT_SUBSCRIPTION_FAILED","MEDIA_ACCESS_DENIED","SCREEN_SHARE_DENIED","PEER_CONNECTION_FAILED","ICE_CONNECTION_FAILED","SIGNALING_FAILED","DEVICE_SWITCH_FAILED","ENUMERATE_DEVICES_FAILED","CALL_ROOM_NOT_FOUND","INVALID_STATE","UNKNOWN_ERROR","SignalTypes","CALL_OFFER","CALL_ANSWER","ICE_CANDIDATE","JOIN_ROOM","LEAVE_ROOM","PEER_JOINED","PEER_LEFT","VIDEO_STATE_CHANGED","AUDIO_STATE_CHANGED","SCREEN_SHARE_STARTED","SCREEN_SHARE_ENDED","CALL_REQUEST","CALL_ACCEPT","CALL_REJECT","CALL_CANCEL","CALL_END","CALL_INVITATION","CALL_BUSY","ChatRoomType","DIRECT","GROUP","PRIVATE_GROUP","TEAM","RoomListEventType","MESSAGE_RECEIVED","MESSAGE_DELETED","MESSAGE_UPDATED","ROOM_CREATED","ROOM_JOINED","ROOM_LEFT","ROOM_UPDATED","ROOM_KICKED","ROOM_BANNED","MESSAGE_RETENTION_CLEANUP","WebSocketPaths","SOCKJS_ENDPOINT","NATIVE_ENDPOINT","APP_PREFIX","TOPIC_PREFIX","QUEUE_PREFIX","USER_PREFIX","getChatDestination","roomId","getChatReadDestination","getChatTypingDestination","getChatAssistantStreamDestination","ROOM_LIST_USER_DESTINATION","getWebRTCDestination","getWebRTCUserDestination","CHAT_SEND","CHAT_READ","CHAT_TYPING","WEBRTC_SIGNAL","Environment","DEVELOPMENT","STAGING","PRODUCTION","Endpoints","serverUrl","wsEndpoint","DefaultConfig","environment","reconnectDelay","maxReconnectAttempts","heartbeatIncoming","heartbeatOutgoing","apiTimeout","iceServers","urls","logLevel","getServerUrl","env","LogLevel","DEBUG","INFO","WARN","NONE","Logger","level","prefix","setLevel","setPrefix","_format","Date","toISOString","debug","info","warn","ApiClient","options","baseUrl","replace","apiKey","projectId","jwtToken","timeout","logger","setJwtToken","token","_getHeaders","headers","startsWith","request","method","path","body","params","url","searchParams","URLSearchParams","Object","entries","key","value","append","queryString","toString","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","JSON","stringify","undefined","signal","clearTimeout","name","timeoutError","Error","code","contentType","includes","json","text","ok","message","status","post","put","patch","upload","file","fieldName","onProgress","Promise","resolve","reject","xhr","XMLHttpRequest","form","FormData","open","setRequestHeader","onprogress","e","lengthComputable","loaded","total","percent","Math","round","onAbort","aborted","addEventListener","cleanupSignal","removeEventListener","onload","parsed","responseText","getResponseHeader","parse","onerror","err","ontimeout","onabort","send","stripBearer","base64UrlDecode","str","base64","padding","length","repeat","binaryString","atob","bytes","Uint8Array","c","charCodeAt","TextDecoder","decode","validateAndParseJWT","bufferSeconds","validateExpiry","parts","split","payload","originalError","exp","expiryTime","bufferTime","now","expiredAt","iat","clockSkew","userId","sub","user_id","extractUserIdFromJWT","isJWTExpired","BYTE","FrameImpl","_body","isBinaryBody","_binaryBody","binaryBody","TextEncoder","encode","command","escapeHeaderValues","skipContentLengthHeader","assign","fromRawFrame","rawFrame","trim","header","reverse","indexOf","hdrValueUnEscape","serializeCmdAndHeaders","serialize","cmdAndHeaders","toUnit8Array","buffer","lines","push","hdrValueEscape","isBodyEmpty","bodyLength","join","sizeOfUTF8","s","uint8CmdAndHeaders","nullTerminator","uint8Frame","marshall","Parser","onFrame","onIncomingPing","_encoder","_decoder","_token","_initState","parseChunk","segment","appendMissingNULLonIncoming","chunk","chunkWithNull","i","byte","_onByte","_collectFrame","_collectCommand","_reinjectByte","_results","_consumeTokenAsUTF8","_collectHeaders","_consumeByte","_collectHeaderKey","_setupCollectBody","_headerKey","_collectHeaderValue","contentLengthHeader","filter","_bodyBytesRemaining","parseInt","_collectBodyFixedSize","_collectBodyNullTerminated","_retrievedBody","_consumeTokenAsRaw","log","rawResult","StompSocketState","ActivationState","ReconnectionTimeMode","TickerStrategy","Ticker","_interval","_strategy","Interval","_debug","_workerScript","start","tick","stop","shouldUseWorker","runWorker","runInterval","disposeWorker","disposeInterval","Worker","_worker","URL","createObjectURL","Blob","type","onmessage","_timer","startTime","setInterval","terminate","clearInterval","Versions","versions","supportedVersions","protocolVersions","map","x","V1_0","V1_1","V1_2","default","StompHandler","connectedVersion","_connectedVersion","connected","_connected","_client","_webSocket","config","_serverFrameHandlers","frame","server","version","_escapeHeaderValues","_setupHeartbeat","onConnect","MESSAGE","subscription","onReceive","_subscriptions","onUnhandledMessage","client","messageId","ack","nack","RECEIPT","callback","_receiptWatchers","onUnhandledReceipt","onStompError","_counter","_partialData","_lastServerActivityTS","stompVersions","connectHeaders","disconnectHeaders","heartbeatToleranceMultiplier","heartbeatGracePeriods","splitLargeFrames","maxWebSocketChunkSize","forceBinaryWSFrames","logRawCommunication","discardWebsocketOnCommFailure","onDisconnect","onWebSocketClose","onWebSocketError","onUnhandledFrame","onHeartbeatReceived","onHeartbeatLost","parser","evt","rawChunkAsString","ArrayBuffer","onclose","closeEvent","_cleanUp","errorEvent","onopen","_transmit","serverOutgoing","serverIncoming","v","ttl","max","_pinger","heartbeatStrategy","readyState","OPEN","_ponger","delta","_closeOrDiscardWebsocket","discardWebsocket","_closeWebsocket","forceDisconnect","close","webSocket","msg","noOp","ts","id","random","substring","origOnClose","delay","getTime","reason","call","wasClean","rawChunk","out","dispose","receipt","watchForReceipt","publish","destination","hdrs","receiptId","subscribe","unsubscribe","begin","transactionId","txId","transaction","commit","subscriptionId","Client","_stompHandler","_disconnectHeaders","active","state","ACTIVE","_changeState","onChangeState","conf","connectionTimeout","_nextReconnectDelay","maxReconnectDelay","reconnectTimeMode","LINEAR","INACTIVE","beforeConnect","configure","activate","_activate","_connect","DEACTIVATING","deactivate","then","_connectionWatcher","_createWebSocket","_disposeStompHandler","_schedule_reconnect","webSocketFactory","brokerURL","WebSocket","binaryType","_reconnector","EXPONENTIAL","min","force","needToDispose","retPromise","CLOSED","origOnWebSocketClose","_checkConnection","TypeError","crypto","global","getRandomValues","browserCrypto","randomBytes","floor","_randomStringChars","string","ret","substr","number","numberString","t","slice","require$$0","onUnload","afterUnload","isChromePackagedApp","chrome","app","runtime","module","exports","attachEvent","document","detachEvent","unloadAdd","ref","triggerUnloadCallbacks","unloadDel","required","requiresPort","port","protocol","qs","prototype","hasOwnProperty","input","decodeURIComponent","encodeURIComponent","querystringify_1","obj","pairs","isNaN","query","part","result","exec","require$$1","controlOrWhitespace","CRHTLF","slashes","protocolre","windowsDriveLetter","trimLeft","rules","address","isSpecial","NaN","ignore","hash","lolcation","loc","location","window","self","finaldestination","Url","unescape","pathname","test","href","scheme","extractProtocol","rest","match","toLowerCase","forwardSlashes","otherSlashes","slashesCount","relative","extracted","instruction","index","instructions","lastIndexOf","charAt","base","concat","last","unshift","up","splice","host","hostname","username","password","auth","origin","fn","pop","char","ins","urlParse","getOrigin","p","isOriginEqual","a","b","isSchemeEqual","addPath","addQuery","q","isLoopbackAddr","addr","create","inherits_browserModule","ctor","superCtor","super_","enumerable","writable","configurable","TempCtor","EventTarget","_listeners","eventType","arr","idx","dispatchEvent","arguments","eventtarget","inherits","fired","g","removeListener","ai","addListener","emitter","utils","urlUtils","require$$2","require$$3","WebsocketDriver","Driver","MozWebSocket","websocketModule","require$$4","WebSocketTransport","transUrl","enabled","ws","unloadRef","_cleanup","transportName","roundTrips","websocket","BufferedSender","sender","sendBuffer","sendStop","sendSchedule","sendScheduleWait","tref","bufferedSender","Polling","Receiver","receiveUrl","AjaxObject","_scheduleReceiver","poll","pollIsClosing","polling","SenderReceiver","urlSuffix","senderFunc","pollUrl","senderReceiver","AjaxBasedTransport","opt","ajaxUrl","xo","createAjaxSender","ajaxBased","XhrReceiver","bufferPosition","_chunkHandler","bind","buf","XHR","AbstractXHRObject","opts","_start","noCredentials","supportsCORS","withCredentials","onreadystatechange","axo","cors","ignored","abstractXhr","XhrDriver","XHRCorsObject","xhrCors","XHRLocalObject","xhrLocal","browser","isOpera","navigator","userAgent","isKonqueror","hasDomain","domain","require$$5","XhrStreamingTransport","nullOrigin","needBody","xhrStreaming","eventUtils","XDRObject","xdr","XDomainRequest","_error","XdrStreamingTransport","cookie_needed","sameScheme","xdrStreaming","eventsource","EventSource","EventSourceReceiver","EventSourceDriver","es","decodeURI","_close","EventSourceTransport","WPrefix","currentWindowId","polluteGlobalNamespace","postMessage","parent","windowId","createIframe","iframeUrl","errorCallback","iframe","createElement","unattach","cleanup","parentNode","removeChild","src","style","display","position","appendChild","contentWindow","createHtmlfile","doc","CollectGarbage","r","write","parentWindow","iframeEnabled","iframeUtils","require$$6","IframeTransport","transport","iframeObj","onmessageCallback","_message","iframeMessage","cdata","object","isObject","extend","source","prop","objectUtils","iframeWrap","IframeWrapTransport","iframeInfo","sameOrigin","facadeTransport","HtmlfileReceiver","htmlfileEnabled","constructFunc","htmlfile","HtmlFileTransport","XhrPollingTransport","xhrPolling","XdrPollingTransport","xdrPolling","area","jsonp","enctype","acceptCharset","target","action","submit","completed","JsonpReceiver","urlWithId","_callback","_createScript","_abort","scriptErrorTimeout","aborting","script2","script","onclick","_scriptError","errorTimer","loadedOkay","charset","htmlFor","async","head","getElementsByTagName","insertBefore","firstChild","jsonpSender","JsonPTransport","jsonpPolling","transportList","require$$7","require$$8","defineProperty","ArrayPrototype","ObjectPrototype","FunctionPrototype","Function","StringPrototype","String","array_slice","_toString","isFunction","val","isString","supportsDescriptors","forceAssign","defineProperties","toObject","o","Empty","that","boundLength","boundArgs","bound","isArray","properlyBoxesNonStrict","properlyBoxesStrict","boxedString","splitString","fun","thisp","_","__","context","hasFirefox2IndexOfBug","sought","n","abs","compliantExecNpcg","string_split","separator","limit","separator2","lastIndex","lastLength","output","flags","ignoreCase","multiline","extended","sticky","lastLastIndex","RegExp","string_substr","hasNegativeSubstrBug","extraLookup","extraEscapable","_escape","quote","quoted","escapable","unrolled","fromCharCode","unrollLookup","availableTransports","filterToEnabled","transportsWhitelist","transports","main","facade","trans","logObject","levelExists","Event","initEvent","canBubble","cancelable","bubbles","timeStamp","stopPropagation","preventDefault","CAPTURING_PHASE","AT_TARGET","BUBBLING_PHASE","InfoAjax","t0","rtt","infoAjax","InfoReceiverIframe","ir","infoIframeReceiver","XDR","XHRCors","XHRLocal","XHRFake","to","xhrFake","InfoIframe","go","ifr","d","infoIframe","InfoReceiver","urlInfo","doXhr","_getReceiver","timeoutRef","infoReceiver","FacadeJS","_transport","_transportMessage","_transportClose","_send","InfoIframeReceiver","iframeBootstrap","SockJS","parentOrigin","transportMap","at","bootstrap_iframe","escape","require$$9","require$$10","require$$11","require$$12","require$$13","CloseEvent","require$$14","TransportMessageEvent","transMessage","require$$15","require$$16","protocols","extensions","protocols_whitelist","_transportsWhitelist","_transportOptions","transportOptions","_timeout","sessionId","_generateSessionId","_server","parsedUrl","SyntaxError","secure","sortedProtocols","sort","proto","_origin","_urlInfo","_ir","_receiveInfo","userSetCode","CLOSING","require$$17","_rto","countRTO","_transUrl","base_url","enabledTransports","_transports","Transport","shift","timeoutMs","_transportTimeoutId","_transportTimeout","transportUrl","transportObj","content","_open","forceFail","require$$18","entry","_sockjs_onload","getStompClient","StompJs","StompClient","getSockJS","ConnectionManager","super","useSockJS","stompClient","reconnectAttempts","subscriptions","pendingSubscriptions","_setState","prevState","_getWebSocketUrl","endpoint","_createStompClient","wsUrl","Authorization","_onConnect","_onDisconnect","_onStompError","_onWebSocketClose","_onWebSocketError","wsProtocol","wsHost","connect","_connectResolve","_connectReject","_drainPendingSubscriptions","_restoreSubscriptions","_processPendingSubscriptions","_handleReconnect","attempt","previousSubscriptions","_subscribeInternal","unsubscribeAll","updateToken","disconnect","pending","isConnected","getState","setLogLevel","destroy","ChatClient","connectionManager","apiClient","subscribedRooms","roomListSubscribed","_activeRoomId","_typingTimers","_assistantTypingTimers","_assistantTypingTimeoutMs","_assistantTypingUserId","_assistantTypingUserName","_assistantProgress","_assistantPhaseLabels","THINKING","SEARCHING","PLANNING","WRITING","_assistantProgressFallbackTtlMs","_seenChatMessageIdsByRoom","_seenRoomListMessageIdsByRoom","_maxSeenPerRoom","_shouldDedupMessage","bucket","seen","oldestKey","next","getRooms","lastId","lastSortValue","getRoom","enterRoom","wasAlreadySubscribed","subscribeRoom","setActiveRoom","getActiveRoom","clearActiveRoom","unsubscribeRoom","getRoomInfo","setMyRoomLanguage","language","createOneToOneRoom","friendId","createGroupRoom","roomType","roomName","description","invitedUserIds","messageRetentionHours","invitedAssistantPersonaIds","assistantMode","roomAiType","engagementIntensity","getAssistants","_unwrapSuccessResponse","getRoomAiMeta","rateAssistantMessage","rating","comment","Number","isInteger","summarizeWithAssistant","personaId","format","messageCount","translateWithAssistant","targetLang","sourceMessageId","getRoomPmPrompt","upsertRoomPmPrompt","activateRoomPmPrompt","deactivateRoomPmPrompt","getRoomPmPromptVersions","activateRoomPmPromptVersion","previewRoomPmPrompt","joinGroupRoom","leaveRoom","updateGroupRoom","inviteToGroupRoom","userIdsOrData","userIds","assistantPersonaIds","kickMember","banMember","unbanMember","getBannedMembers","pinMessage","unpinMessage","toggleReaction","emoji","getAvailableGroupRooms","getAllGroupRooms","getMessages","page","m","_normalizeMessageTimestamps","fetchLinkPreview","normalizedUrl","sendMessage","_generateMessageId","_sendMessageWithId","sendMessageOptimistic","baseMessageId","messageIds","_predictMessageIdsFromData","promise","_attachOptimisticMetadata","stopTyping","fileInfos","separateFiles","replyToMessageId","messages","sendTextMessage","sendTextMessageOptimistic","uploadFile","sendFileMessage","files","fileArray","_normalizeFileArray","metas","_uploadFiles","_applyFileMetadata","metadata","sendFileMessageOptimistic","_predictMessageIds","_predictGroupCount","_hasTextMessage","all","onUploadProgress","fileIndex","meta","itemMetadata","sendReply","sendReplyOptimistic","deleteMessage","deleteType","editMessage","_classifyFileType","mimeType","mime","typesSeen","fileType","groupCount","hasMessage","expectedCount","ids","separate","predictedMessageIds","actualMessageIds","predictionMatched","every","predicted","actual","optimistic","randomUUID","sentAt","_toEpochMillis","editedAt","replyTo","chatDestination","_handleChatMessage","readDestination","_handleReadEvent","typingDestination","_handleTypingEvent","assistantStreamDestination","_handleAssistantStreamEvent","memberJoinedHandler","members","participantCount","activeParticipantCount","timestamp","memberLeftHandler","subscribedAt","_clearTypingTimer","_clearAssistantTypingTimer","_clearAssistantProgress","unsubscribeAllRooms","subscribeRoomList","_handleRoomListEvent","unsubscribeRoomList","isRoomListSubscribed","markAsRead","translations","sourceLang","translationsOf","senderType","userName","typing","actorId","_isSelfInMembers","cutoffTime","resolvedRoomId","events","remainingUnreadCount","startTyping","existingTimer","timer","some","_startAssistantTypingTimer","phase","label","prev","streamId","_startAssistantProgressTimer","seq","fields","expiresAt","getSubscribedRooms","isSubscribed","MediaStreamManager","localStream","screenStream","videoEnabled","audioEnabled","_deviceChangeHandler","getUserMedia","constraints","video","audio","mediaDevices","stream","_getMediaErrorMessage","getDisplayMedia","getVideoTracks","onended","toggleVideo","videoTracks","track","toggleAudio","audioTracks","getAudioTracks","setVideoEnabled","setAudioEnabled","getLocalStream","getScreenStream","isVideoEnabled","isAudioEnabled","stopAll","getTracks","replaceTrack","oldTrack","newTrack","removeTrack","addTrack","kind","getTrack","tracks","getDevices","devices","enumerateDevices","videoInputs","audioInputs","audioOutputs","switchDevice","deviceId","exact","startDeviceChangeDetection","stopDeviceChangeDetection","getStreamInfo","hasStream","muted","applyVideoConstraints","videoTrack","applyConstraints","getVideoSettings","getSettings","getAudioSettings","audioTrack","PeerConnectionManager","peers","setLocalStream","peer","_addTracksToConnection","connection","setIceServers","servers","createPeerConnection","peerId","polite","iceCandidatePoolSize","RTCPeerConnection","peerState","makingOffer","ignoreOffer","isSettingRemoteAnswerPending","skipAutoNegotiation","_setupConnectionHandlers","onicecandidate","candidate","oniceconnectionstatechange","iceConnectionState","onconnectionstatechange","connectionState","onnegotiationneeded","setLocalDescription","localDescription","ontrack","streams","ondatachannel","channel","existingSenders","getSenders","find","createOffer","offer","createAnswer","answer","handleRemoteDescription","offerCollision","signalingState","setRemoteDescription","addIceCandidate","remoteDescription","getPeerConnection","getPeerIds","closePeerConnection","closeAllPeerConnections","promises","catch","getStats","getConnectionSummary","summary","WebRTCClient","mediaManager","peerManager","currentRoom","isGroupCall","participants","_iceServersFetched","_waitingForCallAccept","_pendingCallTarget","_pendingIceCandidates","_setupEventHandlers","initializeIceServers","getTurnCredentials","_setFallbackIceServers","_sendMediaState","_sendSignal","receiverId","sdp","createCallRoom","getCallRoom","joinCallRoomApi","leaveCallRoomApi","enableIncomingCalls","_incomingCallsEnabled","_subscribeToDirectSignaling","disableIncomingCalls","directDestination","isIncomingCallsEnabled","startCall","isGroup","mediaConstraints","_subscribeToSignaling","callUser","targetUserId","acceptCall","callerId","rejectCall","cancelCall","endCall","_unsubscribeFromSignaling","startScreenShare","localVideoTrack","currentVideoTrack","stopScreenShare","_handleSignal","mediaState","senderId","_handleUserJoined","_handleUserLeft","_handleOffer","_handleAnswer","_handleIceCandidate","_handleMediaState","isInCall","callRoomId","title","hostUserId","maxParticipants","createdAt","_handleCallAccepted","_handleCallEnded","joinedAt","_processPendingIceCandidates","_queueIceCandidate","RTCIceCandidate","candidates","getCurrentRoom","getParticipants","getMediaState","streamInfo","PushErrorCode","freeze","UNSUPPORTED_BROWSER","FIREBASE_NOT_INSTALLED","SW_REGISTER_FAILED","PERMISSION_DENIED","TOKEN_FAILED","SERVER_REGISTER_FAILED","PushError","cause","DEFAULT_FIREBASE_CONFIG","authDomain","storageBucket","messagingSenderId","appId","PENDING_NAVIGATION_STORE_NAME","PENDING_NAVIGATION_LEGACY_KEY","DEVICE_STORE_NAME","DEVICE_ID_KEY","LEGACY_LOCAL_STORAGE_DEVICE_ID_KEY","openPendingNavigationDb","indexedDB","onupgradeneeded","db","objectStoreNames","contains","createObjectStore","keyPath","onsuccess","readDeviceIdFromIndexedDb","tx","objectStore","record","oncomplete","writeDeviceIdToIndexedDb","generateDeviceId","consumePendingRoomFromIndexedDB","primaryKey","PENDING_NAVIGATION_KEY_PREFIX","buildPendingNavigationKey","shouldTryLegacyFallback","store","pendingRoomId","primaryRequest","legacyRequest","legacyRecord","PushManager","firebaseConfig","vapidKey","serviceWorkerPath","_messaging","_currentToken","_enabled","_enablePromise","_foregroundMessageUnsubscribe","_swRegistration","_projectRegisteredToSW","_visibilityHandler","_deviceIdCache","consumePendingRoom","getPermissionState","Notification","permission","enable","_enableInternal","firebaseApp","firebaseMessaging","import","getApp","initializeApp","registration","_registerServiceWorker","requestPermission","getMessaging","tokenOptions","serviceWorkerRegistration","getToken","_registerTokenToServer","onMessage","_onForegroundMessage","_registerProjectToSW","_installVisibilityReRegister","update","swVersion","_getSWVersion","serviceWorker","register","updateViaCache","_getDeviceId","deviceToken","deviceType","localStorage","legacy","getItem","removeItem","fallbackId","reset","_uninstallVisibilityReRegister","_unregisterProjectFromSW","sw","ready","visibilityState","isEnabled","getMyDevices","setDeviceEnabled","setCurrentDeviceEnabled","readyRegistration","waiting","installing","MessageChannel","port1","port2","CONNECTION_EVENT_MAP","CHAT_EVENT_MAP","WEBRTC_EVENT_MAP","forwardMappedEvents","eventMap","sourceEvent","targetEvent","CHAT_DELEGATE_METHODS","WEBRTC_DELEGATE_METHODS","defineDelegates","targetKey","methods","TalkFlowClient","_validateOptions","autoSubscribeRoomList","_initialized","_state","_userId","_chat","_webrtc","_pushManager","_pushEnablePromise","_initializeSubClients","hasToken","chat","webrtc","pushManager","_setupEventForwarding","initializeSubClients","registerUser","userData","preferredLanguage","detected","detectBrowserLanguage","accessToken","setToken","updateMyInfo","_checkToken","setPreferredLanguage","displayText","myLang","checkUserExists","projectUserId","getUsers","searchUsers","keyword","getUserId","isInitialized","isReady","getStatus","initialized","jwt","enablePush","enablePushNotifications","logout","PUSH_DEDUP_TTL_MS","activeRoomId","_recentPushMessageIds","notification","alreadyEnabled","finally","getPushPermissionState","isPushEnabled","getPushToken","resetPush","needReinitialize","newToken","applySessionMethods","applyDelegatedMethods","GENERAL","PEOPLE_ONLY","CALL_ONLY","TEXT","IMAGE","FILE","VIDEO","AUDIO","SYSTEM","QUIET","NORMAL","LEGAL_ADVISOR","MARKETING","PRODUCT_PLANNING","HR","FINANCE","CUSTOMER_SUPPORT","SALES","ENGINEERING","DATA_ANALYST","PROJECT_MANAGEMENT","RESEARCH","TRANSLATION","DESIGN","PM","SUPER_ADMIN","PROJECT_ADMIN","ROOM_OWNER","PROJECT","ROOM","PERSONA_MULTI","PM_BACKSTAGE","USER","ASSISTANT","MINUTES","SHORT","TIMELINE","ACTIONS","OPTIONS","Infinity"],"mappings":"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","x_google_ignoreList":[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]}
|