@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
package/dist/talkflow-sdk.umd.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@stomp/stompjs"),require("sockjs-client")):"function"==typeof define&&define.amd?define(["exports","@stomp/stompjs","sockjs-client"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TalkFlowSDK={},e.StompJs,e.SockJS)}(this,function(e,t,s){"use strict";class i{constructor(){this._events=new Map}on(e,t){return this._events.has(e)||this._events.set(e,new Set),this._events.get(e).add(t),()=>this.off(e,t)}once(e,t){const s=(...i)=>{this.off(e,s),t.apply(this,i)};return s._originalListener=t,this.on(e,s)}off(e,t){const s=this._events.get(e);if(s){for(const e of s)if(e===t||e._originalListener===t){s.delete(e);break}0===s.size&&this._events.delete(e)}}emit(e,t){const s=this._events.get(e);s&&s.forEach(s=>{try{s(t)}catch(t){console.error(`Error in event listener for '${e}':`,t)}})}removeAllListeners(e){e?this._events.delete(e):this._events.clear()}listenerCount(e){const t=this._events.get(e);return t?t.size:0}eventNames(){return Array.from(this._events.keys())}}const n={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected",RECONNECTING:"reconnecting",ERROR:"error"},r={CONNECTION_FAILED:"CONNECTION_FAILED",CONNECTION_LOST:"CONNECTION_LOST",CONNECTION_TIMEOUT:"CONNECTION_TIMEOUT",JWT_INVALID:"JWT_INVALID",JWT_EXPIRED:"JWT_EXPIRED",JWT_PARSE_FAILED:"JWT_PARSE_FAILED",UNAUTHORIZED:"UNAUTHORIZED",API_ERROR:"API_ERROR",API_TIMEOUT:"API_TIMEOUT",CHAT_ROOM_NOT_FOUND:"CHAT_ROOM_NOT_FOUND",CHAT_MESSAGE_FAILED:"CHAT_MESSAGE_FAILED",CHAT_SUBSCRIPTION_FAILED:"CHAT_SUBSCRIPTION_FAILED",MEDIA_ACCESS_DENIED:"MEDIA_ACCESS_DENIED",SCREEN_SHARE_DENIED:"SCREEN_SHARE_DENIED",PEER_CONNECTION_FAILED:"PEER_CONNECTION_FAILED",ICE_CONNECTION_FAILED:"ICE_CONNECTION_FAILED",SIGNALING_FAILED:"SIGNALING_FAILED",DEVICE_SWITCH_FAILED:"DEVICE_SWITCH_FAILED",ENUMERATE_DEVICES_FAILED:"ENUMERATE_DEVICES_FAILED",CALL_ROOM_NOT_FOUND:"CALL_ROOM_NOT_FOUND",INVALID_STATE:"INVALID_STATE",UNKNOWN_ERROR:"UNKNOWN_ERROR"},a={CALL_OFFER:"call_offer",CALL_ANSWER:"call_answer",ICE_CANDIDATE:"ice_candidate",JOIN_ROOM:"join_room",LEAVE_ROOM:"leave_room",PEER_JOINED:"peer_joined",PEER_LEFT:"peer_left",VIDEO_STATE_CHANGED:"video_state_changed",AUDIO_STATE_CHANGED:"audio_state_changed",SCREEN_SHARE_STARTED:"screen_share_started",SCREEN_SHARE_ENDED:"screen_share_ended",CALL_REQUEST:"call_request",CALL_ACCEPT:"call_accept",CALL_REJECT:"call_reject",CALL_CANCEL:"call_cancel",CALL_END:"call_end",CALL_INVITATION:"call_invitation",CALL_BUSY:"call_busy"},o={DIRECT:"DIRECT",GROUP:"GROUP",PRIVATE_GROUP:"PRIVATE_GROUP",TEAM:"TEAM"},c={MESSAGE_RECEIVED:"MESSAGE_RECEIVED",MESSAGE_DELETED:"MESSAGE_DELETED",MESSAGE_UPDATED:"MESSAGE_UPDATED",ROOM_CREATED:"ROOM_CREATED",ROOM_JOINED:"ROOM_JOINED",ROOM_LEFT:"ROOM_LEFT",ROOM_UPDATED:"ROOM_UPDATED",ROOM_KICKED:"ROOM_KICKED",ROOM_BANNED:"ROOM_BANNED",MESSAGE_RETENTION_CLEANUP:"MESSAGE_RETENTION_CLEANUP"},l={SOCKJS_ENDPOINT:"/ws-chat",NATIVE_ENDPOINT:"/ws-chat-native",APP_PREFIX:"/app",TOPIC_PREFIX:"/topic",QUEUE_PREFIX:"/queue",USER_PREFIX:"/user",getChatDestination:e=>`/topic/chat/${e}`,getChatReadDestination:e=>`/topic/chat/${e}/read`,getChatTypingDestination:e=>`/topic/chat/${e}/typing`,ROOM_LIST_USER_DESTINATION:"/user/queue/rooms",getWebRTCDestination:e=>`/topic/webrtc/${e}`,getWebRTCUserDestination:()=>"/user/queue/webrtc",CHAT_SEND:"/app/chat/send",CHAT_READ:"/app/chat/read",CHAT_TYPING:"/app/chat/typing",WEBRTC_SIGNAL:"/app/webrtc/signal"},d={DEVELOPMENT:"development",STAGING:"staging",PRODUCTION:"production"},h={[d.DEVELOPMENT]:{serverUrl:"https://dev-chat.apiorbit.net",wsEndpoint:"/ws-chat"},[d.STAGING]:{serverUrl:"https://stg-api.talk-x.app",wsEndpoint:"/ws-chat"},[d.PRODUCTION]:{serverUrl:"https://prod-api.talk-x.app",wsEndpoint:"/ws-chat"}},g={environment:d.PRODUCTION,reconnectDelay:5e3,maxReconnectAttempts:10,heartbeatIncoming:1e4,heartbeatOutgoing:1e4,apiTimeout:3e4,iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],logLevel:2};function p(e){return(h[e]||h[d.PRODUCTION]).serverUrl}const u={DEBUG:0,INFO:1,WARN:2,ERROR:3,NONE:4};class m{constructor(e=u.WARN,t="TalkFlow"){this.level=e,this.prefix=t}setLevel(e){this.level=e}setPrefix(e){this.prefix=e}_format(e,...t){return[`[${(new Date).toISOString()}] [${e}] [${this.prefix}]`,...t]}debug(...e){this.level<=u.DEBUG&&console.debug(...this._format("DEBUG",...e))}info(...e){this.level<=u.INFO&&console.info(...this._format("INFO",...e))}warn(...e){this.level<=u.WARN&&console.warn(...this._format("WARN",...e))}error(...e){this.level<=u.ERROR&&console.error(...this._format("ERROR",...e))}}class E{constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,""),this.apiKey=e.apiKey,this.projectId=e.projectId,this.jwtToken=e.jwtToken,this.timeout=e.timeout||g.apiTimeout,this.logger=new m(e.logLevel,"ApiClient")}setJwtToken(e){this.jwtToken=e}_getHeaders(){const e={"Content-Type":"application/json","X-API-KEY":this.apiKey,"X-PROJECT-ID":this.projectId};return this.jwtToken&&(e.Authorization=this.jwtToken.startsWith("Bearer ")?this.jwtToken:`Bearer ${this.jwtToken}`),e}async request(e,t,s={}){const{body:i,params:n}=s;let a=`${this.baseUrl}${t}`;if(n){const e=new URLSearchParams;Object.entries(n).forEach(([t,s])=>{null!=s&&e.append(t,s)});const t=e.toString();t&&(a+=`?${t}`)}const o=new AbortController,c=setTimeout(()=>o.abort(),this.timeout);let l;try{this.logger.debug(`${e} ${a}`,i?{body:i}:""),l=await fetch(a,{method:e,headers:this._getHeaders(),body:i?JSON.stringify(i):void 0,signal:o.signal})}catch(t){if(clearTimeout(c),"AbortError"===t.name){const e=new Error("Request timeout");throw e.code=r.API_TIMEOUT,e}throw this.logger.error(`API Error: ${e} ${a}`,t),t}clearTimeout(c);const d=l.headers.get("content-type");let h;if(h=d&&d.includes("application/json")?await l.json():await l.text(),!l.ok){const t=new Error(h?.message||`HTTP ${l.status}`);throw t.code=r.API_ERROR,t.status=l.status,t.response=h,this.logger.error(`API Error: ${e} ${a}`,t),t}return this.logger.debug(`Response ${l.status}:`,h),h}get(e,t){return this.request("GET",e,{params:t})}post(e,t,s){return this.request("POST",e,{body:t,params:s})}put(e,t){return this.request("PUT",e,{body:t})}patch(e,t){return this.request("PATCH",e,{body:t})}delete(e,t){return this.request("DELETE",e,{params:t})}upload(e,t,s={}){const{fieldName:i="file",onProgress:n,signal:a,timeout:o=6e5}=s;return new Promise((s,c)=>{const l=`${this.baseUrl}${e}`,d=new XMLHttpRequest,h=new FormData;h.append(i,t),d.open("POST",l),d.timeout=o,d.setRequestHeader("X-API-KEY",this.apiKey),d.setRequestHeader("X-PROJECT-ID",this.projectId),this.jwtToken&&d.setRequestHeader("Authorization",this.jwtToken.startsWith("Bearer ")?this.jwtToken:`Bearer ${this.jwtToken}`),"function"==typeof n&&(d.upload.onprogress=e=>{e.lengthComputable&&n({loaded:e.loaded,total:e.total,percent:Math.round(e.loaded/e.total*100)})});const g=()=>d.abort();if(a){if(a.aborted)return void c(new Error("Upload cancelled"));a.addEventListener("abort",g)}const p=()=>{a&&a.removeEventListener("abort",g)};d.onload=()=>{p();let e=d.responseText;if((d.getResponseHeader("content-type")||"").includes("application/json"))try{e=JSON.parse(d.responseText)}catch{}if(d.status>=200&&d.status<300)this.logger.debug(`Upload ${d.status}:`,e),s(e);else{const t=new Error(e?.message||`HTTP ${d.status}`);t.code=r.API_ERROR,t.status=d.status,t.response=e,this.logger.error(`Upload Error: POST ${l}`,t),c(t)}},d.onerror=()=>{p();const e=new Error("Network error during upload");e.code=r.API_ERROR,this.logger.error(`Upload Network Error: POST ${l}`,e),c(e)},d.ontimeout=()=>{p();const e=new Error("Upload timeout");e.code=r.API_TIMEOUT,this.logger.error(`Upload Timeout: POST ${l}`,e),c(e)},d.onabort=()=>{p(),c(new Error("Upload cancelled"))},this.logger.debug(`POST ${l} (multipart, ${t.size} bytes)`),d.send(h)})}}function _(e){return e&&"string"==typeof e?e.replace(/^Bearer\s+/i,""):""}function b(e){let t=e.replace(/-/g,"+").replace(/_/g,"/");const s=t.length%4;s&&(t+="=".repeat(4-s));const i=atob(t),n=Uint8Array.from(i,e=>e.charCodeAt(0));return(new TextDecoder).decode(n)}function I(e,t={}){const{bufferSeconds:s=30,validateExpiry:i=!0}=t;if(!e||"string"!=typeof e){const e=new Error("JWT token is required");throw e.code=r.JWT_INVALID,e}const n=_(e).split(".");if(3!==n.length){const e=new Error("Invalid JWT format: expected 3 parts separated by dots");throw e.code=r.JWT_INVALID,e}let a;try{a=JSON.parse(b(n[1]))}catch(e){const t=new Error("Invalid JWT: failed to decode payload");throw t.code=r.JWT_PARSE_FAILED,t.originalError=e,t}if(i&&a.exp){const e=1e3*a.exp,t=1e3*s,i=Date.now();if(e<i+t){const t=new Error(e<i?"JWT token expired":`JWT token expires within ${s} seconds`);throw t.code=r.JWT_EXPIRED,t.expiredAt=new Date(e).toISOString(),t}}if(a.iat){const e=6e4;if(1e3*a.iat>Date.now()+e){const e=new Error("JWT token issued in the future");throw e.code=r.JWT_INVALID,e}}const o=a.userId||a.sub||a.user_id;if(!o){const e=new Error("Cannot extract userId from JWT: missing userId, sub, or user_id claim");throw e.code=r.JWT_INVALID,e}return{userId:o,payload:a}}function S(e){try{const t=_(e).split(".");if(3!==t.length)return null;const s=JSON.parse(b(t[1]));return s.userId||s.sub||s.user_id||null}catch(e){return console.error("Failed to extract userId from JWT:",e),null}}function T(e,t=0){try{const s=_(e).split(".");if(3!==s.length)return!0;const i=JSON.parse(b(s[1]));if(!i.exp)return!1;const n=1e3*i.exp,r=1e3*t;return n<Date.now()+r}catch(e){return console.error("Failed to check JWT expiration:",e),!0}}const C=()=>"undefined"!=typeof window&&window.SockJS?window.SockJS:s;class v extends i{constructor(e){super(),this.serverUrl=e.serverUrl.replace(/\/$/,""),this.jwtToken=e.jwtToken,this.apiKey=e.apiKey,this.projectId=e.projectId,this.useSockJS=!1!==e.useSockJS,this.reconnectDelay=e.reconnectDelay||g.reconnectDelay,this.maxReconnectAttempts=e.maxReconnectAttempts||g.maxReconnectAttempts,this.heartbeatIncoming=e.heartbeatIncoming||g.heartbeatIncoming,this.heartbeatOutgoing=e.heartbeatOutgoing||g.heartbeatOutgoing,this.logger=new m(e.logLevel||u.WARN,"ConnectionManager"),this.state=n.DISCONNECTED,this.stompClient=null,this.reconnectAttempts=0,this.subscriptions=new Map,this.pendingSubscriptions=[],this.userId=null}_setState(e){const t=this.state;this.state=e,this.emit("stateChange",{state:e,prevState:t}),this.logger.info(`State changed: ${t} -> ${e}`)}_getWebSocketUrl(){const e=this.useSockJS?l.SOCKJS_ENDPOINT:l.NATIVE_ENDPOINT;return`${this.serverUrl}${e}`}_createStompClient(){const e=this._getWebSocketUrl(),s={connectHeaders:{Authorization:this.jwtToken.startsWith("Bearer ")?this.jwtToken:`Bearer ${this.jwtToken}`,"X-API-KEY":this.apiKey,"X-PROJECT-ID":this.projectId},heartbeatIncoming:this.heartbeatIncoming,heartbeatOutgoing:this.heartbeatOutgoing,reconnectDelay:this.reconnectDelay,debug:e=>{this.logger.level<=u.DEBUG&&this.logger.debug("STOMP:",e)},onConnect:e=>this._onConnect(e),onDisconnect:e=>this._onDisconnect(e),onStompError:e=>this._onStompError(e),onWebSocketClose:e=>this._onWebSocketClose(e),onWebSocketError:e=>this._onWebSocketError(e)};if(this.useSockJS){const t=C();s.webSocketFactory=()=>new t(e)}else{const e=this.serverUrl.startsWith("https")?"wss":"ws",t=this.serverUrl.replace(/^https?:\/\//,"");s.brokerURL=`${e}://${t}${l.NATIVE_ENDPOINT}`}return new("undefined"!=typeof window&&window.StompJs&&window.StompJs.Client?window.StompJs.Client:t.Client)(s)}async connect(){if(this.state!==n.CONNECTED){if(this.state!==n.CONNECTING)return this._setState(n.CONNECTING),this.reconnectAttempts=0,new Promise((e,t)=>{this._connectResolve=e,this._connectReject=t;try{this.stompClient=this._createStompClient(),this.stompClient.activate()}catch(e){this._setState(n.ERROR),this._drainPendingSubscriptions("Connection activation failed"),t(e)}});this.logger.warn("Connection in progress")}else this.logger.warn("Already connected")}_onConnect(e){this._setState(n.CONNECTED),this.reconnectAttempts=0,this.logger.info("Connected to server",e),this._restoreSubscriptions(),this._processPendingSubscriptions(),this.emit("connected",{frame:e}),this._connectResolve&&(this._connectResolve(),this._connectResolve=null,this._connectReject=null)}_onDisconnect(e){this.logger.info("Disconnected from server",e),this._setState(n.DISCONNECTED),this.emit("disconnected",{frame:e})}_onStompError(e){this.logger.error("STOMP error:",e),this._setState(n.ERROR);const t={type:r.CONNECTION_FAILED,message:e.headers?.message||"STOMP error",frame:e};this._drainPendingSubscriptions(`STOMP error: ${t.message}`),this.emit("error",t),this._connectReject&&(this._connectReject(new Error(t.message)),this._connectResolve=null,this._connectReject=null)}_onWebSocketClose(e){this.logger.warn("WebSocket closed:",e),this.state!==n.DISCONNECTED&&this._handleReconnect()}_onWebSocketError(e){this.logger.error("WebSocket error:",e),this._connectReject&&(this._drainPendingSubscriptions("WebSocket connection failed"),this._connectReject(new Error("WebSocket connection failed")),this._connectResolve=null,this._connectReject=null)}_handleReconnect(){if(this.reconnectAttempts++,this.reconnectAttempts>this.maxReconnectAttempts){if(this.stompClient){try{this.stompClient.deactivate()}catch(e){this.logger.warn("Error deactivating stomp client on max reconnect:",e)}this.stompClient=null}return this._drainPendingSubscriptions("Max reconnection attempts exceeded"),this._setState(n.ERROR),void this.emit("error",{type:r.CONNECTION_LOST,message:"Max reconnection attempts exceeded"})}this._setState(n.RECONNECTING),this.emit("reconnecting",{attempt:this.reconnectAttempts}),this.logger.info(`Reconnecting... attempt ${this.reconnectAttempts}`)}_restoreSubscriptions(){if(0===this.subscriptions.size)return;this.logger.info(`Restoring ${this.subscriptions.size} subscriptions after reconnect`);const e=new Map(this.subscriptions);this.subscriptions.clear(),e.forEach(({callback:e,headers:t},s)=>{this._subscribeInternal(s,e,t),this.logger.debug(`Restored subscription: ${s}`)})}_processPendingSubscriptions(){for(;this.pendingSubscriptions.length>0;){const{destination:e,callback:t,headers:s,resolve:i}=this.pendingSubscriptions.shift(),n=this._subscribeInternal(e,t,s);i&&i(n)}}_subscribeInternal(e,t,s={}){const i=this.stompClient.subscribe(e,e=>{try{const s=JSON.parse(e.body);t(s,e)}catch(s){this.logger.error("Failed to parse message:",s),t(e.body,e)}},s);return this.subscriptions.set(e,{subscription:i,callback:t,headers:s}),this.logger.debug(`Subscribed to: ${e}`),i}subscribe(e,t,s={}){return this.subscriptions.has(e)&&this.unsubscribe(e),this.state!==n.CONNECTED?new Promise((i,n)=>{this.pendingSubscriptions.push({destination:e,callback:t,headers:s,resolve:i,reject:n}),this.logger.debug(`Queued subscription for: ${e}`)}):Promise.resolve(this._subscribeInternal(e,t,s))}unsubscribe(e){const t=this.subscriptions.get(e);t&&(t.subscription.unsubscribe(),this.subscriptions.delete(e),this.logger.debug(`Unsubscribed from: ${e}`))}unsubscribeAll(){this.subscriptions.forEach(({subscription:e},t)=>{e.unsubscribe(),this.logger.debug(`Unsubscribed from: ${t}`)}),this.subscriptions.clear()}send(e,t,s={}){return this.state!==n.CONNECTED?(this.logger.warn("Cannot send message: not connected"),!1):(this.stompClient.publish({destination:e,body:JSON.stringify(t),headers:s}),this.logger.debug(`Sent to ${e}:`,t),!0)}updateToken(e){this.jwtToken=e,this.stompClient&&(this.stompClient.connectHeaders={...this.stompClient.connectHeaders,Authorization:e.startsWith("Bearer ")?e:`Bearer ${e}`})}async disconnect(){this._drainPendingSubscriptions("Disconnected before subscription completed"),this.stompClient&&(this.unsubscribeAll(),await this.stompClient.deactivate(),this.stompClient=null),this._setState(n.DISCONNECTED),this.logger.info("Disconnected")}_drainPendingSubscriptions(e){if(0===this.pendingSubscriptions.length)return;const t=this.pendingSubscriptions;this.pendingSubscriptions=[],t.forEach(({destination:t,reject:s})=>{s&&s(new Error(`${e}: ${t}`))}),this.logger.debug(`Drained ${t.length} pending subscriptions: ${e}`)}isConnected(){return this.state===n.CONNECTED}getState(){return this.state}setLogLevel(e){this.logger.setLevel(e)}async destroy(){await this.disconnect(),this.removeAllListeners(),this.logger.info("ConnectionManager destroyed")}}class f extends i{constructor(e){super(),this.connectionManager=e.connectionManager,this.apiClient=e.apiClient,this.userId=e.userId,this.logger=new m(e.logLevel||u.WARN,"ChatClient"),this.subscribedRooms=new Map,this.roomListSubscribed=!1,this._activeRoomId=null,this._typingTimers=new Map,this._assistantTypingTimers=new Map,this._assistantTypingTimeoutMs=3e4,this._assistantTypingUserId="__assistant__",this._assistantTypingUserName="AI",this._seenChatMessageIdsByRoom=new Map,this._seenRoomListMessageIdsByRoom=new Map,this._maxSeenPerRoom=200}_shouldDedupMessage(e,t,s){if(!t||!s)return!1;let i=e.get(t);if(i||(i=new Map,e.set(t,i)),i.has(s))return!0;if(i.set(s,!0),i.size>this._maxSeenPerRoom){const e=i.keys().next().value;i.delete(e)}return!1}async getRooms(e={}){const{size:t=50,lastId:s,lastSortValue:i,type:n}=e;return this.apiClient.get("/api/v1/rooms/my",{size:t,lastId:s,lastSortValue:i,type:n})}async getRoom(e){return this.apiClient.get(`/api/v1/rooms/${e}`)}async enterRoom(e){const t=this.subscribedRooms.has(e);t||await this.subscribeRoom(e),this.setActiveRoom(e);try{return await this.getRoom(e)}catch(s){throw this.getActiveRoom()===e&&this.clearActiveRoom(),t||this.unsubscribeRoom(e),s}}async getRoomInfo(e){return this.apiClient.get(`/api/v1/rooms/${e}/info`)}async setMyRoomLanguage(e,t){return this.apiClient.put(`/api/v1/rooms/${e}/my-language`,{language:t})}async createOneToOneRoom(e){return this.apiClient.post(`/api/v1/rooms/direct/${e}`)}async createGroupRoom(e){const t=e.roomType||o.GROUP;if(t===o.PRIVATE_GROUP){if(!e.password||e.password.length<4)throw new Error("PRIVATE_GROUP requires a password of at least 4 characters")}else if(t===o.GROUP){if(e.password)throw new Error("Public GROUP rooms cannot have a password")}else if(t===o.TEAM&&e.password)throw new Error("TEAM rooms cannot have a password (invite-only)");return this.apiClient.post("/api/v1/rooms/group",{roomName:e.roomName,description:e.description,invitedUserIds:e.invitedUserIds,roomType:t,password:e.password,messageRetentionHours:e.messageRetentionHours,invitedAssistantPersonaIds:e.invitedAssistantPersonaIds,assistantMode:e.assistantMode,roomAiType:e.roomAiType,engagementIntensity:e.engagementIntensity})}async getAssistants(){const e=await this.apiClient.get("/api/v1/assistants");return this._unwrapSuccessResponse(e)}async getRoomAiMeta(){const e=await this.apiClient.get("/api/v1/rooms/ai-meta");return this._unwrapSuccessResponse(e)}async rateAssistantMessage(e,t,s,i){if(!Number.isInteger(s)||s<1||s>5)throw new Error("rating must be an integer between 1 and 5");await this.apiClient.post(`/api/v1/chat-messages/${e}/messages/${t}/rating`,{rating:s,comment:i})}async summarizeWithAssistant(e,t={}){const{personaId:s,format:i,messageCount:n}=t;if(!s)throw new Error("summarizeWithAssistant requires options.personaId");const r=await this.apiClient.post(`/api/v1/rooms/${e}/assistant/tools/summarize`,{personaId:s,format:i,messageCount:n});return this._unwrapSuccessResponse(r)}async translateWithAssistant(e,t={}){const{personaId:s,targetLang:i,sourceMessageId:n}=t;if(!s)throw new Error("translateWithAssistant requires options.personaId");if(!n)throw new Error("translateWithAssistant requires options.sourceMessageId");const r=await this.apiClient.post(`/api/v1/rooms/${e}/assistant/tools/translate`,{personaId:s,targetLang:i,sourceMessageId:n});return this._unwrapSuccessResponse(r)}async getRoomPmPrompt(e){const t=await this.apiClient.get(`/api/v1/rooms/${e}/pm-prompt`);return this._unwrapSuccessResponse(t)}async upsertRoomPmPrompt(e,t){if("string"!=typeof t||!t.trim())throw new Error("upsertRoomPmPrompt requires non-blank content");if(t.length>2e3)throw new Error("upsertRoomPmPrompt content exceeds 2000 characters");const s=await this.apiClient.put(`/api/v1/rooms/${e}/pm-prompt`,{content:t});return this._unwrapSuccessResponse(s)}async activateRoomPmPrompt(e){const t=await this.apiClient.patch(`/api/v1/rooms/${e}/pm-prompt/activate`);return this._unwrapSuccessResponse(t)}async deactivateRoomPmPrompt(e){const t=await this.apiClient.patch(`/api/v1/rooms/${e}/pm-prompt/deactivate`);return this._unwrapSuccessResponse(t)}async getRoomPmPromptVersions(e){const t=await this.apiClient.get(`/api/v1/rooms/${e}/pm-prompt/versions`);return this._unwrapSuccessResponse(t)}async activateRoomPmPromptVersion(e,t){if(!Number.isInteger(t)||t<1)throw new Error("activateRoomPmPromptVersion requires a positive integer version");const s=await this.apiClient.post(`/api/v1/rooms/${e}/pm-prompt/versions/${t}/activate`);return this._unwrapSuccessResponse(s)}async previewRoomPmPrompt(e){const t=await this.apiClient.get(`/api/v1/rooms/${e}/pm-prompt/preview`);return this._unwrapSuccessResponse(t)}async joinGroupRoom(e,t){const s=t?{password:t}:void 0;return this.apiClient.post(`/api/v1/rooms/group/${e}/join`,s)}async leaveRoom(e){const t=await this.apiClient.post(`/api/v1/rooms/${e}/leave`);return this.unsubscribeRoom(e),t}async updateGroupRoom(e,t){return this.apiClient.put(`/api/v1/rooms/group/${e}`,t)}async inviteToGroupRoom(e,t){const s=Array.isArray(t)?{userIds:t}:{userIds:t?.userIds,assistantPersonaIds:t?.assistantPersonaIds};return this.apiClient.post(`/api/v1/rooms/group/${e}/invite`,s)}async kickMember(e,t){return this.apiClient.delete(`/api/v1/rooms/${e}/members/${t}`)}async banMember(e,t){return this.apiClient.post(`/api/v1/rooms/${e}/banned-members`,{userId:t})}async unbanMember(e,t){return this.apiClient.delete(`/api/v1/rooms/${e}/banned-members/${t}`)}async getBannedMembers(e){const t=await this.apiClient.get(`/api/v1/rooms/${e}/banned-members`);return t&&"object"==typeof t&&"data"in t?t.data:t}async pinMessage(e,t){return this.apiClient.post(`/api/v1/rooms/group/${e}/pin/${t}`)}async unpinMessage(e){return this.apiClient.post(`/api/v1/rooms/group/${e}/unpin`)}async toggleReaction(e,t,s){return this.apiClient.post(`/api/v1/chat-messages/${e}/messages/${t}/reaction`,{emoji:s})}async getAvailableGroupRooms(e={}){const{size:t=50,lastId:s,lastSortValue:i}=e;return this.apiClient.get("/api/v1/rooms/groups/available",{size:t,lastId:s,lastSortValue:i})}async getAllGroupRooms(e={}){const{size:t=50,lastId:s,lastSortValue:i}=e;return this.apiClient.get("/api/v1/rooms/groups/all",{size:t,lastId:s,lastSortValue:i})}async getMessages(e,t={}){const{size:s=50,lastId:i,lastSortValue:n}=t,r=await this.apiClient.get(`/api/v1/chat-messages/${e}/list`,{size:s,lastId:i,lastSortValue:n}),a=this._unwrapSuccessResponse(r);return a&&Array.isArray(a.content)&&a.content.forEach(e=>this._normalizeMessageTimestamps(e)),r}async fetchLinkPreview(e){const t="string"==typeof e?e.trim():"";if(!t)throw new Error("url is required");const s=await this.apiClient.post("/api/v1/link-preview",{url:t});return this._unwrapSuccessResponse(s)}async sendMessage(e,t){const s=this._generateMessageId();return this._sendMessageWithId(e,s,t)}sendMessageOptimistic(e,t){const s=this._generateMessageId(),i=this._predictMessageIdsFromData(s,t),n=this._sendMessageWithId(e,s,t).then(e=>this._attachOptimisticMetadata(i,e));return{baseMessageId:s,messageIds:i,promise:n}}async _sendMessageWithId(e,t,s){this.stopTyping(e);const i=await this.apiClient.post(`/api/v1/chat-messages/${e}/send`,{messageId:t,message:s.message,fileInfos:s.fileInfos,separateFiles:!1!==s.separateFiles,replyToMessageId:s.replyToMessageId}),n=this._unwrapSuccessResponse(i);return n&&Array.isArray(n.messages)&&n.messages.forEach(e=>this._normalizeMessageTimestamps(e)),n}async sendTextMessage(e,t){return this.sendMessage(e,{message:t})}sendTextMessageOptimistic(e,t){return this.sendMessageOptimistic(e,{message:t})}async uploadFile(e,t,s={}){if(!e)throw new Error("roomId is required");if(!t)throw new Error("file is required");const i=await this.apiClient.upload(`/api/v1/files/${e}/upload`,t,{onProgress:s.onProgress,signal:s.signal});return this._unwrapSuccessResponse(i)}async sendFileMessage(e,t,s={}){const i=this._normalizeFileArray(t),n=await this._uploadFiles(e,i,s),r=this._applyFileMetadata(n,s.metadata);return this.sendMessage(e,{message:s.message,fileInfos:r,separateFiles:s.separateFiles,replyToMessageId:s.replyToMessageId})}sendFileMessageOptimistic(e,t,s={}){const i=this._normalizeFileArray(t),n=this._generateMessageId(),r=this._predictMessageIds(n,this._predictGroupCount(i,!1!==s.separateFiles),this._hasTextMessage(s.message)),a=(async()=>{const t=await this._uploadFiles(e,i,s),a=this._applyFileMetadata(t,s.metadata),o=await this._sendMessageWithId(e,n,{message:s.message,fileInfos:a,separateFiles:s.separateFiles,replyToMessageId:s.replyToMessageId});return this._attachOptimisticMetadata(r,o)})();return{baseMessageId:n,messageIds:r,promise:a}}_normalizeFileArray(e){const t=Array.isArray(e)?e:[e];if(0===t.length)throw new Error("At least one file is required");if(t.length>20)throw new Error("A maximum of 20 files can be sent in one message");return t}async _uploadFiles(e,t,s={}){return Promise.all(t.map((t,i)=>this.uploadFile(e,t,{signal:s.signal,onProgress:s.onUploadProgress?e=>s.onUploadProgress({...e,fileIndex:i}):void 0})))}_applyFileMetadata(e,t){return null==t?e:e.map((e,s)=>{const i=Array.isArray(t)?t[s]:t;return null==i?e:{...e,metadata:i}})}async sendReply(e,t,s){return this.sendMessage(e,{message:t,replyToMessageId:s})}sendReplyOptimistic(e,t,s){return this.sendMessageOptimistic(e,{message:t,replyToMessageId:s})}async deleteMessage(e,t,s="ALL"){return this.apiClient.post(`/api/v1/chat-messages/${e}/messages/${t}/delete`,{deleteType:s})}async editMessage(e,t,s){return this.apiClient.put(`/api/v1/chat-messages/${e}/messages/${t}`,{message:s})}_hasTextMessage(e){return"string"==typeof e&&e.trim().length>0}_classifyFileType(e){const t=(e||"").toLowerCase();return t.startsWith("image/")?"IMAGE":t.startsWith("video/")?"VIDEO":t.startsWith("audio/")?"AUDIO":t.includes("pdf")||t.includes("document")?"DOCUMENT":"FILE"}_predictGroupCount(e,t){if(!e||0===e.length)return 0;if(t)return e.length;const s=new Set;for(const t of e){const e=t&&(t.type||t.fileType)||"";s.add(this._classifyFileType(e))}return s.size}_predictMessageIds(e,t,s){const i=(s?1:0)+t;if(0===i)return[];if(1===i)return[e];const n=[];s&&n.push(e);for(let s=0;s<t;s++)n.push(`${e}#${s}`);return n}_predictMessageIdsFromData(e,t){const s=t&&t.fileInfos||[],i=!t||!1!==t.separateFiles,n=this._predictGroupCount(s,i),r=this._hasTextMessage(t&&t.message);return this._predictMessageIds(e,n,r)}_attachOptimisticMetadata(e,t){const s=t&&Array.isArray(t.messages)?t.messages.map(e=>e&&e.messageId||null):[],i=s.length===e.length&&e.every((e,t)=>e===s[t]);return i||this.logger.warn("Optimistic messageId prediction mismatch — server may have changed grouping rules. Use result.optimistic.actualMessageIds to reconcile.",{predicted:e,actual:s}),{...t,optimistic:{predictedMessageIds:e,actualMessageIds:s,predictionMatched:i}}}_generateMessageId(){return"undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}_unwrapSuccessResponse(e){return e&&"object"==typeof e&&Object.prototype.hasOwnProperty.call(e,"data")?e.data:e}_normalizeMessageTimestamps(e){return e&&"object"==typeof e?(e.sentAt=this._toEpochMillis(e.sentAt),e.editedAt=this._toEpochMillis(e.editedAt),e.replyTo&&"object"==typeof e.replyTo&&(e.replyTo.sentAt=this._toEpochMillis(e.replyTo.sentAt)),e):e}_toEpochMillis(e){if("string"!=typeof e)return e;const t=Date.parse(e);return Number.isNaN(t)?e:t}async subscribeRoom(e){if(this.subscribedRooms.has(e))return void this.logger.warn(`Already subscribed to room: ${e}`);const t=l.getChatDestination(e);await this.connectionManager.subscribe(t,t=>{this._handleChatMessage(e,t)});const s=l.getChatReadDestination(e);await this.connectionManager.subscribe(s,t=>{this._handleReadEvent(e,t)});const i=l.getChatTypingDestination(e);await this.connectionManager.subscribe(i,t=>{this._handleTypingEvent(e,t)});const n=t=>{t.roomId===e&&this.emit("memberJoined",{roomId:e,members:t.members||[],participantCount:t.activeParticipantCount,timestamp:t.timestamp})},r=t=>{t.roomId===e&&this.emit("memberLeft",{roomId:e,members:t.members||[],participantCount:t.activeParticipantCount,timestamp:t.timestamp})};this.on("roomListJoined",n),this.on("roomListLeft",r),this.subscribedRooms.set(e,{chatDestination:t,readDestination:s,typingDestination:i,memberJoinedHandler:n,memberLeftHandler:r,subscribedAt:new Date}),this.logger.info(`Subscribed to room: ${e}`),this.emit("roomSubscribed",{roomId:e})}unsubscribeRoom(e){const t=this.subscribedRooms.get(e);t?(this.connectionManager.unsubscribe(t.chatDestination),this.connectionManager.unsubscribe(t.readDestination),t.typingDestination&&this.connectionManager.unsubscribe(t.typingDestination),t.memberJoinedHandler&&this.off("roomListJoined",t.memberJoinedHandler),t.memberLeftHandler&&this.off("roomListLeft",t.memberLeftHandler),this._clearTypingTimer(e),this._clearAssistantTypingTimer(e),this._activeRoomId===e&&(this._activeRoomId=null),this.subscribedRooms.delete(e),this._seenChatMessageIdsByRoom.delete(e),this._seenRoomListMessageIdsByRoom.delete(e),this.logger.info(`Unsubscribed from room: ${e}`),this.emit("roomUnsubscribed",{roomId:e})):this.logger.warn(`Not subscribed to room: ${e}`)}unsubscribeAllRooms(){this.subscribedRooms.forEach((e,t)=>{this.unsubscribeRoom(t)})}setActiveRoom(e){this._activeRoomId=e,this.logger.debug(`Active room set: ${e}`)}clearActiveRoom(){this.logger.debug(`Active room cleared (was: ${this._activeRoomId})`),this._activeRoomId=null}getActiveRoom(){return this._activeRoomId}async subscribeRoomList(){if(this.roomListSubscribed)return void this.logger.warn("Already subscribed to room list");const e=l.ROOM_LIST_USER_DESTINATION;await this.connectionManager.subscribe(e,e=>{this._handleRoomListEvent(e)}),this.roomListSubscribed=!0,this.logger.info("Subscribed to room list"),this.emit("roomListSubscribed",{})}unsubscribeRoomList(){this.roomListSubscribed?(this.connectionManager.unsubscribe(l.ROOM_LIST_USER_DESTINATION),this.roomListSubscribed=!1,this.logger.info("Unsubscribed from room list"),this.emit("roomListUnsubscribed",{})):this.logger.warn("Not subscribed to room list")}isRoomListSubscribed(){return this.roomListSubscribed}markAsRead(e,t){return this.connectionManager.isConnected()?this.connectionManager.send(l.CHAT_READ,{roomId:e,messageId:t}):(this.logger.warn("Cannot mark as read: not connected"),!1)}_handleChatMessage(e,t){if(this.logger.debug(`Message received in room ${e} (${t.eventType}):`,t),"MESSAGE_CREATED"===t.eventType&&this._shouldDedupMessage(this._seenChatMessageIdsByRoom,e,t.messageId))this.logger.debug(`Duplicate MESSAGE_CREATED suppressed: room=${e}, messageId=${t.messageId}`);else switch("MESSAGE_UPDATED"===t.eventType&&(void 0===t.translations&&(t.translations=null),void 0===t.sourceLang&&(t.sourceLang=null),void 0===t.translationsOf&&(t.translationsOf=null)),this.emit("message",{roomId:e,message:t}),t.eventType){case"MESSAGE_CREATED":"ASSISTANT"===t.senderType&&(this._clearAssistantTypingTimer(e),this.emit("typing",{roomId:e,userId:this._assistantTypingUserId,userName:this._assistantTypingUserName,typing:!1,senderType:"ASSISTANT"})),t.userId!==this.userId&&(this.emit("newMessage",{roomId:e,message:t}),this._activeRoomId===e&&t.messageId&&this.markAsRead(e,t.messageId));break;case"MESSAGE_UPDATED":this.emit("messageUpdated",{roomId:e,message:t});break;case"MESSAGE_DELETED":this.emit("messageDeleted",{roomId:e,message:t});break;case"REACTION_CHANGED":this.emit("reactionChanged",{roomId:e,message:t});break;case"LINK_PREVIEW_ATTACHED":this.emit("linkPreviewAttached",{roomId:e,message:t});break;case"MESSAGE_TRANSLATED":this.emit("messageTranslated",{roomId:e,message:t});break;default:this.logger.warn(`Unknown eventType "${t.eventType}" — falling back to legacy behavior`),t.userId!==this.userId&&(this.emit("newMessage",{roomId:e,message:t}),this._activeRoomId===e&&t.messageId&&this.markAsRead(e,t.messageId))}}_handleRoomListEvent(e){if(this.logger.debug("Room list event:",e),e.eventType===c.MESSAGE_RECEIVED&&e.messageId&&this._shouldDedupMessage(this._seenRoomListMessageIdsByRoom,e.roomId,e.messageId))this.logger.debug(`Duplicate roomList MESSAGE_RECEIVED suppressed: room=${e.roomId}, messageId=${e.messageId}`);else switch(this.emit("roomListUpdate",e),e.eventType){case c.MESSAGE_RECEIVED:this.emit("roomListMessage",e);break;case c.MESSAGE_DELETED:this.emit("roomListMessageDeleted",e);break;case c.MESSAGE_UPDATED:this.emit("roomListMessageUpdated",e);break;case c.ROOM_CREATED:this.emit("roomListCreated",e);break;case c.ROOM_JOINED:this.emit("roomListJoined",e);break;case c.ROOM_LEFT:this.emit("roomListLeft",e),e.actorId===this.userId&&this.emit("roomListSelfLeft",e);break;case c.ROOM_KICKED:this.emit("roomListKicked",e),this._isSelfInMembers(e)&&this.emit("roomListSelfKicked",e);break;case c.ROOM_BANNED:this.emit("roomListBanned",e),this._isSelfInMembers(e)&&this.emit("roomListSelfBanned",e);break;case c.ROOM_UPDATED:this.emit("roomListRoomUpdated",e);break;case c.MESSAGE_RETENTION_CLEANUP:this.emit("retentionCleanup",{roomId:e.roomId,cutoffTime:e.cutoffTime});break;default:this.logger.warn("Unknown room list event type:",e.eventType)}}_handleReadEvent(e,t){this.logger.debug(`Read event in room ${e}:`,t);const s=t.roomId||e;t.events&&Array.isArray(t.events)?t.events.forEach(e=>{this.emit("messageRead",{roomId:s,messageId:e.messageId,userId:t.userId,remainingUnreadCount:e.remainingUnreadCount})}):this.emit("messageRead",{roomId:s,messageId:t.messageId,userId:t.userId,remainingUnreadCount:t.remainingUnreadCount})}startTyping(e){if(!this.connectionManager.isConnected())return;const t=this._typingTimers.get(e);t?clearTimeout(t):this.connectionManager.send(l.CHAT_TYPING,{roomId:e,typing:!0});const s=setTimeout(()=>{this.stopTyping(e)},3e3);this._typingTimers.set(e,s)}stopTyping(e){this._clearTypingTimer(e),this.connectionManager.isConnected()&&this.connectionManager.send(l.CHAT_TYPING,{roomId:e,typing:!1})}_isSelfInMembers(e){return Array.isArray(e.members)&&e.members.some(e=>e&&e.userId===this.userId)}_handleTypingEvent(e,t){"ASSISTANT"!==t.senderType&&t.userId===this.userId||("ASSISTANT"===t.senderType&&!0===t.typing&&this._startAssistantTypingTimer(e),this.emit("typing",{roomId:e,userId:t.userId,userName:t.userName,typing:t.typing,senderType:t.senderType||"USER"}))}_startAssistantTypingTimer(e){this._clearAssistantTypingTimer(e);const t=setTimeout(()=>{this._assistantTypingTimers.delete(e),this.emit("typing",{roomId:e,userId:this._assistantTypingUserId,userName:this._assistantTypingUserName,typing:!1,senderType:"ASSISTANT"}),this.logger.debug(`Assistant typing auto-cleared (timeout): room=${e}`)},this._assistantTypingTimeoutMs);this._assistantTypingTimers.set(e,t)}_clearAssistantTypingTimer(e){const t=this._assistantTypingTimers.get(e);t&&(clearTimeout(t),this._assistantTypingTimers.delete(e))}_clearTypingTimer(e){const t=this._typingTimers.get(e);t&&(clearTimeout(t),this._typingTimers.delete(e))}getSubscribedRooms(){return Array.from(this.subscribedRooms.keys())}isSubscribed(e){return this.subscribedRooms.has(e)}setLogLevel(e){this.logger.setLevel(e)}destroy(){this.unsubscribeAllRooms(),this.roomListSubscribed&&this.unsubscribeRoomList(),this._typingTimers.forEach(e=>clearTimeout(e)),this._typingTimers.clear(),this._assistantTypingTimers.forEach(e=>clearTimeout(e)),this._assistantTypingTimers.clear(),this._seenChatMessageIdsByRoom.clear(),this._seenRoomListMessageIdsByRoom.clear(),this.removeAllListeners(),this.logger.info("ChatClient destroyed")}}class y extends i{constructor(e={}){super(),this.localStream=null,this.screenStream=null,this.videoEnabled=!0,this.audioEnabled=!0,this.logger=new m(e.logLevel||u.WARN,"MediaStreamManager"),this._deviceChangeHandler=null}async getUserMedia(e={video:!0,audio:!0}){try{return this.localStream=await navigator.mediaDevices.getUserMedia(e),this.videoEnabled=!1!==e.video,this.audioEnabled=!1!==e.audio,this.emit("streamStarted",{stream:this.localStream}),this.logger.info("User media stream started"),this.localStream}catch(e){throw this.logger.error("Failed to get user media:",e),this.emit("error",{type:r.MEDIA_ACCESS_DENIED,message:this._getMediaErrorMessage(e),error:e}),e}}_getMediaErrorMessage(e){switch(e.name){case"NotAllowedError":return"Camera/Microphone permission denied";case"NotFoundError":return"Camera/Microphone not found";case"NotReadableError":return"Camera/Microphone is already in use";case"OverconstrainedError":return"Camera/Microphone constraints cannot be satisfied";case"AbortError":return"Media access aborted";default:return e.message||"Unknown media error"}}async getDisplayMedia(e={video:!0,audio:!1}){try{return this.screenStream=await navigator.mediaDevices.getDisplayMedia(e),this.screenStream.getVideoTracks()[0].onended=()=>{this.emit("screenShareEnded",{}),this.screenStream=null},this.emit("screenShareStarted",{stream:this.screenStream}),this.logger.info("Screen share stream started"),this.screenStream}catch(e){throw this.logger.error("Failed to get display media:",e),this.emit("error",{type:r.SCREEN_SHARE_DENIED,message:"NotAllowedError"===e.name?"Screen share permission denied":e.message,error:e}),e}}toggleVideo(){if(!this.localStream)return this.logger.warn("No local stream available"),this.videoEnabled;const e=this.localStream.getVideoTracks();return 0===e.length?(this.logger.warn("No video track available"),this.videoEnabled):(this.videoEnabled=!this.videoEnabled,e.forEach(e=>{e.enabled=this.videoEnabled}),this.emit("videoToggled",{enabled:this.videoEnabled}),this.logger.debug(`Video toggled: ${this.videoEnabled}`),this.videoEnabled)}toggleAudio(){if(!this.localStream)return this.logger.warn("No local stream available"),this.audioEnabled;const e=this.localStream.getAudioTracks();return 0===e.length?(this.logger.warn("No audio track available"),this.audioEnabled):(this.audioEnabled=!this.audioEnabled,e.forEach(e=>{e.enabled=this.audioEnabled}),this.emit("audioToggled",{enabled:this.audioEnabled}),this.logger.debug(`Audio toggled: ${this.audioEnabled}`),this.audioEnabled)}setVideoEnabled(e){if(!this.localStream)return void this.logger.warn("No local stream available");this.localStream.getVideoTracks().forEach(t=>{t.enabled=e}),this.videoEnabled=e,this.emit("videoToggled",{enabled:e})}setAudioEnabled(e){if(!this.localStream)return void this.logger.warn("No local stream available");this.localStream.getAudioTracks().forEach(t=>{t.enabled=e}),this.audioEnabled=e,this.emit("audioToggled",{enabled:e})}getLocalStream(){return this.localStream}getScreenStream(){return this.screenStream}isVideoEnabled(){return this.videoEnabled}isAudioEnabled(){return this.audioEnabled}stopAll(){this.localStream&&(this.localStream.getTracks().forEach(e=>e.stop()),this.localStream=null,this.emit("streamStopped",{}),this.logger.info("All media tracks stopped")),this.screenStream&&(this.screenStream.getTracks().forEach(e=>e.stop()),this.screenStream=null)}replaceTrack(e,t){this.localStream?(this.localStream.removeTrack(e),this.localStream.addTrack(t),e.stop(),this.emit("trackReplaced",{oldTrack:e,newTrack:t}),this.logger.debug(`Track replaced: ${e.kind}`)):this.logger.warn("No local stream available")}getTrack(e){if(!this.localStream)return null;const t="video"===e?this.localStream.getVideoTracks():this.localStream.getAudioTracks();return t.length>0?t[0]:null}async getDevices(){try{const e=await navigator.mediaDevices.enumerateDevices();return{videoInputs:e.filter(e=>"videoinput"===e.kind),audioInputs:e.filter(e=>"audioinput"===e.kind),audioOutputs:e.filter(e=>"audiooutput"===e.kind)}}catch(e){throw this.logger.error("Failed to enumerate devices:",e),this.emit("error",{type:r.ENUMERATE_DEVICES_FAILED,message:e.message,error:e}),e}}async switchDevice(e,t){if(!this.localStream)throw new Error("No local stream available");try{const s="video"===t?{video:{deviceId:{exact:e}},audio:!1}:{video:!1,audio:{deviceId:{exact:e}}},i=(await navigator.mediaDevices.getUserMedia(s)).getTracks()[0],n="video"===t?this.localStream.getVideoTracks()[0]:this.localStream.getAudioTracks()[0];return n&&(this.replaceTrack(n,i),this.emit("deviceSwitched",{kind:t,deviceId:e,newTrack:i}),this.logger.info(`${t} device switched to ${e}`)),i}catch(e){throw this.logger.error(`Failed to switch ${t} device:`,e),this.emit("error",{type:r.DEVICE_SWITCH_FAILED,message:e.message,error:e}),e}}startDeviceChangeDetection(){this._deviceChangeHandler||(this._deviceChangeHandler=async()=>{try{const e=await this.getDevices();this.emit("deviceChange",e),this.logger.debug("Device change detected")}catch(e){this.logger.warn("Failed to get devices on change:",e)}},navigator.mediaDevices.addEventListener("devicechange",this._deviceChangeHandler),this.logger.debug("Device change detection started"))}stopDeviceChangeDetection(){this._deviceChangeHandler&&(navigator.mediaDevices.removeEventListener("devicechange",this._deviceChangeHandler),this._deviceChangeHandler=null,this.logger.debug("Device change detection stopped"))}getStreamInfo(){return this.localStream?{hasStream:!0,videoEnabled:this.videoEnabled,audioEnabled:this.audioEnabled,tracks:this.localStream.getTracks().map(e=>({kind:e.kind,id:e.id,label:e.label,enabled:e.enabled,readyState:e.readyState,muted:e.muted}))}:{hasStream:!1,videoEnabled:this.videoEnabled,audioEnabled:this.audioEnabled,tracks:[]}}async applyVideoConstraints(e){if(!this.localStream)throw new Error("No local stream available");const t=this.localStream.getVideoTracks()[0];if(!t)throw new Error("No video track available");try{await t.applyConstraints(e),this.emit("videoConstraintsApplied",{constraints:e}),this.logger.info("Video constraints applied:",e)}catch(e){throw this.logger.error("Failed to apply video constraints:",e),e}}getVideoSettings(){if(!this.localStream)return null;const e=this.localStream.getVideoTracks()[0];return e?e.getSettings():null}getAudioSettings(){if(!this.localStream)return null;const e=this.localStream.getAudioTracks()[0];return e?e.getSettings():null}setLogLevel(e){this.logger.setLevel(e)}destroy(){this.stopAll(),this.stopDeviceChangeDetection(),this.removeAllListeners(),this.logger.info("MediaStreamManager destroyed")}}class w extends i{constructor(e={}){super(),this.iceServers=e.iceServers||g.iceServers,this.logger=new m(e.logLevel||u.WARN,"PeerConnectionManager"),this.peers=new Map,this.localStream=null}setLocalStream(e){this.localStream=e,this.peers.forEach(e=>{this._addTracksToConnection(e.connection)})}setIceServers(e){this.iceServers=e}createPeerConnection(e,t=!1,s={}){if(this.peers.has(e))return this.logger.warn(`Peer connection already exists: ${e}`),this.peers.get(e).connection;const i={iceServers:this.iceServers,iceCandidatePoolSize:10},n=new RTCPeerConnection(i),r={connection:n,polite:t,makingOffer:!1,ignoreOffer:!1,isSettingRemoteAnswerPending:!1,skipAutoNegotiation:s.skipAutoNegotiation||!1};return this.peers.set(e,r),this._setupConnectionHandlers(e,n,r),this.localStream&&this._addTracksToConnection(n),this.logger.info(`Peer connection created: ${e} (polite: ${t}, skipAutoNegotiation: ${r.skipAutoNegotiation})`),n}_setupConnectionHandlers(e,t,s){t.onicecandidate=t=>{t.candidate&&this.emit("iceCandidate",{peerId:e,candidate:t.candidate})},t.oniceconnectionstatechange=()=>{this.logger.debug(`ICE connection state (${e}): ${t.iceConnectionState}`),this.emit("iceConnectionStateChange",{peerId:e,state:t.iceConnectionState}),"failed"===t.iceConnectionState&&this.emit("error",{type:r.ICE_CONNECTION_FAILED,peerId:e,message:"ICE connection failed"})},t.onconnectionstatechange=()=>{this.logger.debug(`Connection state (${e}): ${t.connectionState}`),this.emit("connectionStateChange",{peerId:e,state:t.connectionState}),"connected"===t.connectionState?this.emit("peerConnected",{peerId:e}):"disconnected"!==t.connectionState&&"failed"!==t.connectionState||this.emit("peerDisconnected",{peerId:e})},t.onnegotiationneeded=async()=>{if(s.skipAutoNegotiation)this.logger.debug(`Skipping auto negotiation for ${e} (manual offer will be sent)`);else try{s.makingOffer=!0,await t.setLocalDescription(),this.emit("negotiationNeeded",{peerId:e,description:t.localDescription})}catch(t){this.logger.error(`Negotiation error (${e}):`,t)}finally{s.makingOffer=!1}},t.ontrack=t=>{this.logger.info(`Remote track received from ${e}:`,t.track.kind),this.emit("remoteTrack",{peerId:e,track:t.track,streams:t.streams})},t.ondatachannel=t=>{this.logger.info(`Data channel received from ${e}`),this.emit("dataChannel",{peerId:e,channel:t.channel})}}_addTracksToConnection(e){if(!this.localStream)return;const t=e.getSenders();this.localStream.getTracks().forEach(s=>{t.find(e=>e.track&&e.track.kind===s.kind)||e.addTrack(s,this.localStream)})}async createOffer(e){const t=this.peers.get(e);if(!t)throw new Error(`Peer not found: ${e}`);try{const s=await t.connection.createOffer();return await t.connection.setLocalDescription(s),this.logger.debug(`Offer created for ${e}`),t.connection.localDescription}catch(t){throw this.logger.error(`Failed to create offer for ${e}:`,t),t}}async createAnswer(e){const t=this.peers.get(e);if(!t)throw new Error(`Peer not found: ${e}`);try{const s=await t.connection.createAnswer();return await t.connection.setLocalDescription(s),this.logger.debug(`Answer created for ${e}`),t.connection.localDescription}catch(t){throw this.logger.error(`Failed to create answer for ${e}:`,t),t}}async handleRemoteDescription(e,t){const s=this.peers.get(e);if(!s)throw new Error(`Peer not found: ${e}`);const{connection:i,polite:n,makingOffer:r}=s,a="offer"===t.type&&(r||"stable"!==i.signalingState);if(s.ignoreOffer=!n&&a,s.ignoreOffer)this.logger.debug(`Ignoring offer collision from ${e} (I am impolite)`);else try{if(a&&n&&(this.logger.debug(`Offer collision detected, rolling back local offer for ${e} (I am polite)`),await i.setLocalDescription({type:"rollback"})),s.isSettingRemoteAnswerPending="answer"===t.type,await i.setRemoteDescription(t),s.isSettingRemoteAnswerPending=!1,this.logger.debug(`Remote ${t.type} set for ${e}`),"offer"===t.type){const t=await this.createAnswer(e);this.emit("answerCreated",{peerId:e,answer:t})}}catch(t){throw this.logger.error(`Failed to set remote description for ${e}:`,t),t}}async addIceCandidate(e,t){const s=this.peers.get(e);if(!s)return this.logger.warn(`Peer not found for ICE candidate: ${e}`),!1;if(!s.connection.remoteDescription)return this.logger.debug(`Remote description not set yet for ${e}, ICE candidate queued`),!1;try{return await s.connection.addIceCandidate(t),this.logger.debug(`ICE candidate added for ${e}`),!0}catch(t){return s.ignoreOffer||this.logger.error(`Failed to add ICE candidate for ${e}:`,t),!1}}getPeerConnection(e){const t=this.peers.get(e);return t?t.connection:null}getPeerIds(){return Array.from(this.peers.keys())}closePeerConnection(e){const t=this.peers.get(e);t&&(t.connection.close(),this.peers.delete(e),this.emit("peerClosed",{peerId:e}),this.logger.info(`Peer connection closed: ${e}`))}closeAllPeerConnections(){this.peers.forEach((e,t)=>{e.connection.close(),this.logger.debug(`Peer connection closed: ${t}`)}),this.peers.clear(),this.emit("allPeersClosed",{})}async replaceTrack(e,t){const s=[];this.peers.forEach((i,n)=>{const r=i.connection.getSenders().find(t=>t.track&&t.track.kind===e.kind);r&&s.push(r.replaceTrack(t).then(()=>this.logger.debug(`Track replaced for ${n}`)).catch(e=>this.logger.error(`Failed to replace track for ${n}:`,e)))}),await Promise.all(s)}async getStats(e){const t=this.peers.get(e);return t?t.connection.getStats():null}getConnectionSummary(){const e={};return this.peers.forEach((t,s)=>{e[s]={connectionState:t.connection.connectionState,iceConnectionState:t.connection.iceConnectionState,signalingState:t.connection.signalingState,polite:t.polite}}),e}setLogLevel(e){this.logger.setLevel(e)}destroy(){this.closeAllPeerConnections(),this.localStream=null,this.removeAllListeners(),this.logger.info("PeerConnectionManager destroyed")}}class R extends i{constructor(e){super(),this.connectionManager=e.connectionManager,this.apiClient=e.apiClient,this.userId=e.userId,this.logLevel=e.logLevel||u.WARN,this.logger=new m(this.logLevel,"WebRTCClient"),this.mediaManager=new y({logLevel:this.logLevel}),this.peerManager=new w({iceServers:e.iceServers||g.iceServers,logLevel:this.logLevel}),this.currentRoom=null,this.isGroupCall=!1,this.participants=new Map,this._iceServersFetched=!1,this._waitingForCallAccept=!1,this._pendingCallTarget=null,this._pendingIceCandidates=new Map,this._setupEventHandlers()}async initializeIceServers(){if(!this._iceServersFetched)try{const e=await this.getTurnCredentials();e&&e.iceServers&&(this.peerManager.setIceServers(e.iceServers),this._iceServersFetched=!0,this.logger.info("ICE servers fetched successfully"))}catch(e){this.logger.warn("Failed to fetch TURN credentials, using fallback ICE servers:",e.message),this._setFallbackIceServers()}}_setFallbackIceServers(){this.peerManager.setIceServers([{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}])}_setupEventHandlers(){this.mediaManager.on("streamStarted",e=>this.emit("localStreamStarted",e)),this.mediaManager.on("streamStopped",e=>this.emit("localStreamStopped",e)),this.mediaManager.on("videoToggled",()=>this._sendMediaState()),this.mediaManager.on("audioToggled",()=>this._sendMediaState()),this.mediaManager.on("screenShareStarted",e=>this.emit("screenShareStarted",e)),this.mediaManager.on("screenShareEnded",e=>this.emit("screenShareEnded",e)),this.mediaManager.on("error",e=>this.emit("error",e)),this.peerManager.on("remoteTrack",e=>this.emit("remoteTrack",e)),this.peerManager.on("peerConnected",e=>this.emit("peerConnected",e)),this.peerManager.on("peerDisconnected",e=>this.emit("peerDisconnected",e)),this.peerManager.on("peerClosed",e=>this.emit("peerClosed",e)),this.peerManager.on("error",e=>this.emit("error",e)),this.peerManager.on("iceCandidate",({peerId:e,candidate:t})=>{this._sendSignal({type:a.ICE_CANDIDATE,receiverId:e,data:{candidate:t}})}),this.peerManager.on("negotiationNeeded",({peerId:e,description:t})=>{this._sendSignal({type:a.CALL_OFFER,receiverId:e,data:{sdp:t}})}),this.peerManager.on("answerCreated",({peerId:e,answer:t})=>{this._sendSignal({type:a.CALL_ANSWER,receiverId:e,data:{sdp:t}})})}async getTurnCredentials(){return(await this.apiClient.get("/api/v1/webrtc/turn-credentials")).data}async createCallRoom(e){return this.apiClient.post("/api/v1/webrtc/rooms",e)}async getCallRoom(e){return this.apiClient.get(`/api/v1/webrtc/rooms/${e}`)}async joinCallRoomApi(e){return this.apiClient.post(`/api/v1/webrtc/rooms/${e}/join`)}async leaveCallRoomApi(e){return this.apiClient.delete(`/api/v1/webrtc/rooms/${e}/leave`)}async enableIncomingCalls(){this._incomingCallsEnabled?this.logger.debug("Incoming calls already enabled"):(await this._subscribeToDirectSignaling(),this._incomingCallsEnabled=!0,this.logger.info("Incoming calls enabled - now listening for call invitations"))}disableIncomingCalls(){if(!this._incomingCallsEnabled)return;const e=l.getWebRTCUserDestination();this.connectionManager.unsubscribe(e),this._incomingCallsEnabled=!1,this.logger.info("Incoming calls disabled")}isIncomingCallsEnabled(){return this._incomingCallsEnabled||!1}async startCall(e){const{roomId:t,isGroup:s=!1,mediaConstraints:i={video:!0,audio:!0}}=e;try{await this.initializeIceServers();const e=await this.mediaManager.getUserMedia(i);return this.peerManager.setLocalStream(e),this.currentRoom=t,this.isGroupCall=s,await this._subscribeToSignaling(t,s),this._sendSignal({type:a.JOIN_ROOM,roomId:t}),this.logger.info(`Call started in room: ${t}`),this.emit("callStarted",{roomId:t,isGroup:s,localStream:e}),e}catch(e){throw this.logger.error("Failed to start call:",e),this.emit("error",{type:r.PEER_CONNECTION_FAILED,message:e.message,error:e}),e}}async callUser(e,t={video:!0,audio:!0}){try{await this.initializeIceServers();const s=await this.mediaManager.getUserMedia(t);this.peerManager.setLocalStream(s),await this._subscribeToDirectSignaling(),this._waitingForCallAccept=!0,this._pendingCallTarget=e,this._sendSignal({type:a.CALL_REQUEST,receiverId:e}),this.currentRoom=null,this.isGroupCall=!1,this.emit("callRequested",{targetUserId:e,localStream:s})}catch(e){throw this.logger.error("Failed to call user:",e),this._waitingForCallAccept=!1,this._pendingCallTarget=null,e}}async acceptCall(e,t={video:!0,audio:!0}){try{await this.initializeIceServers();const s=await this.mediaManager.getUserMedia(t);this.peerManager.setLocalStream(s),this.peerManager.createPeerConnection(e,!0,{skipAutoNegotiation:!0}),this._sendSignal({type:a.CALL_ACCEPT,receiverId:e}),this.emit("callAccepted",{callerId:e})}catch(e){throw this.logger.error("Failed to accept call:",e),e}}rejectCall(e){this._sendSignal({type:a.CALL_REJECT,receiverId:e}),this.emit("callRejected",{callerId:e})}cancelCall(){this._waitingForCallAccept&&this._pendingCallTarget&&(this._sendSignal({type:a.CALL_CANCEL,receiverId:this._pendingCallTarget}),this.emit("callCancelled",{targetUserId:this._pendingCallTarget}),this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.mediaManager.stopAll())}endCall(){this._waitingForCallAccept&&this._pendingCallTarget&&this._sendSignal({type:a.CALL_CANCEL,receiverId:this._pendingCallTarget}),this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.currentRoom&&(this._sendSignal({type:a.LEAVE_ROOM,roomId:this.currentRoom}),this._unsubscribeFromSignaling()),this.peerManager.getPeerIds().forEach(e=>{this._sendSignal({type:a.CALL_END,receiverId:e})}),this.peerManager.closeAllPeerConnections(),this.mediaManager.stopAll(),this.participants.clear(),this._pendingIceCandidates.clear();const e=this.currentRoom;this.currentRoom=null,this.isGroupCall=!1,this.emit("callEnded",{roomId:e}),this.logger.info("Call ended")}toggleVideo(){return this.mediaManager.toggleVideo()}toggleAudio(){return this.mediaManager.toggleAudio()}async startScreenShare(){const e=await this.mediaManager.getDisplayMedia(),t=e.getVideoTracks()[0],s=this.mediaManager.getTrack("video");return s&&t&&await this.peerManager.replaceTrack(s,t),t.onended=async()=>{const e=this.mediaManager.getTrack("video");e&&await this.peerManager.replaceTrack(t,e),this.emit("screenShareEnded",{})},e}stopScreenShare(){const e=this.mediaManager.getScreenStream();e&&e.getTracks().forEach(e=>e.stop())}getLocalStream(){return this.mediaManager.getLocalStream()}async getDevices(){return this.mediaManager.getDevices()}async switchDevice(e,t){const s=this.mediaManager.getTrack(t),i=await this.mediaManager.switchDevice(e,t);return s&&await this.peerManager.replaceTrack(s,i),i}setVideoEnabled(e){this.mediaManager.setVideoEnabled(e),this._sendMediaState()}setAudioEnabled(e){this.mediaManager.setAudioEnabled(e),this._sendMediaState()}startDeviceChangeDetection(){this.mediaManager.startDeviceChangeDetection(),this.mediaManager.on("deviceChange",e=>{this.emit("deviceChange",e)})}stopDeviceChangeDetection(){this.mediaManager.stopDeviceChangeDetection()}async applyVideoConstraints(e){return this.mediaManager.applyVideoConstraints(e)}getVideoSettings(){return this.mediaManager.getVideoSettings()}getAudioSettings(){return this.mediaManager.getAudioSettings()}async _subscribeToSignaling(e,t){if(t){const t=l.getWebRTCDestination(e);await this.connectionManager.subscribe(t,e=>{this._handleSignal(e)})}await this._subscribeToDirectSignaling()}async _subscribeToDirectSignaling(){const e=l.getWebRTCUserDestination();await this.connectionManager.subscribe(e,e=>{this._handleSignal(e)})}_unsubscribeFromSignaling(){if(this.currentRoom&&this.isGroupCall){const e=l.getWebRTCDestination(this.currentRoom);this.connectionManager.unsubscribe(e)}if(!this._incomingCallsEnabled){const e=l.getWebRTCUserDestination();this.connectionManager.unsubscribe(e)}}_sendSignal(e){const t={type:e.type,roomId:e.roomId||this.currentRoom,receiverId:e.receiverId,data:e.data};this.connectionManager.send(l.WEBRTC_SIGNAL,t),this.logger.debug("Signal sent:",t)}_sendMediaState(){if(!this.currentRoom&&0===this.peerManager.getPeerIds().length)return;const e={videoEnabled:this.mediaManager.isVideoEnabled(),audioEnabled:this.mediaManager.isAudioEnabled()};this._sendSignal({type:a.VIDEO_STATE_CHANGED,data:e}),this.emit("mediaStateChanged",e)}async _handleSignal(e){const{type:t,senderId:s,data:i}=e;if(s!==this.userId)switch(this.logger.debug(`Signal received: ${t} from ${s}`),t){case a.JOIN_ROOM:case a.PEER_JOINED:await this._handleUserJoined(s);break;case a.LEAVE_ROOM:case a.PEER_LEFT:this._handleUserLeft(s);break;case a.CALL_OFFER:await this._handleOffer(s,i.sdp);break;case a.CALL_ANSWER:await this._handleAnswer(s,i.sdp);break;case a.ICE_CANDIDATE:await this._handleIceCandidate(s,i.candidate);break;case a.VIDEO_STATE_CHANGED:case a.AUDIO_STATE_CHANGED:this._handleMediaState(s,i);break;case a.CALL_REQUEST:this.isInCall()||this._waitingForCallAccept?(this._sendSignal({type:a.CALL_BUSY,receiverId:s}),this.emit("incomingCallWhileBusy",{callerId:s})):this.emit("incomingCall",{callerId:s});break;case a.CALL_BUSY:this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.mediaManager.stopAll(),this.emit("callBusy",{userId:s});break;case a.CALL_INVITATION:this.emit("callInvitation",{callRoomId:i?.callRoomId||e.roomId,title:i?.title,hostUserId:i?.hostUserId||s,maxParticipants:i?.maxParticipants,createdAt:i?.createdAt});break;case a.CALL_ACCEPT:await this._handleCallAccepted(s);break;case a.CALL_REJECT:this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.mediaManager.stopAll(),this.emit("callRejected",{userId:s});break;case a.CALL_CANCEL:this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.emit("callCancelled",{userId:s});break;case a.CALL_END:this._handleCallEnded(s);break;default:this.logger.warn(`Unknown signal type: ${t}`)}}async _handleUserJoined(e){this.participants.set(e,{joinedAt:new Date});const t=this.userId<e;this.peerManager.createPeerConnection(e,t),this.emit("userJoined",{userId:e}),this.logger.info(`User joined: ${e} (I am ${t?"polite":"impolite"})`)}_handleUserLeft(e){this.participants.delete(e),this.peerManager.closePeerConnection(e),this.emit("userLeft",{userId:e}),this.logger.info(`User left: ${e}`)}async _handleOffer(e,t){if(!this.peerManager.getPeerConnection(e)){const t=this.userId<e;this.peerManager.createPeerConnection(e,t,{skipAutoNegotiation:!0})}await this.peerManager.handleRemoteDescription(e,t),await this._processPendingIceCandidates(e)}async _handleAnswer(e,t){await this.peerManager.handleRemoteDescription(e,t),await this._processPendingIceCandidates(e)}async _handleIceCandidate(e,t){if(!this.peerManager.getPeerConnection(e))return void this._queueIceCandidate(e,t);await this.peerManager.addIceCandidate(e,new RTCIceCandidate(t))||this._queueIceCandidate(e,t)}_queueIceCandidate(e,t){this._pendingIceCandidates.has(e)||this._pendingIceCandidates.set(e,[]),this._pendingIceCandidates.get(e).push(t),this.logger.debug(`ICE candidate queued for ${e}`)}async _processPendingIceCandidates(e){const t=this._pendingIceCandidates.get(e);if(t&&0!==t.length){this.logger.debug(`Processing ${t.length} pending ICE candidates for ${e}`);for(const s of t)await this.peerManager.addIceCandidate(e,new RTCIceCandidate(s));this._pendingIceCandidates.delete(e)}}_handleMediaState(e,t){this.emit("participantMediaState",{userId:e,videoEnabled:t.videoEnabled,audioEnabled:t.audioEnabled})}async _handleCallAccepted(e){this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.peerManager.createPeerConnection(e,!1,{skipAutoNegotiation:!0});const t=await this.peerManager.createOffer(e);this._sendSignal({type:a.CALL_OFFER,receiverId:e,data:{sdp:t}}),this.emit("callAccepted",{userId:e})}_handleCallEnded(e){this.peerManager.closePeerConnection(e),this.participants.delete(e),this.emit("participantLeft",{userId:e}),0===this.peerManager.getPeerIds().length&&this.endCall()}isInCall(){return null!==this.currentRoom||this.peerManager.getPeerIds().length>0}getCurrentRoom(){return this.currentRoom}getParticipants(){return Array.from(this.participants.keys())}getConnectionSummary(){return this.peerManager.getConnectionSummary()}async getStats(e){return this.peerManager.getStats(e)}getMediaState(){return{videoEnabled:this.mediaManager.isVideoEnabled(),audioEnabled:this.mediaManager.isAudioEnabled(),streamInfo:this.mediaManager.getStreamInfo()}}setLogLevel(e){this.logger.setLevel(e),this.mediaManager.setLogLevel(e),this.peerManager.setLogLevel(e)}destroy(){this.endCall(),this.mediaManager.destroy(),this.peerManager.destroy(),this.removeAllListeners(),this.logger.info("WebRTCClient destroyed")}}const A=Object.freeze({UNSUPPORTED_BROWSER:"UNSUPPORTED_BROWSER",FIREBASE_NOT_INSTALLED:"FIREBASE_NOT_INSTALLED",SW_REGISTER_FAILED:"SW_REGISTER_FAILED",PERMISSION_DENIED:"PERMISSION_DENIED",TOKEN_FAILED:"TOKEN_FAILED",SERVER_REGISTER_FAILED:"SERVER_REGISTER_FAILED"});class M extends Error{constructor(e,t,s){super(t),this.name="PushError",this.code=e,void 0!==s&&(this.cause=s)}}const L={apiKey:"AIzaSyB8VhRg6rSvUI7K3Ua7h6sBpLvaQGmIkRc",authDomain:"chatting-c3e5d.firebaseapp.com",projectId:"chatting-c3e5d",storageBucket:"chatting-c3e5d.firebasestorage.app",messagingSenderId:"1020496565673",appId:"1:1020496565673:web:5039167257fd83f5ce20b8"},N="pendingNavigation",D="pending-room",O="deviceInfo",P="device-id",k="talkflow_device_id";function U(){return"undefined"==typeof indexedDB?Promise.resolve(null):new Promise((e,t)=>{const s=indexedDB.open("talkflowPush",2);s.onupgradeneeded=()=>{const e=s.result;e.objectStoreNames.contains(N)||e.createObjectStore(N,{keyPath:"id"}),e.objectStoreNames.contains(O)||e.createObjectStore(O,{keyPath:"id"})},s.onsuccess=()=>e(s.result),s.onerror=()=>t(s.error||new Error("IndexedDB open failed"))})}function $(){return U().then(e=>e?new Promise((t,s)=>{const i=e.transaction(O,"readonly"),n=i.objectStore(O).get(P);n.onsuccess=()=>{const e=n.result;t(e&&e.value?e.value:null)},n.onerror=()=>s(n.error||new Error("IndexedDB read failed")),i.oncomplete=()=>e.close(),i.onerror=()=>e.close(),i.onabort=()=>e.close()}):null)}function F(e){return U().then(t=>!!t&&new Promise(s=>{try{const i=t.transaction(O,"readwrite");i.objectStore(O).put({id:P,value:e}),i.oncomplete=()=>{t.close(),s(!0)},i.onerror=()=>{t.close(),s(!1)},i.onabort=()=>{t.close(),s(!1)}}catch(e){try{t.close()}catch(e){}s(!1)}})).catch(()=>!1)}function W(){return"web-"+("undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():Date.now().toString(36)+Math.random().toString(36).substring(2))}async function j(e){const t=await U();if(!t)return null;const s=function(e){return e&&"string"==typeof e&&""!==e.trim()?"pending-room:"+e:D}(e),i=!!e&&s!==D;return new Promise((n,r)=>{const a=t.transaction(N,"readwrite"),o=a.objectStore(N);let c=null;const l=o.get(s);l.onsuccess=()=>{const t=l.result;if(t&&t.roomId){if((t.projectId||null)===(e||null))return c=t.roomId,void o.delete(s)}if(!i)return;const n=o.get(D);n.onsuccess=()=>{const e=n.result;e&&e.roomId&&!e.projectId&&(c=e.roomId,o.delete(D))}},l.onerror=()=>{r(l.error||new Error("IndexedDB read failed"))},a.oncomplete=()=>{t.close(),n(c)},a.onerror=()=>{t.close(),r(a.error||new Error("IndexedDB transaction failed"))},a.onabort=()=>{t.close(),r(a.error||new Error("IndexedDB transaction aborted"))}})}class x{constructor(e){this.apiClient=e.apiClient,this.projectId=e.projectId||null,this.firebaseConfig=e.firebaseConfig||L,this.vapidKey=e.vapidKey||"BGP8qaSm1ntjWd4n9pc0lX_rw4BmMxm9u4pvRIANCitbmYaV0iy-gn05suTKBo88kUBejxdxM8sb6x2nt3avu8c",this.serviceWorkerPath=e.serviceWorkerPath||"/firebase-messaging-sw.js",this.logger=new m(e.logLevel||u.WARN,"PushManager"),this._messaging=null,this._currentToken=null,this._enabled=!1,this._enablePromise=null,this._foregroundMessageUnsubscribe=null,this._swRegistration=null,this._projectRegisteredToSW=!1,this._visibilityHandler=null,this._deviceIdCache=null}static async consumePendingRoom(e){return j(e)}static getPermissionState(){return"undefined"==typeof window||"undefined"==typeof Notification?"unsupported":"undefined"!=typeof navigator&&"serviceWorker"in navigator?Notification.permission:"unsupported"}async enable(){if(this._enabled)this.logger.debug("푸시 알림이 이미 활성화되어 있습니다.");else{if(this._enablePromise)return this.logger.debug("푸시 알림 활성화가 이미 진행 중입니다."),this._enablePromise;this._enablePromise=this._enableInternal();try{return await this._enablePromise}finally{this._enablePromise=null}}}async _enableInternal(){if("undefined"==typeof window||!("Notification"in window))throw new M(A.UNSUPPORTED_BROWSER,"푸시 알림은 브라우저 환경에서만 사용 가능합니다");if(!("serviceWorker"in navigator))throw new M(A.UNSUPPORTED_BROWSER,"이 브라우저는 서비스 워커를 지원하지 않습니다");if("denied"===Notification.permission)throw new M(A.PERMISSION_DENIED,"알림 권한이 이미 거부되어 있습니다. 브라우저 사이트 설정에서 허용해주세요.");let e,t,s;try{e=await import("firebase/app"),t=await import("firebase/messaging")}catch(e){throw new M(A.FIREBASE_NOT_INSTALLED,"firebase 패키지가 설치되지 않았습니다. npm install firebase 를 실행하세요.",e)}try{s=e.getApp("talkflow")}catch{s=e.initializeApp(this.firebaseConfig,"talkflow")}const i=await this._registerServiceWorker();if("granted"!==await Notification.requestPermission())throw new M(A.PERMISSION_DENIED,"알림 권한이 거부되었습니다. 브라우저 설정에서 허용해주세요.");this._messaging=t.getMessaging(s);const n={serviceWorkerRegistration:i};this.vapidKey&&(n.vapidKey=this.vapidKey);try{this._currentToken=await t.getToken(this._messaging,n)}catch(e){throw new M(A.TOKEN_FAILED,"FCM 토큰 획득 중 오류 발생: "+(e?.message||e),e)}if(!this._currentToken)throw new M(A.TOKEN_FAILED,"FCM 토큰 획득 실패 (응답이 비어있음)");await this._registerTokenToServer(this._currentToken),this._foregroundMessageUnsubscribe&&this._foregroundMessageUnsubscribe(),this._foregroundMessageUnsubscribe=t.onMessage(this._messaging,e=>{this.logger.debug("포그라운드 푸시 수신:",e),this._onForegroundMessage(e)}),this._swRegistration=i,await this._registerProjectToSW(),this._installVisibilityReRegister();try{await i.update()}catch(e){this.logger.debug("SW 업데이트 체크 실패 (무시):",e.message)}const r=await this._getSWVersion(i);r?this.logger.debug("SW 버전:",r):this.logger.warn("[TalkFlow] 서비스 워커가 구버전(v1)입니다. 이미지 미리보기 등 리치 알림 기능을 사용하려면 firebase-messaging-sw.js 를 v2 로 업데이트하세요. 가이드: https://docs.talkflow.ai/push/sw-upgrade"),this._enabled=!0,this.logger.info("푸시 알림 활성화 완료")}async _registerServiceWorker(){try{const e=await navigator.serviceWorker.register(this.serviceWorkerPath,{updateViaCache:"none"});return this.logger.debug("서비스 워커 등록 완료:",this.serviceWorkerPath),e}catch(e){throw new M(A.SW_REGISTER_FAILED,`서비스 워커 등록 실패: ${this.serviceWorkerPath}\n프로젝트 public 폴더에 firebase-messaging-sw.js 파일을 배치하세요.`,e)}}async _registerTokenToServer(e){try{const t=await this._getDeviceId();await this.apiClient.post("/api/v1/push/devices",{deviceToken:e,deviceType:"WEB",deviceId:t}),this.logger.info("디바이스 토큰 서버 등록 완료")}catch(e){if(this.logger.error("디바이스 토큰 서버 등록 실패:",e),e instanceof M)throw e;throw new M(A.SERVER_REGISTER_FAILED,"디바이스 토큰 서버 등록 실패: "+(e?.message||e),e)}}async _getDeviceId(){if(this._deviceIdCache)return this._deviceIdCache;try{let e=await $();if(!e&&"undefined"!=typeof localStorage){const t=localStorage.getItem(k);if(t){e=t;if(await F(e)){if(await $()===e)try{localStorage.removeItem(k),this.logger.debug("deviceId migrated from localStorage to IndexedDB")}catch(e){}else this.logger.warn("deviceId IDB read-back 불일치, localStorage 보존")}else this.logger.warn("deviceId IDB 저장 실패 (private mode / quota 등), localStorage 보존")}}if(!e){e=W();await F(e)||this.logger.warn("신규 deviceId IDB 저장 실패, 이번 세션만 유효")}return this._deviceIdCache=e,e}catch(e){if(this.logger.warn("IndexedDB deviceId 조회/저장 실패, fallback:",e),!this._deviceIdCache){let e=null;if("undefined"!=typeof localStorage)try{e=localStorage.getItem(k)}catch(e){}this._deviceIdCache=e||W()}return this._deviceIdCache}}_onForegroundMessage(e){}async consumePendingRoom(){return x.consumePendingRoom(this.projectId)}reset(){if(this._foregroundMessageUnsubscribe)try{this._foregroundMessageUnsubscribe()}catch(e){this.logger.debug("포그라운드 푸시 리스너 해제 실패 (무시):",e?.message||e)}this._uninstallVisibilityReRegister(),this._unregisterProjectFromSW(),this._foregroundMessageUnsubscribe=null,this._enablePromise=null,this._messaging=null,this._currentToken=null,this._enabled=!1,this._swRegistration=null,this._projectRegisteredToSW=!1}async _registerProjectToSW(){if(this.projectId)try{const e=(await navigator.serviceWorker.ready).active||this._swRegistration&&this._swRegistration.active;if(!e)return void this.logger.debug("SW active worker 없음 — projectId 등록 스킵");e.postMessage({type:"TALKFLOW_REGISTER_PROJECT",projectId:this.projectId}),this._projectRegisteredToSW=!0}catch(e){this.logger.debug("SW projectId 등록 실패 (무시):",e?.message||e)}}_unregisterProjectFromSW(){if(this._projectRegisteredToSW)try{if("undefined"==typeof navigator||!navigator.serviceWorker)return;const e=navigator.serviceWorker.controller;if(!e)return;e.postMessage({type:"TALKFLOW_UNREGISTER_PROJECT"})}catch(e){this.logger.debug("SW projectId 해제 실패 (무시):",e?.message||e)}}_installVisibilityReRegister(){"undefined"==typeof document||this._visibilityHandler||(this._visibilityHandler=()=>{"visible"===document.visibilityState&&this._enabled&&this.projectId&&this._registerProjectToSW().catch(()=>{})},document.addEventListener("visibilitychange",this._visibilityHandler))}_uninstallVisibilityReRegister(){if("undefined"!=typeof document&&this._visibilityHandler){try{document.removeEventListener("visibilitychange",this._visibilityHandler)}catch(e){}this._visibilityHandler=null}}getToken(){return this._currentToken}isEnabled(){return this._enabled}async getMyDevices(){const e=await this.apiClient.get("/api/v1/push/devices/me");return e&&"object"==typeof e&&"data"in e?e.data:e}async setDeviceEnabled(e,t){await this.apiClient.patch("/api/v1/push/devices/me/enabled",{deviceId:e,enabled:t}),this.logger.info(`디바이스 푸시 enabled=${t}: deviceId=${e}`)}async setCurrentDeviceEnabled(e){const t=await this._getDeviceId();await this.setDeviceEnabled(t,e)}async _getSWVersion(e){try{const t=await navigator.serviceWorker.ready,s=t.waiting||t.installing||t.active||e.waiting||e.installing||e.active;return s?new Promise(e=>{const t=setTimeout(()=>e(null),2e3),i=new MessageChannel;i.port1.onmessage=s=>{clearTimeout(t),e(s.data?.version||null)};try{s.postMessage({type:"TALKFLOW_SW_VERSION"},[i.port2])}catch(s){clearTimeout(t),e(null)}}):null}catch{return null}}setLogLevel(e){this.logger.setLevel(e)}}const G=[["connected","connected"],["disconnected","disconnected"],["reconnecting","reconnecting"],["error","connectionError"]],J=[["message","chatMessage"],["newMessage","newChatMessage"],["messageUpdated","messageUpdated"],["messageDeleted","messageDeleted"],["reactionChanged","reactionChanged"],["linkPreviewAttached","linkPreviewAttached"],["messageTranslated","messageTranslated"],["messageRead","messageRead"],["typing","typing"],["memberJoined","memberJoined"],["memberLeft","memberLeft"],["roomSubscribed","roomSubscribed"],["roomUnsubscribed","roomUnsubscribed"],["roomListSubscribed","roomListSubscribed"],["roomListUnsubscribed","roomListUnsubscribed"],["roomListUpdate","roomListUpdate"],["roomListMessage","roomListMessage"],["roomListCreated","roomListCreated"],["roomListJoined","roomListJoined"],["roomListLeft","roomListLeft"],["roomListSelfLeft","roomListSelfLeft"],["roomListKicked","roomListKicked"],["roomListSelfKicked","roomListSelfKicked"],["roomListBanned","roomListBanned"],["roomListSelfBanned","roomListSelfBanned"],["roomListRoomUpdated","roomListRoomUpdated"],["retentionCleanup","retentionCleanup"]],V=[["localStreamStarted","localStreamStarted"],["localStreamStopped","localStreamStopped"],["remoteTrack","remoteTrack"],["screenShareStarted","screenShareStarted"],["screenShareEnded","screenShareEnded"],["deviceChange","deviceChange"],["mediaStateChanged","mediaStateChanged"],["callStarted","callStarted"],["callEnded","callEnded"],["callRequested","callRequested"],["callAccepted","callAccepted"],["callRejected","callRejected"],["callCancelled","callCancelled"],["callBusy","callBusy"],["incomingCall","incomingCall"],["incomingCallWhileBusy","incomingCallWhileBusy"],["callInvitation","callInvitation"],["userJoined","userJoined"],["userLeft","userLeft"],["participantLeft","participantLeft"],["participantMediaState","participantMediaState"],["peerConnected","peerConnected"],["peerDisconnected","peerDisconnected"],["peerClosed","peerClosed"],["error","webrtcError"]];function B(e,t,s){s.forEach(([s,i])=>{e.on(s,e=>{t.emit(i,e)})})}const H=["getRooms","getRoom","getRoomInfo","setMyRoomLanguage","createOneToOneRoom","createGroupRoom","joinGroupRoom","leaveRoom","updateGroupRoom","inviteToGroupRoom","kickMember","banMember","unbanMember","getBannedMembers","getAvailableGroupRooms","getAllGroupRooms","enterRoom","getMessages","fetchLinkPreview","sendMessage","sendMessageOptimistic","sendTextMessage","sendTextMessageOptimistic","sendReply","sendReplyOptimistic","uploadFile","sendFileMessage","sendFileMessageOptimistic","editMessage","deleteMessage","markAsRead","pinMessage","unpinMessage","toggleReaction","subscribeRoom","unsubscribeRoom","unsubscribeAllRooms","setActiveRoom","clearActiveRoom","getActiveRoom","subscribeRoomList","unsubscribeRoomList","isRoomListSubscribed","getSubscribedRooms","isSubscribed","startTyping","stopTyping","getAssistants","getRoomAiMeta","rateAssistantMessage","summarizeWithAssistant","translateWithAssistant","getRoomPmPrompt","upsertRoomPmPrompt","activateRoomPmPrompt","deactivateRoomPmPrompt","getRoomPmPromptVersions","activateRoomPmPromptVersion","previewRoomPmPrompt"],K=["initializeIceServers","getTurnCredentials","createCallRoom","getCallRoom","joinCallRoomApi","leaveCallRoomApi","enableIncomingCalls","disableIncomingCalls","isIncomingCallsEnabled","startCall","callUser","acceptCall","rejectCall","cancelCall","endCall","toggleVideo","toggleAudio","setVideoEnabled","setAudioEnabled","startScreenShare","stopScreenShare","getLocalStream","getDevices","switchDevice","startDeviceChangeDetection","stopDeviceChangeDetection","applyVideoConstraints","getVideoSettings","getAudioSettings","isInCall","getCurrentRoom","getParticipants","getMediaState","getConnectionSummary","getStats"];function z(e,t,s){s.forEach(s=>{e[s]=function(...e){return this[t][s](...e)}})}class q extends i{constructor(e){super(),this._validateOptions(e);const t=e.env||g.environment,s=(e.serverUrl||p(t)).replace(/\/$/,"");this.options={serverUrl:s,env:t,apiKey:e.apiKey,projectId:e.projectId,jwtToken:e.jwtToken||null,useSockJS:!1!==e.useSockJS,reconnectDelay:e.reconnectDelay||g.reconnectDelay,maxReconnectAttempts:e.maxReconnectAttempts||g.maxReconnectAttempts,iceServers:e.iceServers||g.iceServers,autoSubscribeRoomList:!1!==e.autoSubscribeRoomList,logLevel:void 0!==e.logLevel?e.logLevel:u.WARN},this.logger=new m(this.options.logLevel,"TalkFlowClient"),this._initialized=!1,this._state=n.DISCONNECTED,this._userId=null,this.options.jwtToken&&(this._userId=S(this.options.jwtToken)),this.apiClient=new E({baseUrl:this.options.serverUrl,apiKey:this.options.apiKey,projectId:this.options.projectId,jwtToken:this.options.jwtToken,logLevel:this.options.logLevel}),this.connectionManager=null,this._chat=null,this._webrtc=null,this._pushManager=null,this._pushEnablePromise=null,this.options.jwtToken&&this._userId&&this._initializeSubClients(),this._initialized=!0,this.logger.info("TalkFlowClient initialized",{userId:this._userId,hasToken:!!this.options.jwtToken})}get chat(){return this._chat}get webrtc(){return this._webrtc}get pushManager(){return this._pushManager}get userId(){return this._userId}_validateOptions(e){if(!e)throw new Error("Options are required");if(!e.apiKey)throw new Error("apiKey is required");if(!e.projectId)throw new Error("projectId is required");if(e.apiKey.startsWith("sk-")){const t=e.env||"production";"development"===t?console.warn("⚠️ [TalkFlow] Server Key (sk-) 를 개발 모드에서 사용 중입니다. 프로덕션 배포 전에 반드시 Client Key (ck-) 로 교체하세요."):console.error("🚨 [TalkFlow] Server Key (sk-) 가 비개발 환경 ("+t+") 에서 감지되었습니다. 보안 위험: Server Key 를 브라우저에 포함하면 공격자가 임의 사용자로 JWT 를 발급받을 수 있습니다. 반드시 Client Key (ck-) 로 교체하세요. 서버에서도 프로덕션 모드에서 Server Key 의 브라우저 호출을 차단합니다.")}}_initializeSubClients(){!function(e){if(!e.connectionManager){if(!e.userId)throw new Error("userId is required to initialize sub-clients. Please set JWT token first.");e.connectionManager=new v({serverUrl:e.options.serverUrl,jwtToken:e.options.jwtToken,apiKey:e.options.apiKey,projectId:e.options.projectId,useSockJS:e.options.useSockJS,reconnectDelay:e.options.reconnectDelay,maxReconnectAttempts:e.options.maxReconnectAttempts,logLevel:e.options.logLevel}),e._chat=new f({connectionManager:e.connectionManager,apiClient:e.apiClient,userId:e._userId,logLevel:e.options.logLevel}),e._webrtc=new R({connectionManager:e.connectionManager,apiClient:e.apiClient,userId:e._userId,iceServers:e.options.iceServers,logLevel:e.options.logLevel}),e._setupEventForwarding(),e.logger.debug("Sub-clients initialized")}}(this)}_setupEventForwarding(){var e;(e=this).connectionManager&&(e.connectionManager.on("stateChange",({state:t,prevState:s})=>{e._state=t,e.emit("stateChange",{state:t,prevState:s})}),B(e.connectionManager,e,G),e.chat&&B(e.chat,e,J),e.webrtc&&B(e.webrtc,e,V))}isConnected(){return!!this.connectionManager&&this.connectionManager.isConnected()}getState(){return this._state}hasToken(){return!!this.options.jwtToken}async registerUser(e){const t={...e};if(void 0===t.preferredLanguage){const e=q.detectBrowserLanguage();e&&(t.preferredLanguage=e)}const s=await this.apiClient.post("/api/v1/users/auth",t);return s.data&&s.data.accessToken&&(await this.setToken(s.data.accessToken),this.logger.info("User authenticated, token auto-set")),s}async updateMyInfo(e){return this._checkToken(),this.apiClient.put("/api/v1/users/update",e)}async setPreferredLanguage(e){return this.updateMyInfo({preferredLanguage:e})}static detectBrowserLanguage(){return"undefined"!=typeof navigator&&navigator.language&&navigator.language.trim().toLowerCase()||null}static displayText(e,t){return e&&e.translations&&t&&e.translations[t]?e.translations[t]:e?e.content:null}async checkUserExists(e){return this.apiClient.get(`/api/v1/users/${e}/exists`)}async getUsers(e={}){this._checkToken();const{size:t=50,lastId:s,lastSortValue:i}=e;return this.apiClient.get("/api/v1/users",{size:t,lastId:s,lastSortValue:i})}async searchUsers(e){this._checkToken();const{keyword:t,limit:s=20}=e;return this.apiClient.get("/api/v1/users/search",{keyword:t,limit:s})}_checkToken(){if(!this.options.jwtToken)throw new Error('JWT token is required for this operation. Obtain it from your backend (POST /api/v1/users/auth with Server API Key) and pass it via setToken(). See README "프로덕션 인증 플로우".')}getUserId(){return this.userId}isInitialized(){return this._initialized}isReady(){return this._initialized&&!!this.connectionManager}setLogLevel(e){this.options.logLevel=e,this.logger.setLevel(e),this.apiClient.logger?.setLevel(e),this.connectionManager&&this.connectionManager.setLogLevel(e),this.chat&&this.chat.setLogLevel(e),this.webrtc&&this.webrtc.setLogLevel(e)}getStatus(){return{initialized:this._initialized,ready:this.isReady(),hasToken:this.hasToken(),connectionState:this._state,isConnected:this.isConnected(),userId:this.userId,chat:this.chat?{subscribedRooms:this.chat.getSubscribedRooms()}:null,webrtc:this.webrtc?{isInCall:this.webrtc.isInCall(),currentRoom:this.webrtc.getCurrentRoom(),participants:this.webrtc.getParticipants(),mediaState:this.webrtc.getMediaState()}:null}}async destroy(){await this.disconnect(),this.chat&&this.chat.destroy(),this.webrtc&&this.webrtc.destroy(),this.connectionManager&&await this.connectionManager.destroy(),this.removeAllListeners(),this._initialized=!1,this.logger.info("TalkFlowClient destroyed")}}q.ConnectionState=n,q.ErrorTypes=r,q.LogLevel=u,q.Environment=d,function(e){e.prototype.connect=async function(e,t={}){const{enablePush:s=!1}=t;if(e&&await this.setToken(e),!this.options.jwtToken)throw new Error('JWT token is required. Obtain it from your backend (which calls POST /api/v1/users/auth with a Server API Key) and pass it to the SDK via the jwtToken option, setToken(), or connect(jwt). See README "프로덕션 인증 플로우".');if(T(this.options.jwtToken))throw new Error("JWT token has expired. Please update the token before connecting.");if(this.connectionManager||this._initializeSubClients(),await this.connectionManager.connect(),this.webrtc&&await this.webrtc.enableIncomingCalls(),this.chat&&this.options.autoSubscribeRoomList)try{await this.chat.subscribeRoomList()}catch(e){this.logger.warn("Failed to auto-subscribe room list (non-fatal):",e)}s&&this.enablePushNotifications(),this.logger.info("Connected to server")},e.prototype.disconnect=async function(){this.connectionManager&&(this.webrtc&&this.webrtc.isInCall()&&this.webrtc.endCall(),this.webrtc&&this.webrtc.disableIncomingCalls(),this.chat&&(this.chat.unsubscribeAllRooms(),this.chat.unsubscribeRoomList()),await this.connectionManager.disconnect(),this.logger.info("Disconnected from server"))},e.prototype.logout=async function(){try{await this.apiClient.post("/v1/auth/signout"),this.logger.info("Logged out from server")}catch(e){this.logger.warn("Server logout failed (proceeding with local cleanup):",e.message)}await this.disconnect(),this.pushManager&&(this.pushManager.reset(),this._pushEnablePromise=null),this.options.jwtToken=null,this._userId=null,this.apiClient.setJwtToken(null),this.emit("loggedOut")},e.prototype.enablePushNotifications=async function(e={}){if(!this.options.jwtToken){const e=new Error("JWT token is required to enable push notifications.");return this.emit("pushFailed",{reason:"NO_TOKEN",error:e}),{ok:!1,reason:"NO_TOKEN",error:e}}if(!this.pushManager){this._pushManager=new x({apiClient:this.apiClient,projectId:this.options.projectId,firebaseConfig:e.firebaseConfig,vapidKey:e.vapidKey,serviceWorkerPath:e.serviceWorkerPath,logLevel:this.options.logLevel});const t=3e5;this.pushManager._onForegroundMessage=e=>{const s=e.data||{};if(s.projectId&&s.projectId!==this.options.projectId)return void this.logger.debug("다른 프로젝트 푸시 무시:",s.projectId);const i=this.chat?.getActiveRoom();if(i&&i===s.roomId)this.logger.debug("포그라운드 푸시 suppress (현재 방):",s.roomId);else{if(s.messageId){if(this._recentPushMessageIds||(this._recentPushMessageIds=new Map),this._recentPushMessageIds.has(s.messageId))return void this.logger.debug("포그라운드 푸시 dedup:",s.messageId);const e=setTimeout(()=>{this._recentPushMessageIds.delete(s.messageId)},t);this._recentPushMessageIds.set(s.messageId,e)}this.emit("pushNotification",{title:e.notification?.title||s.title,body:e.notification?.body||s.body,data:s})}}}if(this.pushManager.isEnabled())return this.logger.debug("웹 푸시가 이미 활성화되어 있어 중복 호출을 건너뜁니다."),{ok:!0,alreadyEnabled:!0};if(this._pushEnablePromise)return this.logger.debug("웹 푸시 활성화가 이미 진행 중입니다."),this._pushEnablePromise;const t=(async()=>{try{return await this.pushManager.enable(),this.emit("pushEnabled"),{ok:!0}}catch(e){const t=e&&e.code?e.code:"UNKNOWN";return this.logger.warn("Failed to enable push notifications:",t,e?.message),this.emit("pushFailed",{reason:t,error:e}),{ok:!1,reason:t,error:e}}})();return this._pushEnablePromise=t,t.finally(()=>{this._pushEnablePromise===t&&(this._pushEnablePromise=null)}),t},e.prototype.consumePendingRoom=async function(){return x.consumePendingRoom(this.options.projectId)},e.prototype.setCurrentDeviceEnabled=function(e){return this.pushManager?.setCurrentDeviceEnabled(e)},e.prototype.setDeviceEnabled=function(e,t){return this.pushManager?.setDeviceEnabled(e,t)},e.prototype.getMyDevices=function(){return this.pushManager?.getMyDevices()},e.prototype.getPushPermissionState=function(){return x.getPermissionState()},e.prototype.isPushEnabled=function(){return this.pushManager?.isEnabled()??!1},e.prototype.getPushToken=function(){return this.pushManager?.getToken()??null},e.prototype.resetPush=function(){this.pushManager?.reset(),this._pushEnablePromise=null},e.prototype.setToken=async function(e){const{userId:t}=I(e,{validateExpiry:!1}),s=this.userId&&this.userId!==t;this.options.jwtToken=e,this._userId=t,this.apiClient.setJwtToken(e),s&&this.connectionManager?(await this.disconnect(),this.pushManager&&(this.pushManager.reset(),this._pushEnablePromise=null),this.connectionManager=null,this._chat=null,this._webrtc=null,this._initializeSubClients()):this.connectionManager?this.connectionManager.updateToken(e):this._initializeSubClients(),this.logger.info("JWT token set",{userId:t}),this.emit("tokenSet",{userId:t})},e.prototype.updateToken=async function(e){await this.setToken(e)}}(q),function(e){z(e.prototype,"chat",H),z(e.prototype,"webrtc",K)}(q),e.AssistantMode={GENERAL:"GENERAL",PEOPLE_ONLY:"PEOPLE_ONLY",CALL_ONLY:"CALL_ONLY"},e.ChatClient=f,e.ChatMessageType={TEXT:"TEXT",IMAGE:"IMAGE",FILE:"FILE",VIDEO:"VIDEO",AUDIO:"AUDIO",SYSTEM:"SYSTEM"},e.ChatRoomType=o,e.ConnectionManager=v,e.ConnectionState=n,e.DefaultConfig=g,e.Endpoints=h,e.EngagementIntensity={QUIET:"QUIET",NORMAL:"NORMAL",ACTIVE:"ACTIVE"},e.Environment=d,e.ErrorTypes=r,e.EventEmitter=i,e.LogLevel=u,e.Logger=m,e.MediaStreamManager=y,e.PeerConnectionManager=w,e.PersonaRole={LEGAL_ADVISOR:"LEGAL_ADVISOR",MARKETING:"MARKETING",PRODUCT_PLANNING:"PRODUCT_PLANNING",HR:"HR",FINANCE:"FINANCE",CUSTOMER_SUPPORT:"CUSTOMER_SUPPORT",SALES:"SALES",ENGINEERING:"ENGINEERING",DATA_ANALYST:"DATA_ANALYST",PROJECT_MANAGEMENT:"PROJECT_MANAGEMENT",RESEARCH:"RESEARCH",TRANSLATION:"TRANSLATION",DESIGN:"DESIGN",PM:"PM"},e.PmPromptLayerEditorType={SUPER_ADMIN:"SUPER_ADMIN",PROJECT_ADMIN:"PROJECT_ADMIN",ROOM_OWNER:"ROOM_OWNER"},e.PmPromptLayerScope={PROJECT:"PROJECT",ROOM:"ROOM"},e.PushError=M,e.PushErrorCode=A,e.PushManager=x,e.RoomAiType={NONE:"NONE",PERSONA_MULTI:"PERSONA_MULTI",PM_BACKSTAGE:"PM_BACKSTAGE"},e.RoomListEventType=c,e.SenderType={USER:"USER",ASSISTANT:"ASSISTANT"},e.SignalTypes=a,e.SummarizeFormat={MINUTES:"MINUTES",SHORT:"SHORT",TIMELINE:"TIMELINE",ACTIONS:"ACTIONS",OPTIONS:"OPTIONS"},e.WebRTCClient=R,e.WebSocketPaths=l,e.decodeJWTPayload=function(e){try{const t=_(e).split(".");return 3!==t.length?null:JSON.parse(b(t[1]))}catch(e){return console.error("Failed to decode JWT payload:",e),null}},e.default=q,e.extractUserIdFromJWT=S,e.getJWTRemainingTime=function(e){try{const t=_(e).split(".");if(3!==t.length)return-1;const s=JSON.parse(b(t[1]));return s.exp?1e3*s.exp-Date.now():1/0}catch(e){return console.error("Failed to get JWT remaining time:",e),-1}},e.getServerUrl=p,e.isJWTExpired=T,e.validateAndParseJWT=I,Object.defineProperty(e,"__esModule",{value:!0})});
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@stomp/stompjs"),require("sockjs-client")):"function"==typeof define&&define.amd?define(["exports","@stomp/stompjs","sockjs-client"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TalkFlowSDK={},e.StompJs,e.SockJS)}(this,function(e,t,s){"use strict";class i{constructor(){this._events=new Map}on(e,t){return this._events.has(e)||this._events.set(e,new Set),this._events.get(e).add(t),()=>this.off(e,t)}once(e,t){const s=(...i)=>{this.off(e,s),t.apply(this,i)};return s._originalListener=t,this.on(e,s)}off(e,t){const s=this._events.get(e);if(s){for(const e of s)if(e===t||e._originalListener===t){s.delete(e);break}0===s.size&&this._events.delete(e)}}emit(e,t){const s=this._events.get(e);s&&s.forEach(s=>{try{s(t)}catch(t){console.error(`Error in event listener for '${e}':`,t)}})}removeAllListeners(e){e?this._events.delete(e):this._events.clear()}listenerCount(e){const t=this._events.get(e);return t?t.size:0}eventNames(){return Array.from(this._events.keys())}}const r={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected",RECONNECTING:"reconnecting",ERROR:"error"},n={CONNECTION_FAILED:"CONNECTION_FAILED",CONNECTION_LOST:"CONNECTION_LOST",CONNECTION_TIMEOUT:"CONNECTION_TIMEOUT",JWT_INVALID:"JWT_INVALID",JWT_EXPIRED:"JWT_EXPIRED",JWT_PARSE_FAILED:"JWT_PARSE_FAILED",UNAUTHORIZED:"UNAUTHORIZED",API_ERROR:"API_ERROR",API_TIMEOUT:"API_TIMEOUT",CHAT_ROOM_NOT_FOUND:"CHAT_ROOM_NOT_FOUND",CHAT_MESSAGE_FAILED:"CHAT_MESSAGE_FAILED",CHAT_SUBSCRIPTION_FAILED:"CHAT_SUBSCRIPTION_FAILED",MEDIA_ACCESS_DENIED:"MEDIA_ACCESS_DENIED",SCREEN_SHARE_DENIED:"SCREEN_SHARE_DENIED",PEER_CONNECTION_FAILED:"PEER_CONNECTION_FAILED",ICE_CONNECTION_FAILED:"ICE_CONNECTION_FAILED",SIGNALING_FAILED:"SIGNALING_FAILED",DEVICE_SWITCH_FAILED:"DEVICE_SWITCH_FAILED",ENUMERATE_DEVICES_FAILED:"ENUMERATE_DEVICES_FAILED",CALL_ROOM_NOT_FOUND:"CALL_ROOM_NOT_FOUND",INVALID_STATE:"INVALID_STATE",UNKNOWN_ERROR:"UNKNOWN_ERROR"},a={CALL_OFFER:"call_offer",CALL_ANSWER:"call_answer",ICE_CANDIDATE:"ice_candidate",JOIN_ROOM:"join_room",LEAVE_ROOM:"leave_room",PEER_JOINED:"peer_joined",PEER_LEFT:"peer_left",VIDEO_STATE_CHANGED:"video_state_changed",AUDIO_STATE_CHANGED:"audio_state_changed",SCREEN_SHARE_STARTED:"screen_share_started",SCREEN_SHARE_ENDED:"screen_share_ended",CALL_REQUEST:"call_request",CALL_ACCEPT:"call_accept",CALL_REJECT:"call_reject",CALL_CANCEL:"call_cancel",CALL_END:"call_end",CALL_INVITATION:"call_invitation",CALL_BUSY:"call_busy"},o={DIRECT:"DIRECT",GROUP:"GROUP",PRIVATE_GROUP:"PRIVATE_GROUP",TEAM:"TEAM"},c={MESSAGE_RECEIVED:"MESSAGE_RECEIVED",MESSAGE_DELETED:"MESSAGE_DELETED",MESSAGE_UPDATED:"MESSAGE_UPDATED",ROOM_CREATED:"ROOM_CREATED",ROOM_JOINED:"ROOM_JOINED",ROOM_LEFT:"ROOM_LEFT",ROOM_UPDATED:"ROOM_UPDATED",ROOM_KICKED:"ROOM_KICKED",ROOM_BANNED:"ROOM_BANNED",MESSAGE_RETENTION_CLEANUP:"MESSAGE_RETENTION_CLEANUP"},l={SOCKJS_ENDPOINT:"/ws-chat",NATIVE_ENDPOINT:"/ws-chat-native",APP_PREFIX:"/app",TOPIC_PREFIX:"/topic",QUEUE_PREFIX:"/queue",USER_PREFIX:"/user",getChatDestination:e=>`/topic/chat/${e}`,getChatReadDestination:e=>`/topic/chat/${e}/read`,getChatTypingDestination:e=>`/topic/chat/${e}/typing`,getChatAssistantStreamDestination:e=>`/topic/chat/${e}/assistant-stream`,ROOM_LIST_USER_DESTINATION:"/user/queue/rooms",getWebRTCDestination:e=>`/topic/webrtc/${e}`,getWebRTCUserDestination:()=>"/user/queue/webrtc",CHAT_SEND:"/app/chat/send",CHAT_READ:"/app/chat/read",CHAT_TYPING:"/app/chat/typing",WEBRTC_SIGNAL:"/app/webrtc/signal"},d={DEVELOPMENT:"development",STAGING:"staging",PRODUCTION:"production"},h={[d.DEVELOPMENT]:{serverUrl:"https://dev-chat.apiorbit.net",wsEndpoint:"/ws-chat"},[d.STAGING]:{serverUrl:"https://stg-api.talk-x.app",wsEndpoint:"/ws-chat"},[d.PRODUCTION]:{serverUrl:"https://prod-api.talk-x.app",wsEndpoint:"/ws-chat"}},g={environment:d.PRODUCTION,reconnectDelay:5e3,maxReconnectAttempts:10,heartbeatIncoming:1e4,heartbeatOutgoing:1e4,apiTimeout:3e4,iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],logLevel:2};function u(e){return(h[e]||h[d.PRODUCTION]).serverUrl}const p={DEBUG:0,INFO:1,WARN:2,ERROR:3,NONE:4};class m{constructor(e=p.WARN,t="TalkFlow"){this.level=e,this.prefix=t}setLevel(e){this.level=e}setPrefix(e){this.prefix=e}_format(e,...t){return[`[${(new Date).toISOString()}] [${e}] [${this.prefix}]`,...t]}debug(...e){this.level<=p.DEBUG&&console.debug(...this._format("DEBUG",...e))}info(...e){this.level<=p.INFO&&console.info(...this._format("INFO",...e))}warn(...e){this.level<=p.WARN&&console.warn(...this._format("WARN",...e))}error(...e){this.level<=p.ERROR&&console.error(...this._format("ERROR",...e))}}class E{constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,""),this.apiKey=e.apiKey,this.projectId=e.projectId,this.jwtToken=e.jwtToken,this.timeout=e.timeout||g.apiTimeout,this.logger=new m(e.logLevel,"ApiClient")}setJwtToken(e){this.jwtToken=e}_getHeaders(){const e={"Content-Type":"application/json","X-API-KEY":this.apiKey,"X-PROJECT-ID":this.projectId};return this.jwtToken&&(e.Authorization=this.jwtToken.startsWith("Bearer ")?this.jwtToken:`Bearer ${this.jwtToken}`),e}async request(e,t,s={}){const{body:i,params:r}=s;let a=`${this.baseUrl}${t}`;if(r){const e=new URLSearchParams;Object.entries(r).forEach(([t,s])=>{null!=s&&e.append(t,s)});const t=e.toString();t&&(a+=`?${t}`)}const o=new AbortController,c=setTimeout(()=>o.abort(),this.timeout);let l;try{this.logger.debug(`${e} ${a}`,i?{body:i}:""),l=await fetch(a,{method:e,headers:this._getHeaders(),body:i?JSON.stringify(i):void 0,signal:o.signal})}catch(t){if(clearTimeout(c),"AbortError"===t.name){const e=new Error("Request timeout");throw e.code=n.API_TIMEOUT,e}throw this.logger.error(`API Error: ${e} ${a}`,t),t}clearTimeout(c);const d=l.headers.get("content-type");let h;if(h=d&&d.includes("application/json")?await l.json():await l.text(),!l.ok){const t=new Error(h?.message||`HTTP ${l.status}`);throw t.code=n.API_ERROR,t.status=l.status,t.response=h,this.logger.error(`API Error: ${e} ${a}`,t),t}return this.logger.debug(`Response ${l.status}:`,h),h}get(e,t){return this.request("GET",e,{params:t})}post(e,t,s){return this.request("POST",e,{body:t,params:s})}put(e,t){return this.request("PUT",e,{body:t})}patch(e,t){return this.request("PATCH",e,{body:t})}delete(e,t){return this.request("DELETE",e,{params:t})}upload(e,t,s={}){const{fieldName:i="file",onProgress:r,signal:a,timeout:o=6e5}=s;return new Promise((s,c)=>{const l=`${this.baseUrl}${e}`,d=new XMLHttpRequest,h=new FormData;h.append(i,t),d.open("POST",l),d.timeout=o,d.setRequestHeader("X-API-KEY",this.apiKey),d.setRequestHeader("X-PROJECT-ID",this.projectId),this.jwtToken&&d.setRequestHeader("Authorization",this.jwtToken.startsWith("Bearer ")?this.jwtToken:`Bearer ${this.jwtToken}`),"function"==typeof r&&(d.upload.onprogress=e=>{e.lengthComputable&&r({loaded:e.loaded,total:e.total,percent:Math.round(e.loaded/e.total*100)})});const g=()=>d.abort();if(a){if(a.aborted)return void c(new Error("Upload cancelled"));a.addEventListener("abort",g)}const u=()=>{a&&a.removeEventListener("abort",g)};d.onload=()=>{u();let e=d.responseText;if((d.getResponseHeader("content-type")||"").includes("application/json"))try{e=JSON.parse(d.responseText)}catch{}if(d.status>=200&&d.status<300)this.logger.debug(`Upload ${d.status}:`,e),s(e);else{const t=new Error(e?.message||`HTTP ${d.status}`);t.code=n.API_ERROR,t.status=d.status,t.response=e,this.logger.error(`Upload Error: POST ${l}`,t),c(t)}},d.onerror=()=>{u();const e=new Error("Network error during upload");e.code=n.API_ERROR,this.logger.error(`Upload Network Error: POST ${l}`,e),c(e)},d.ontimeout=()=>{u();const e=new Error("Upload timeout");e.code=n.API_TIMEOUT,this.logger.error(`Upload Timeout: POST ${l}`,e),c(e)},d.onabort=()=>{u(),c(new Error("Upload cancelled"))},this.logger.debug(`POST ${l} (multipart, ${t.size} bytes)`),d.send(h)})}}function I(e){return e&&"string"==typeof e?e.replace(/^Bearer\s+/i,""):""}function _(e){let t=e.replace(/-/g,"+").replace(/_/g,"/");const s=t.length%4;s&&(t+="=".repeat(4-s));const i=atob(t),r=Uint8Array.from(i,e=>e.charCodeAt(0));return(new TextDecoder).decode(r)}function b(e,t={}){const{bufferSeconds:s=30,validateExpiry:i=!0}=t;if(!e||"string"!=typeof e){const e=new Error("JWT token is required");throw e.code=n.JWT_INVALID,e}const r=I(e).split(".");if(3!==r.length){const e=new Error("Invalid JWT format: expected 3 parts separated by dots");throw e.code=n.JWT_INVALID,e}let a;try{a=JSON.parse(_(r[1]))}catch(e){const t=new Error("Invalid JWT: failed to decode payload");throw t.code=n.JWT_PARSE_FAILED,t.originalError=e,t}if(i&&a.exp){const e=1e3*a.exp,t=1e3*s,i=Date.now();if(e<i+t){const t=new Error(e<i?"JWT token expired":`JWT token expires within ${s} seconds`);throw t.code=n.JWT_EXPIRED,t.expiredAt=new Date(e).toISOString(),t}}if(a.iat){const e=6e4;if(1e3*a.iat>Date.now()+e){const e=new Error("JWT token issued in the future");throw e.code=n.JWT_INVALID,e}}const o=a.userId||a.sub||a.user_id;if(!o){const e=new Error("Cannot extract userId from JWT: missing userId, sub, or user_id claim");throw e.code=n.JWT_INVALID,e}return{userId:o,payload:a}}function S(e){try{const t=I(e).split(".");if(3!==t.length)return null;const s=JSON.parse(_(t[1]));return s.userId||s.sub||s.user_id||null}catch(e){return console.error("Failed to extract userId from JWT:",e),null}}function T(e,t=0){try{const s=I(e).split(".");if(3!==s.length)return!0;const i=JSON.parse(_(s[1]));if(!i.exp)return!1;const r=1e3*i.exp,n=1e3*t;return r<Date.now()+n}catch(e){return console.error("Failed to check JWT expiration:",e),!0}}const C=()=>"undefined"!=typeof window&&window.SockJS?window.SockJS:s;class v extends i{constructor(e){super(),this.serverUrl=e.serverUrl.replace(/\/$/,""),this.jwtToken=e.jwtToken,this.apiKey=e.apiKey,this.projectId=e.projectId,this.useSockJS=!1!==e.useSockJS,this.reconnectDelay=e.reconnectDelay||g.reconnectDelay,this.maxReconnectAttempts=e.maxReconnectAttempts||g.maxReconnectAttempts,this.heartbeatIncoming=e.heartbeatIncoming||g.heartbeatIncoming,this.heartbeatOutgoing=e.heartbeatOutgoing||g.heartbeatOutgoing,this.logger=new m(e.logLevel||p.WARN,"ConnectionManager"),this.state=r.DISCONNECTED,this.stompClient=null,this.reconnectAttempts=0,this.subscriptions=new Map,this.pendingSubscriptions=[],this.userId=null}_setState(e){const t=this.state;this.state=e,this.emit("stateChange",{state:e,prevState:t}),this.logger.info(`State changed: ${t} -> ${e}`)}_getWebSocketUrl(){const e=this.useSockJS?l.SOCKJS_ENDPOINT:l.NATIVE_ENDPOINT;return`${this.serverUrl}${e}`}_createStompClient(){const e=this._getWebSocketUrl(),s={connectHeaders:{Authorization:this.jwtToken.startsWith("Bearer ")?this.jwtToken:`Bearer ${this.jwtToken}`,"X-API-KEY":this.apiKey,"X-PROJECT-ID":this.projectId},heartbeatIncoming:this.heartbeatIncoming,heartbeatOutgoing:this.heartbeatOutgoing,reconnectDelay:this.reconnectDelay,debug:e=>{this.logger.level<=p.DEBUG&&this.logger.debug("STOMP:",e)},onConnect:e=>this._onConnect(e),onDisconnect:e=>this._onDisconnect(e),onStompError:e=>this._onStompError(e),onWebSocketClose:e=>this._onWebSocketClose(e),onWebSocketError:e=>this._onWebSocketError(e)};if(this.useSockJS){const t=C();s.webSocketFactory=()=>new t(e)}else{const e=this.serverUrl.startsWith("https")?"wss":"ws",t=this.serverUrl.replace(/^https?:\/\//,"");s.brokerURL=`${e}://${t}${l.NATIVE_ENDPOINT}`}return new("undefined"!=typeof window&&window.StompJs&&window.StompJs.Client?window.StompJs.Client:t.Client)(s)}async connect(){if(this.state!==r.CONNECTED){if(this.state!==r.CONNECTING)return this._setState(r.CONNECTING),this.reconnectAttempts=0,new Promise((e,t)=>{this._connectResolve=e,this._connectReject=t;try{this.stompClient=this._createStompClient(),this.stompClient.activate()}catch(e){this._setState(r.ERROR),this._drainPendingSubscriptions("Connection activation failed"),t(e)}});this.logger.warn("Connection in progress")}else this.logger.warn("Already connected")}_onConnect(e){this._setState(r.CONNECTED),this.reconnectAttempts=0,this.logger.info("Connected to server",e),this._restoreSubscriptions(),this._processPendingSubscriptions(),this.emit("connected",{frame:e}),this._connectResolve&&(this._connectResolve(),this._connectResolve=null,this._connectReject=null)}_onDisconnect(e){this.logger.info("Disconnected from server",e),this._setState(r.DISCONNECTED),this.emit("disconnected",{frame:e})}_onStompError(e){this.logger.error("STOMP error:",e),this._setState(r.ERROR);const t={type:n.CONNECTION_FAILED,message:e.headers?.message||"STOMP error",frame:e};this._drainPendingSubscriptions(`STOMP error: ${t.message}`),this.emit("error",t),this._connectReject&&(this._connectReject(new Error(t.message)),this._connectResolve=null,this._connectReject=null)}_onWebSocketClose(e){this.logger.warn("WebSocket closed:",e),this.state!==r.DISCONNECTED&&this._handleReconnect()}_onWebSocketError(e){this.logger.error("WebSocket error:",e),this._connectReject&&(this._drainPendingSubscriptions("WebSocket connection failed"),this._connectReject(new Error("WebSocket connection failed")),this._connectResolve=null,this._connectReject=null)}_handleReconnect(){if(this.reconnectAttempts++,this.reconnectAttempts>this.maxReconnectAttempts){if(this.stompClient){try{this.stompClient.deactivate()}catch(e){this.logger.warn("Error deactivating stomp client on max reconnect:",e)}this.stompClient=null}return this._drainPendingSubscriptions("Max reconnection attempts exceeded"),this._setState(r.ERROR),void this.emit("error",{type:n.CONNECTION_LOST,message:"Max reconnection attempts exceeded"})}this._setState(r.RECONNECTING),this.emit("reconnecting",{attempt:this.reconnectAttempts}),this.logger.info(`Reconnecting... attempt ${this.reconnectAttempts}`)}_restoreSubscriptions(){if(0===this.subscriptions.size)return;this.logger.info(`Restoring ${this.subscriptions.size} subscriptions after reconnect`);const e=new Map(this.subscriptions);this.subscriptions.clear(),e.forEach(({callback:e,headers:t},s)=>{this._subscribeInternal(s,e,t),this.logger.debug(`Restored subscription: ${s}`)})}_processPendingSubscriptions(){for(;this.pendingSubscriptions.length>0;){const{destination:e,callback:t,headers:s,resolve:i}=this.pendingSubscriptions.shift(),r=this._subscribeInternal(e,t,s);i&&i(r)}}_subscribeInternal(e,t,s={}){const i=this.stompClient.subscribe(e,e=>{try{const s=JSON.parse(e.body);t(s,e)}catch(s){this.logger.error("Failed to parse message:",s),t(e.body,e)}},s);return this.subscriptions.set(e,{subscription:i,callback:t,headers:s}),this.logger.debug(`Subscribed to: ${e}`),i}subscribe(e,t,s={}){return this.subscriptions.has(e)&&this.unsubscribe(e),this.state!==r.CONNECTED?new Promise((i,r)=>{this.pendingSubscriptions.push({destination:e,callback:t,headers:s,resolve:i,reject:r}),this.logger.debug(`Queued subscription for: ${e}`)}):Promise.resolve(this._subscribeInternal(e,t,s))}unsubscribe(e){const t=this.subscriptions.get(e);t&&(t.subscription.unsubscribe(),this.subscriptions.delete(e),this.logger.debug(`Unsubscribed from: ${e}`))}unsubscribeAll(){this.subscriptions.forEach(({subscription:e},t)=>{e.unsubscribe(),this.logger.debug(`Unsubscribed from: ${t}`)}),this.subscriptions.clear()}send(e,t,s={}){return this.state!==r.CONNECTED?(this.logger.warn("Cannot send message: not connected"),!1):(this.stompClient.publish({destination:e,body:JSON.stringify(t),headers:s}),this.logger.debug(`Sent to ${e}:`,t),!0)}updateToken(e){this.jwtToken=e,this.stompClient&&(this.stompClient.connectHeaders={...this.stompClient.connectHeaders,Authorization:e.startsWith("Bearer ")?e:`Bearer ${e}`})}async disconnect(){this._drainPendingSubscriptions("Disconnected before subscription completed"),this.stompClient&&(this.unsubscribeAll(),await this.stompClient.deactivate(),this.stompClient=null),this._setState(r.DISCONNECTED),this.logger.info("Disconnected")}_drainPendingSubscriptions(e){if(0===this.pendingSubscriptions.length)return;const t=this.pendingSubscriptions;this.pendingSubscriptions=[],t.forEach(({destination:t,reject:s})=>{s&&s(new Error(`${e}: ${t}`))}),this.logger.debug(`Drained ${t.length} pending subscriptions: ${e}`)}isConnected(){return this.state===r.CONNECTED}getState(){return this.state}setLogLevel(e){this.logger.setLevel(e)}async destroy(){await this.disconnect(),this.removeAllListeners(),this.logger.info("ConnectionManager destroyed")}}class f extends i{constructor(e){super(),this.connectionManager=e.connectionManager,this.apiClient=e.apiClient,this.userId=e.userId,this.logger=new m(e.logLevel||p.WARN,"ChatClient"),this.subscribedRooms=new Map,this.roomListSubscribed=!1,this._activeRoomId=null,this._typingTimers=new Map,this._assistantTypingTimers=new Map,this._assistantTypingTimeoutMs=3e4,this._assistantTypingUserId="__assistant__",this._assistantTypingUserName="AI",this._assistantProgress=new Map,this._assistantPhaseLabels={THINKING:"생각 중…",SEARCHING:"검색 중…",PLANNING:"기획 중…",WRITING:"작성 중…"},this._assistantProgressFallbackTtlMs=35e3,this._seenChatMessageIdsByRoom=new Map,this._seenRoomListMessageIdsByRoom=new Map,this._maxSeenPerRoom=200}_shouldDedupMessage(e,t,s){if(!t||!s)return!1;let i=e.get(t);if(i||(i=new Map,e.set(t,i)),i.has(s))return!0;if(i.set(s,!0),i.size>this._maxSeenPerRoom){const e=i.keys().next().value;i.delete(e)}return!1}async getRooms(e={}){const{size:t=50,lastId:s,lastSortValue:i,type:r}=e;return this.apiClient.get("/api/v1/rooms/my",{size:t,lastId:s,lastSortValue:i,type:r})}async getRoom(e){return this.apiClient.get(`/api/v1/rooms/${e}`)}async enterRoom(e){const t=this.subscribedRooms.has(e);t||await this.subscribeRoom(e),this.setActiveRoom(e);try{return await this.getRoom(e)}catch(s){throw this.getActiveRoom()===e&&this.clearActiveRoom(),t||this.unsubscribeRoom(e),s}}async getRoomInfo(e){return this.apiClient.get(`/api/v1/rooms/${e}/info`)}async setMyRoomLanguage(e,t){return this.apiClient.put(`/api/v1/rooms/${e}/my-language`,{language:t})}async createOneToOneRoom(e){return this.apiClient.post(`/api/v1/rooms/direct/${e}`)}async createGroupRoom(e){const t=e.roomType||o.GROUP;if(t===o.PRIVATE_GROUP){if(!e.password||e.password.length<4)throw new Error("PRIVATE_GROUP requires a password of at least 4 characters")}else if(t===o.GROUP){if(e.password)throw new Error("Public GROUP rooms cannot have a password")}else if(t===o.TEAM&&e.password)throw new Error("TEAM rooms cannot have a password (invite-only)");return this.apiClient.post("/api/v1/rooms/group",{roomName:e.roomName,description:e.description,invitedUserIds:e.invitedUserIds,roomType:t,password:e.password,messageRetentionHours:e.messageRetentionHours,invitedAssistantPersonaIds:e.invitedAssistantPersonaIds,assistantMode:e.assistantMode,roomAiType:e.roomAiType,engagementIntensity:e.engagementIntensity})}async getAssistants(){const e=await this.apiClient.get("/api/v1/assistants");return this._unwrapSuccessResponse(e)}async getRoomAiMeta(){const e=await this.apiClient.get("/api/v1/rooms/ai-meta");return this._unwrapSuccessResponse(e)}async rateAssistantMessage(e,t,s,i){if(!Number.isInteger(s)||s<1||s>5)throw new Error("rating must be an integer between 1 and 5");await this.apiClient.post(`/api/v1/chat-messages/${e}/messages/${t}/rating`,{rating:s,comment:i})}async summarizeWithAssistant(e,t={}){const{personaId:s,format:i,messageCount:r}=t;if(!s)throw new Error("summarizeWithAssistant requires options.personaId");const n=await this.apiClient.post(`/api/v1/rooms/${e}/assistant/tools/summarize`,{personaId:s,format:i,messageCount:r});return this._unwrapSuccessResponse(n)}async translateWithAssistant(e,t={}){const{personaId:s,targetLang:i,sourceMessageId:r}=t;if(!s)throw new Error("translateWithAssistant requires options.personaId");if(!r)throw new Error("translateWithAssistant requires options.sourceMessageId");const n=await this.apiClient.post(`/api/v1/rooms/${e}/assistant/tools/translate`,{personaId:s,targetLang:i,sourceMessageId:r});return this._unwrapSuccessResponse(n)}async getRoomPmPrompt(e){const t=await this.apiClient.get(`/api/v1/rooms/${e}/pm-prompt`);return this._unwrapSuccessResponse(t)}async upsertRoomPmPrompt(e,t){if("string"!=typeof t||!t.trim())throw new Error("upsertRoomPmPrompt requires non-blank content");if(t.length>2e3)throw new Error("upsertRoomPmPrompt content exceeds 2000 characters");const s=await this.apiClient.put(`/api/v1/rooms/${e}/pm-prompt`,{content:t});return this._unwrapSuccessResponse(s)}async activateRoomPmPrompt(e){const t=await this.apiClient.patch(`/api/v1/rooms/${e}/pm-prompt/activate`);return this._unwrapSuccessResponse(t)}async deactivateRoomPmPrompt(e){const t=await this.apiClient.patch(`/api/v1/rooms/${e}/pm-prompt/deactivate`);return this._unwrapSuccessResponse(t)}async getRoomPmPromptVersions(e){const t=await this.apiClient.get(`/api/v1/rooms/${e}/pm-prompt/versions`);return this._unwrapSuccessResponse(t)}async activateRoomPmPromptVersion(e,t){if(!Number.isInteger(t)||t<1)throw new Error("activateRoomPmPromptVersion requires a positive integer version");const s=await this.apiClient.post(`/api/v1/rooms/${e}/pm-prompt/versions/${t}/activate`);return this._unwrapSuccessResponse(s)}async previewRoomPmPrompt(e){const t=await this.apiClient.get(`/api/v1/rooms/${e}/pm-prompt/preview`);return this._unwrapSuccessResponse(t)}async joinGroupRoom(e,t){const s=t?{password:t}:void 0;return this.apiClient.post(`/api/v1/rooms/group/${e}/join`,s)}async leaveRoom(e){const t=await this.apiClient.post(`/api/v1/rooms/${e}/leave`);return this.unsubscribeRoom(e),t}async updateGroupRoom(e,t){return this.apiClient.put(`/api/v1/rooms/group/${e}`,t)}async inviteToGroupRoom(e,t){const s=Array.isArray(t)?{userIds:t}:{userIds:t?.userIds,assistantPersonaIds:t?.assistantPersonaIds};return this.apiClient.post(`/api/v1/rooms/group/${e}/invite`,s)}async kickMember(e,t){return this.apiClient.delete(`/api/v1/rooms/${e}/members/${t}`)}async banMember(e,t){return this.apiClient.post(`/api/v1/rooms/${e}/banned-members`,{userId:t})}async unbanMember(e,t){return this.apiClient.delete(`/api/v1/rooms/${e}/banned-members/${t}`)}async getBannedMembers(e){const t=await this.apiClient.get(`/api/v1/rooms/${e}/banned-members`);return t&&"object"==typeof t&&"data"in t?t.data:t}async pinMessage(e,t){return this.apiClient.post(`/api/v1/rooms/group/${e}/pin/${t}`)}async unpinMessage(e){return this.apiClient.post(`/api/v1/rooms/group/${e}/unpin`)}async toggleReaction(e,t,s){return this.apiClient.post(`/api/v1/chat-messages/${e}/messages/${t}/reaction`,{emoji:s})}async getAvailableGroupRooms(e={}){const{size:t=50,lastId:s,lastSortValue:i}=e;return this.apiClient.get("/api/v1/rooms/groups/available",{size:t,lastId:s,lastSortValue:i})}async getAllGroupRooms(e={}){const{size:t=50,lastId:s,lastSortValue:i}=e;return this.apiClient.get("/api/v1/rooms/groups/all",{size:t,lastId:s,lastSortValue:i})}async getMessages(e,t={}){const{size:s=50,lastId:i,lastSortValue:r}=t,n=await this.apiClient.get(`/api/v1/chat-messages/${e}/list`,{size:s,lastId:i,lastSortValue:r}),a=this._unwrapSuccessResponse(n);return a&&Array.isArray(a.content)&&a.content.forEach(e=>this._normalizeMessageTimestamps(e)),n}async fetchLinkPreview(e){const t="string"==typeof e?e.trim():"";if(!t)throw new Error("url is required");const s=await this.apiClient.post("/api/v1/link-preview",{url:t});return this._unwrapSuccessResponse(s)}async sendMessage(e,t){const s=this._generateMessageId();return this._sendMessageWithId(e,s,t)}sendMessageOptimistic(e,t){const s=this._generateMessageId(),i=this._predictMessageIdsFromData(s,t),r=this._sendMessageWithId(e,s,t).then(e=>this._attachOptimisticMetadata(i,e));return{baseMessageId:s,messageIds:i,promise:r}}async _sendMessageWithId(e,t,s){this.stopTyping(e);const i=await this.apiClient.post(`/api/v1/chat-messages/${e}/send`,{messageId:t,message:s.message,fileInfos:s.fileInfos,separateFiles:!1!==s.separateFiles,replyToMessageId:s.replyToMessageId}),r=this._unwrapSuccessResponse(i);return r&&Array.isArray(r.messages)&&r.messages.forEach(e=>this._normalizeMessageTimestamps(e)),r}async sendTextMessage(e,t){return this.sendMessage(e,{message:t})}sendTextMessageOptimistic(e,t){return this.sendMessageOptimistic(e,{message:t})}async uploadFile(e,t,s={}){if(!e)throw new Error("roomId is required");if(!t)throw new Error("file is required");const i=await this.apiClient.upload(`/api/v1/files/${e}/upload`,t,{onProgress:s.onProgress,signal:s.signal});return this._unwrapSuccessResponse(i)}async sendFileMessage(e,t,s={}){const i=this._normalizeFileArray(t),r=await this._uploadFiles(e,i,s),n=this._applyFileMetadata(r,s.metadata);return this.sendMessage(e,{message:s.message,fileInfos:n,separateFiles:s.separateFiles,replyToMessageId:s.replyToMessageId})}sendFileMessageOptimistic(e,t,s={}){const i=this._normalizeFileArray(t),r=this._generateMessageId(),n=this._predictMessageIds(r,this._predictGroupCount(i,!1!==s.separateFiles),this._hasTextMessage(s.message)),a=(async()=>{const t=await this._uploadFiles(e,i,s),a=this._applyFileMetadata(t,s.metadata),o=await this._sendMessageWithId(e,r,{message:s.message,fileInfos:a,separateFiles:s.separateFiles,replyToMessageId:s.replyToMessageId});return this._attachOptimisticMetadata(n,o)})();return{baseMessageId:r,messageIds:n,promise:a}}_normalizeFileArray(e){const t=Array.isArray(e)?e:[e];if(0===t.length)throw new Error("At least one file is required");if(t.length>20)throw new Error("A maximum of 20 files can be sent in one message");return t}async _uploadFiles(e,t,s={}){return Promise.all(t.map((t,i)=>this.uploadFile(e,t,{signal:s.signal,onProgress:s.onUploadProgress?e=>s.onUploadProgress({...e,fileIndex:i}):void 0})))}_applyFileMetadata(e,t){return null==t?e:e.map((e,s)=>{const i=Array.isArray(t)?t[s]:t;return null==i?e:{...e,metadata:i}})}async sendReply(e,t,s){return this.sendMessage(e,{message:t,replyToMessageId:s})}sendReplyOptimistic(e,t,s){return this.sendMessageOptimistic(e,{message:t,replyToMessageId:s})}async deleteMessage(e,t,s="ALL"){return this.apiClient.post(`/api/v1/chat-messages/${e}/messages/${t}/delete`,{deleteType:s})}async editMessage(e,t,s){return this.apiClient.put(`/api/v1/chat-messages/${e}/messages/${t}`,{message:s})}_hasTextMessage(e){return"string"==typeof e&&e.trim().length>0}_classifyFileType(e){const t=(e||"").toLowerCase();return t.startsWith("image/")?"IMAGE":t.startsWith("video/")?"VIDEO":t.startsWith("audio/")?"AUDIO":t.includes("pdf")||t.includes("document")?"DOCUMENT":"FILE"}_predictGroupCount(e,t){if(!e||0===e.length)return 0;if(t)return e.length;const s=new Set;for(const t of e){const e=t&&(t.type||t.fileType)||"";s.add(this._classifyFileType(e))}return s.size}_predictMessageIds(e,t,s){const i=(s?1:0)+t;if(0===i)return[];if(1===i)return[e];const r=[];s&&r.push(e);for(let s=0;s<t;s++)r.push(`${e}#${s}`);return r}_predictMessageIdsFromData(e,t){const s=t&&t.fileInfos||[],i=!t||!1!==t.separateFiles,r=this._predictGroupCount(s,i),n=this._hasTextMessage(t&&t.message);return this._predictMessageIds(e,r,n)}_attachOptimisticMetadata(e,t){const s=t&&Array.isArray(t.messages)?t.messages.map(e=>e&&e.messageId||null):[],i=s.length===e.length&&e.every((e,t)=>e===s[t]);return i||this.logger.warn("Optimistic messageId prediction mismatch — server may have changed grouping rules. Use result.optimistic.actualMessageIds to reconcile.",{predicted:e,actual:s}),{...t,optimistic:{predictedMessageIds:e,actualMessageIds:s,predictionMatched:i}}}_generateMessageId(){return"undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}_unwrapSuccessResponse(e){return e&&"object"==typeof e&&Object.prototype.hasOwnProperty.call(e,"data")?e.data:e}_normalizeMessageTimestamps(e){return e&&"object"==typeof e?(e.sentAt=this._toEpochMillis(e.sentAt),e.editedAt=this._toEpochMillis(e.editedAt),e.replyTo&&"object"==typeof e.replyTo&&(e.replyTo.sentAt=this._toEpochMillis(e.replyTo.sentAt)),e):e}_toEpochMillis(e){if("string"!=typeof e)return e;const t=Date.parse(e);return Number.isNaN(t)?e:t}async subscribeRoom(e){if(this.subscribedRooms.has(e))return void this.logger.warn(`Already subscribed to room: ${e}`);const t=l.getChatDestination(e);await this.connectionManager.subscribe(t,t=>{this._handleChatMessage(e,t)});const s=l.getChatReadDestination(e);await this.connectionManager.subscribe(s,t=>{this._handleReadEvent(e,t)});const i=l.getChatTypingDestination(e);await this.connectionManager.subscribe(i,t=>{this._handleTypingEvent(e,t)});const r=l.getChatAssistantStreamDestination(e);await this.connectionManager.subscribe(r,t=>{this._handleAssistantStreamEvent(e,t)});const n=t=>{t.roomId===e&&this.emit("memberJoined",{roomId:e,members:t.members||[],participantCount:t.activeParticipantCount,timestamp:t.timestamp})},a=t=>{t.roomId===e&&this.emit("memberLeft",{roomId:e,members:t.members||[],participantCount:t.activeParticipantCount,timestamp:t.timestamp})};this.on("roomListJoined",n),this.on("roomListLeft",a),this.subscribedRooms.set(e,{chatDestination:t,readDestination:s,typingDestination:i,assistantStreamDestination:r,memberJoinedHandler:n,memberLeftHandler:a,subscribedAt:new Date}),this.logger.info(`Subscribed to room: ${e}`),this.emit("roomSubscribed",{roomId:e})}unsubscribeRoom(e){const t=this.subscribedRooms.get(e);t?(this.connectionManager.unsubscribe(t.chatDestination),this.connectionManager.unsubscribe(t.readDestination),t.typingDestination&&this.connectionManager.unsubscribe(t.typingDestination),t.assistantStreamDestination&&this.connectionManager.unsubscribe(t.assistantStreamDestination),t.memberJoinedHandler&&this.off("roomListJoined",t.memberJoinedHandler),t.memberLeftHandler&&this.off("roomListLeft",t.memberLeftHandler),this._clearTypingTimer(e),this._clearAssistantTypingTimer(e),this._clearAssistantProgress(e,!1),this._activeRoomId===e&&(this._activeRoomId=null),this.subscribedRooms.delete(e),this._seenChatMessageIdsByRoom.delete(e),this._seenRoomListMessageIdsByRoom.delete(e),this.logger.info(`Unsubscribed from room: ${e}`),this.emit("roomUnsubscribed",{roomId:e})):this.logger.warn(`Not subscribed to room: ${e}`)}unsubscribeAllRooms(){this.subscribedRooms.forEach((e,t)=>{this.unsubscribeRoom(t)})}setActiveRoom(e){this._activeRoomId=e,this.logger.debug(`Active room set: ${e}`)}clearActiveRoom(){this.logger.debug(`Active room cleared (was: ${this._activeRoomId})`),this._activeRoomId=null}getActiveRoom(){return this._activeRoomId}async subscribeRoomList(){if(this.roomListSubscribed)return void this.logger.warn("Already subscribed to room list");const e=l.ROOM_LIST_USER_DESTINATION;await this.connectionManager.subscribe(e,e=>{this._handleRoomListEvent(e)}),this.roomListSubscribed=!0,this.logger.info("Subscribed to room list"),this.emit("roomListSubscribed",{})}unsubscribeRoomList(){this.roomListSubscribed?(this.connectionManager.unsubscribe(l.ROOM_LIST_USER_DESTINATION),this.roomListSubscribed=!1,this.logger.info("Unsubscribed from room list"),this.emit("roomListUnsubscribed",{})):this.logger.warn("Not subscribed to room list")}isRoomListSubscribed(){return this.roomListSubscribed}markAsRead(e,t){return this.connectionManager.isConnected()?this.connectionManager.send(l.CHAT_READ,{roomId:e,messageId:t}):(this.logger.warn("Cannot mark as read: not connected"),!1)}_handleChatMessage(e,t){if(this.logger.debug(`Message received in room ${e} (${t.eventType}):`,t),"MESSAGE_CREATED"===t.eventType&&this._shouldDedupMessage(this._seenChatMessageIdsByRoom,e,t.messageId))this.logger.debug(`Duplicate MESSAGE_CREATED suppressed: room=${e}, messageId=${t.messageId}`);else switch("MESSAGE_UPDATED"===t.eventType&&(void 0===t.translations&&(t.translations=null),void 0===t.sourceLang&&(t.sourceLang=null),void 0===t.translationsOf&&(t.translationsOf=null)),this.emit("message",{roomId:e,message:t}),t.eventType){case"MESSAGE_CREATED":"ASSISTANT"===t.senderType&&(this._clearAssistantTypingTimer(e),this.emit("typing",{roomId:e,userId:this._assistantTypingUserId,userName:this._assistantTypingUserName,typing:!1,senderType:"ASSISTANT"}),this._clearAssistantProgress(e,!0,"message")),t.userId!==this.userId&&(this.emit("newMessage",{roomId:e,message:t}),this._activeRoomId===e&&t.messageId&&this.markAsRead(e,t.messageId));break;case"MESSAGE_UPDATED":this.emit("messageUpdated",{roomId:e,message:t});break;case"MESSAGE_DELETED":this.emit("messageDeleted",{roomId:e,message:t});break;case"REACTION_CHANGED":this.emit("reactionChanged",{roomId:e,message:t});break;case"LINK_PREVIEW_ATTACHED":this.emit("linkPreviewAttached",{roomId:e,message:t});break;case"MESSAGE_TRANSLATED":this.emit("messageTranslated",{roomId:e,message:t});break;default:this.logger.warn(`Unknown eventType "${t.eventType}" — falling back to legacy behavior`),t.userId!==this.userId&&(this.emit("newMessage",{roomId:e,message:t}),this._activeRoomId===e&&t.messageId&&this.markAsRead(e,t.messageId))}}_handleRoomListEvent(e){if(this.logger.debug("Room list event:",e),e.eventType===c.MESSAGE_RECEIVED&&e.messageId&&this._shouldDedupMessage(this._seenRoomListMessageIdsByRoom,e.roomId,e.messageId))this.logger.debug(`Duplicate roomList MESSAGE_RECEIVED suppressed: room=${e.roomId}, messageId=${e.messageId}`);else switch(this.emit("roomListUpdate",e),e.eventType){case c.MESSAGE_RECEIVED:this.emit("roomListMessage",e);break;case c.MESSAGE_DELETED:this.emit("roomListMessageDeleted",e);break;case c.MESSAGE_UPDATED:this.emit("roomListMessageUpdated",e);break;case c.ROOM_CREATED:this.emit("roomListCreated",e);break;case c.ROOM_JOINED:this.emit("roomListJoined",e);break;case c.ROOM_LEFT:this.emit("roomListLeft",e),e.actorId===this.userId&&this.emit("roomListSelfLeft",e);break;case c.ROOM_KICKED:this.emit("roomListKicked",e),this._isSelfInMembers(e)&&this.emit("roomListSelfKicked",e);break;case c.ROOM_BANNED:this.emit("roomListBanned",e),this._isSelfInMembers(e)&&this.emit("roomListSelfBanned",e);break;case c.ROOM_UPDATED:this.emit("roomListRoomUpdated",e);break;case c.MESSAGE_RETENTION_CLEANUP:this.emit("retentionCleanup",{roomId:e.roomId,cutoffTime:e.cutoffTime});break;default:this.logger.warn("Unknown room list event type:",e.eventType)}}_handleReadEvent(e,t){this.logger.debug(`Read event in room ${e}:`,t);const s=t.roomId||e;t.events&&Array.isArray(t.events)?t.events.forEach(e=>{this.emit("messageRead",{roomId:s,messageId:e.messageId,userId:t.userId,remainingUnreadCount:e.remainingUnreadCount})}):this.emit("messageRead",{roomId:s,messageId:t.messageId,userId:t.userId,remainingUnreadCount:t.remainingUnreadCount})}startTyping(e){if(!this.connectionManager.isConnected())return;const t=this._typingTimers.get(e);t?clearTimeout(t):this.connectionManager.send(l.CHAT_TYPING,{roomId:e,typing:!0});const s=setTimeout(()=>{this.stopTyping(e)},3e3);this._typingTimers.set(e,s)}stopTyping(e){this._clearTypingTimer(e),this.connectionManager.isConnected()&&this.connectionManager.send(l.CHAT_TYPING,{roomId:e,typing:!1})}_isSelfInMembers(e){return Array.isArray(e.members)&&e.members.some(e=>e&&e.userId===this.userId)}_handleTypingEvent(e,t){"ASSISTANT"!==t.senderType&&t.userId===this.userId||("ASSISTANT"===t.senderType&&!0===t.typing&&this._startAssistantTypingTimer(e),this.emit("typing",{roomId:e,userId:t.userId,userName:t.userName,typing:t.typing,senderType:t.senderType||"USER"}))}_startAssistantTypingTimer(e){this._clearAssistantTypingTimer(e);const t=setTimeout(()=>{this._assistantTypingTimers.delete(e),this.emit("typing",{roomId:e,userId:this._assistantTypingUserId,userName:this._assistantTypingUserName,typing:!1,senderType:"ASSISTANT"}),this.logger.debug(`Assistant typing auto-cleared (timeout): room=${e}`)},this._assistantTypingTimeoutMs);this._assistantTypingTimers.set(e,t)}_clearAssistantTypingTimer(e){const t=this._assistantTypingTimers.get(e);t&&(clearTimeout(t),this._assistantTypingTimers.delete(e))}_clearTypingTimer(e){const t=this._typingTimers.get(e);t&&(clearTimeout(t),this._typingTimers.delete(e))}_handleAssistantStreamEvent(e,t){if(t&&t.type){if("PHASE"===t.type){const s=t.phase||null,i=s&&this._assistantPhaseLabels[s]||null,r=this._assistantProgress.get(e),n=r&&r.streamId===t.streamId&&r.text||"";return this._startAssistantProgressTimer(e,t,{phase:s,text:n}),void this.emit("assistantProgress",{roomId:e,streamId:t.streamId||null,personaId:t.personaId||null,phase:s,label:i,active:!0,text:n||null})}if("DELTA"===t.type){const s=this._assistantProgress.get(e);if(s?.streamId&&t.streamId&&s.streamId!==t.streamId)return void this.logger.debug(`Stale assistant DELTA ignored: room=${e}, delta=${t.streamId}, active=${s.streamId}`);const i=s&&s.phase||"WRITING",r=(s&&s.text||"")+(t.delta||"");return this._startAssistantProgressTimer(e,t,{phase:i,text:r}),void this.emit("assistantProgress",{roomId:e,streamId:t.streamId||null,personaId:t.personaId||null,phase:i,label:this._assistantPhaseLabels[i]||null,active:!0,text:r,delta:t.delta||null,seq:"number"==typeof t.seq?t.seq:null})}if("DONE"===t.type){const s=this._assistantProgress.get(e);if(s?.streamId&&t.streamId&&s.streamId!==t.streamId)return void this.logger.debug(`Stale assistant DONE ignored: room=${e}, done=${t.streamId}, active=${s.streamId}`);s&&s.timer&&clearTimeout(s.timer),this._assistantProgress.delete(e),this.emit("assistantProgress",{roomId:e,streamId:t.streamId||null,personaId:t.personaId||null,phase:null,label:null,active:!1,status:t.status||null,messageId:t.messageId||null})}}}_startAssistantProgressTimer(e,t,s){const i=this._assistantProgress.get(e);i&&i.timer&&clearTimeout(i.timer);let r=this._assistantProgressFallbackTtlMs;"number"==typeof t.expiresAt&&(r=Math.min(12e4,Math.max(5e3,t.expiresAt-Date.now())));const n=setTimeout(()=>{this._clearAssistantProgress(e,!0,"timeout"),this.logger.debug(`Assistant progress auto-cleared (timeout): room=${e}`)},r);this._assistantProgress.set(e,{streamId:t.streamId||null,personaId:t.personaId||null,phase:s.phase,text:s.text||"",timer:n})}_clearAssistantProgress(e,t,s){const i=this._assistantProgress.get(e);i&&(i.timer&&clearTimeout(i.timer),this._assistantProgress.delete(e),t&&this.emit("assistantProgress",{roomId:e,streamId:i.streamId||null,personaId:i.personaId||null,phase:null,label:null,active:!1,reason:s||null}))}getSubscribedRooms(){return Array.from(this.subscribedRooms.keys())}isSubscribed(e){return this.subscribedRooms.has(e)}setLogLevel(e){this.logger.setLevel(e)}destroy(){this.unsubscribeAllRooms(),this.roomListSubscribed&&this.unsubscribeRoomList(),this._typingTimers.forEach(e=>clearTimeout(e)),this._typingTimers.clear(),this._assistantTypingTimers.forEach(e=>clearTimeout(e)),this._assistantTypingTimers.clear(),this._assistantProgress.forEach(e=>e.timer&&clearTimeout(e.timer)),this._assistantProgress.clear(),this._seenChatMessageIdsByRoom.clear(),this._seenRoomListMessageIdsByRoom.clear(),this.removeAllListeners(),this.logger.info("ChatClient destroyed")}}class y extends i{constructor(e={}){super(),this.localStream=null,this.screenStream=null,this.videoEnabled=!0,this.audioEnabled=!0,this.logger=new m(e.logLevel||p.WARN,"MediaStreamManager"),this._deviceChangeHandler=null}async getUserMedia(e={video:!0,audio:!0}){try{return this.localStream=await navigator.mediaDevices.getUserMedia(e),this.videoEnabled=!1!==e.video,this.audioEnabled=!1!==e.audio,this.emit("streamStarted",{stream:this.localStream}),this.logger.info("User media stream started"),this.localStream}catch(e){throw this.logger.error("Failed to get user media:",e),this.emit("error",{type:n.MEDIA_ACCESS_DENIED,message:this._getMediaErrorMessage(e),error:e}),e}}_getMediaErrorMessage(e){switch(e.name){case"NotAllowedError":return"Camera/Microphone permission denied";case"NotFoundError":return"Camera/Microphone not found";case"NotReadableError":return"Camera/Microphone is already in use";case"OverconstrainedError":return"Camera/Microphone constraints cannot be satisfied";case"AbortError":return"Media access aborted";default:return e.message||"Unknown media error"}}async getDisplayMedia(e={video:!0,audio:!1}){try{return this.screenStream=await navigator.mediaDevices.getDisplayMedia(e),this.screenStream.getVideoTracks()[0].onended=()=>{this.emit("screenShareEnded",{}),this.screenStream=null},this.emit("screenShareStarted",{stream:this.screenStream}),this.logger.info("Screen share stream started"),this.screenStream}catch(e){throw this.logger.error("Failed to get display media:",e),this.emit("error",{type:n.SCREEN_SHARE_DENIED,message:"NotAllowedError"===e.name?"Screen share permission denied":e.message,error:e}),e}}toggleVideo(){if(!this.localStream)return this.logger.warn("No local stream available"),this.videoEnabled;const e=this.localStream.getVideoTracks();return 0===e.length?(this.logger.warn("No video track available"),this.videoEnabled):(this.videoEnabled=!this.videoEnabled,e.forEach(e=>{e.enabled=this.videoEnabled}),this.emit("videoToggled",{enabled:this.videoEnabled}),this.logger.debug(`Video toggled: ${this.videoEnabled}`),this.videoEnabled)}toggleAudio(){if(!this.localStream)return this.logger.warn("No local stream available"),this.audioEnabled;const e=this.localStream.getAudioTracks();return 0===e.length?(this.logger.warn("No audio track available"),this.audioEnabled):(this.audioEnabled=!this.audioEnabled,e.forEach(e=>{e.enabled=this.audioEnabled}),this.emit("audioToggled",{enabled:this.audioEnabled}),this.logger.debug(`Audio toggled: ${this.audioEnabled}`),this.audioEnabled)}setVideoEnabled(e){if(!this.localStream)return void this.logger.warn("No local stream available");this.localStream.getVideoTracks().forEach(t=>{t.enabled=e}),this.videoEnabled=e,this.emit("videoToggled",{enabled:e})}setAudioEnabled(e){if(!this.localStream)return void this.logger.warn("No local stream available");this.localStream.getAudioTracks().forEach(t=>{t.enabled=e}),this.audioEnabled=e,this.emit("audioToggled",{enabled:e})}getLocalStream(){return this.localStream}getScreenStream(){return this.screenStream}isVideoEnabled(){return this.videoEnabled}isAudioEnabled(){return this.audioEnabled}stopAll(){this.localStream&&(this.localStream.getTracks().forEach(e=>e.stop()),this.localStream=null,this.emit("streamStopped",{}),this.logger.info("All media tracks stopped")),this.screenStream&&(this.screenStream.getTracks().forEach(e=>e.stop()),this.screenStream=null)}replaceTrack(e,t){this.localStream?(this.localStream.removeTrack(e),this.localStream.addTrack(t),e.stop(),this.emit("trackReplaced",{oldTrack:e,newTrack:t}),this.logger.debug(`Track replaced: ${e.kind}`)):this.logger.warn("No local stream available")}getTrack(e){if(!this.localStream)return null;const t="video"===e?this.localStream.getVideoTracks():this.localStream.getAudioTracks();return t.length>0?t[0]:null}async getDevices(){try{const e=await navigator.mediaDevices.enumerateDevices();return{videoInputs:e.filter(e=>"videoinput"===e.kind),audioInputs:e.filter(e=>"audioinput"===e.kind),audioOutputs:e.filter(e=>"audiooutput"===e.kind)}}catch(e){throw this.logger.error("Failed to enumerate devices:",e),this.emit("error",{type:n.ENUMERATE_DEVICES_FAILED,message:e.message,error:e}),e}}async switchDevice(e,t){if(!this.localStream)throw new Error("No local stream available");try{const s="video"===t?{video:{deviceId:{exact:e}},audio:!1}:{video:!1,audio:{deviceId:{exact:e}}},i=(await navigator.mediaDevices.getUserMedia(s)).getTracks()[0],r="video"===t?this.localStream.getVideoTracks()[0]:this.localStream.getAudioTracks()[0];return r&&(this.replaceTrack(r,i),this.emit("deviceSwitched",{kind:t,deviceId:e,newTrack:i}),this.logger.info(`${t} device switched to ${e}`)),i}catch(e){throw this.logger.error(`Failed to switch ${t} device:`,e),this.emit("error",{type:n.DEVICE_SWITCH_FAILED,message:e.message,error:e}),e}}startDeviceChangeDetection(){this._deviceChangeHandler||(this._deviceChangeHandler=async()=>{try{const e=await this.getDevices();this.emit("deviceChange",e),this.logger.debug("Device change detected")}catch(e){this.logger.warn("Failed to get devices on change:",e)}},navigator.mediaDevices.addEventListener("devicechange",this._deviceChangeHandler),this.logger.debug("Device change detection started"))}stopDeviceChangeDetection(){this._deviceChangeHandler&&(navigator.mediaDevices.removeEventListener("devicechange",this._deviceChangeHandler),this._deviceChangeHandler=null,this.logger.debug("Device change detection stopped"))}getStreamInfo(){return this.localStream?{hasStream:!0,videoEnabled:this.videoEnabled,audioEnabled:this.audioEnabled,tracks:this.localStream.getTracks().map(e=>({kind:e.kind,id:e.id,label:e.label,enabled:e.enabled,readyState:e.readyState,muted:e.muted}))}:{hasStream:!1,videoEnabled:this.videoEnabled,audioEnabled:this.audioEnabled,tracks:[]}}async applyVideoConstraints(e){if(!this.localStream)throw new Error("No local stream available");const t=this.localStream.getVideoTracks()[0];if(!t)throw new Error("No video track available");try{await t.applyConstraints(e),this.emit("videoConstraintsApplied",{constraints:e}),this.logger.info("Video constraints applied:",e)}catch(e){throw this.logger.error("Failed to apply video constraints:",e),e}}getVideoSettings(){if(!this.localStream)return null;const e=this.localStream.getVideoTracks()[0];return e?e.getSettings():null}getAudioSettings(){if(!this.localStream)return null;const e=this.localStream.getAudioTracks()[0];return e?e.getSettings():null}setLogLevel(e){this.logger.setLevel(e)}destroy(){this.stopAll(),this.stopDeviceChangeDetection(),this.removeAllListeners(),this.logger.info("MediaStreamManager destroyed")}}class w extends i{constructor(e={}){super(),this.iceServers=e.iceServers||g.iceServers,this.logger=new m(e.logLevel||p.WARN,"PeerConnectionManager"),this.peers=new Map,this.localStream=null}setLocalStream(e){this.localStream=e,this.peers.forEach(e=>{this._addTracksToConnection(e.connection)})}setIceServers(e){this.iceServers=e}createPeerConnection(e,t=!1,s={}){if(this.peers.has(e))return this.logger.warn(`Peer connection already exists: ${e}`),this.peers.get(e).connection;const i={iceServers:this.iceServers,iceCandidatePoolSize:10},r=new RTCPeerConnection(i),n={connection:r,polite:t,makingOffer:!1,ignoreOffer:!1,isSettingRemoteAnswerPending:!1,skipAutoNegotiation:s.skipAutoNegotiation||!1};return this.peers.set(e,n),this._setupConnectionHandlers(e,r,n),this.localStream&&this._addTracksToConnection(r),this.logger.info(`Peer connection created: ${e} (polite: ${t}, skipAutoNegotiation: ${n.skipAutoNegotiation})`),r}_setupConnectionHandlers(e,t,s){t.onicecandidate=t=>{t.candidate&&this.emit("iceCandidate",{peerId:e,candidate:t.candidate})},t.oniceconnectionstatechange=()=>{this.logger.debug(`ICE connection state (${e}): ${t.iceConnectionState}`),this.emit("iceConnectionStateChange",{peerId:e,state:t.iceConnectionState}),"failed"===t.iceConnectionState&&this.emit("error",{type:n.ICE_CONNECTION_FAILED,peerId:e,message:"ICE connection failed"})},t.onconnectionstatechange=()=>{this.logger.debug(`Connection state (${e}): ${t.connectionState}`),this.emit("connectionStateChange",{peerId:e,state:t.connectionState}),"connected"===t.connectionState?this.emit("peerConnected",{peerId:e}):"disconnected"!==t.connectionState&&"failed"!==t.connectionState||this.emit("peerDisconnected",{peerId:e})},t.onnegotiationneeded=async()=>{if(s.skipAutoNegotiation)this.logger.debug(`Skipping auto negotiation for ${e} (manual offer will be sent)`);else try{s.makingOffer=!0,await t.setLocalDescription(),this.emit("negotiationNeeded",{peerId:e,description:t.localDescription})}catch(t){this.logger.error(`Negotiation error (${e}):`,t)}finally{s.makingOffer=!1}},t.ontrack=t=>{this.logger.info(`Remote track received from ${e}:`,t.track.kind),this.emit("remoteTrack",{peerId:e,track:t.track,streams:t.streams})},t.ondatachannel=t=>{this.logger.info(`Data channel received from ${e}`),this.emit("dataChannel",{peerId:e,channel:t.channel})}}_addTracksToConnection(e){if(!this.localStream)return;const t=e.getSenders();this.localStream.getTracks().forEach(s=>{t.find(e=>e.track&&e.track.kind===s.kind)||e.addTrack(s,this.localStream)})}async createOffer(e){const t=this.peers.get(e);if(!t)throw new Error(`Peer not found: ${e}`);try{const s=await t.connection.createOffer();return await t.connection.setLocalDescription(s),this.logger.debug(`Offer created for ${e}`),t.connection.localDescription}catch(t){throw this.logger.error(`Failed to create offer for ${e}:`,t),t}}async createAnswer(e){const t=this.peers.get(e);if(!t)throw new Error(`Peer not found: ${e}`);try{const s=await t.connection.createAnswer();return await t.connection.setLocalDescription(s),this.logger.debug(`Answer created for ${e}`),t.connection.localDescription}catch(t){throw this.logger.error(`Failed to create answer for ${e}:`,t),t}}async handleRemoteDescription(e,t){const s=this.peers.get(e);if(!s)throw new Error(`Peer not found: ${e}`);const{connection:i,polite:r,makingOffer:n}=s,a="offer"===t.type&&(n||"stable"!==i.signalingState);if(s.ignoreOffer=!r&&a,s.ignoreOffer)this.logger.debug(`Ignoring offer collision from ${e} (I am impolite)`);else try{if(a&&r&&(this.logger.debug(`Offer collision detected, rolling back local offer for ${e} (I am polite)`),await i.setLocalDescription({type:"rollback"})),s.isSettingRemoteAnswerPending="answer"===t.type,await i.setRemoteDescription(t),s.isSettingRemoteAnswerPending=!1,this.logger.debug(`Remote ${t.type} set for ${e}`),"offer"===t.type){const t=await this.createAnswer(e);this.emit("answerCreated",{peerId:e,answer:t})}}catch(t){throw this.logger.error(`Failed to set remote description for ${e}:`,t),t}}async addIceCandidate(e,t){const s=this.peers.get(e);if(!s)return this.logger.warn(`Peer not found for ICE candidate: ${e}`),!1;if(!s.connection.remoteDescription)return this.logger.debug(`Remote description not set yet for ${e}, ICE candidate queued`),!1;try{return await s.connection.addIceCandidate(t),this.logger.debug(`ICE candidate added for ${e}`),!0}catch(t){return s.ignoreOffer||this.logger.error(`Failed to add ICE candidate for ${e}:`,t),!1}}getPeerConnection(e){const t=this.peers.get(e);return t?t.connection:null}getPeerIds(){return Array.from(this.peers.keys())}closePeerConnection(e){const t=this.peers.get(e);t&&(t.connection.close(),this.peers.delete(e),this.emit("peerClosed",{peerId:e}),this.logger.info(`Peer connection closed: ${e}`))}closeAllPeerConnections(){this.peers.forEach((e,t)=>{e.connection.close(),this.logger.debug(`Peer connection closed: ${t}`)}),this.peers.clear(),this.emit("allPeersClosed",{})}async replaceTrack(e,t){const s=[];this.peers.forEach((i,r)=>{const n=i.connection.getSenders().find(t=>t.track&&t.track.kind===e.kind);n&&s.push(n.replaceTrack(t).then(()=>this.logger.debug(`Track replaced for ${r}`)).catch(e=>this.logger.error(`Failed to replace track for ${r}:`,e)))}),await Promise.all(s)}async getStats(e){const t=this.peers.get(e);return t?t.connection.getStats():null}getConnectionSummary(){const e={};return this.peers.forEach((t,s)=>{e[s]={connectionState:t.connection.connectionState,iceConnectionState:t.connection.iceConnectionState,signalingState:t.connection.signalingState,polite:t.polite}}),e}setLogLevel(e){this.logger.setLevel(e)}destroy(){this.closeAllPeerConnections(),this.localStream=null,this.removeAllListeners(),this.logger.info("PeerConnectionManager destroyed")}}class R extends i{constructor(e){super(),this.connectionManager=e.connectionManager,this.apiClient=e.apiClient,this.userId=e.userId,this.logLevel=e.logLevel||p.WARN,this.logger=new m(this.logLevel,"WebRTCClient"),this.mediaManager=new y({logLevel:this.logLevel}),this.peerManager=new w({iceServers:e.iceServers||g.iceServers,logLevel:this.logLevel}),this.currentRoom=null,this.isGroupCall=!1,this.participants=new Map,this._iceServersFetched=!1,this._waitingForCallAccept=!1,this._pendingCallTarget=null,this._pendingIceCandidates=new Map,this._setupEventHandlers()}async initializeIceServers(){if(!this._iceServersFetched)try{const e=await this.getTurnCredentials();e&&e.iceServers&&(this.peerManager.setIceServers(e.iceServers),this._iceServersFetched=!0,this.logger.info("ICE servers fetched successfully"))}catch(e){this.logger.warn("Failed to fetch TURN credentials, using fallback ICE servers:",e.message),this._setFallbackIceServers()}}_setFallbackIceServers(){this.peerManager.setIceServers([{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}])}_setupEventHandlers(){this.mediaManager.on("streamStarted",e=>this.emit("localStreamStarted",e)),this.mediaManager.on("streamStopped",e=>this.emit("localStreamStopped",e)),this.mediaManager.on("videoToggled",()=>this._sendMediaState()),this.mediaManager.on("audioToggled",()=>this._sendMediaState()),this.mediaManager.on("screenShareStarted",e=>this.emit("screenShareStarted",e)),this.mediaManager.on("screenShareEnded",e=>this.emit("screenShareEnded",e)),this.mediaManager.on("error",e=>this.emit("error",e)),this.peerManager.on("remoteTrack",e=>this.emit("remoteTrack",e)),this.peerManager.on("peerConnected",e=>this.emit("peerConnected",e)),this.peerManager.on("peerDisconnected",e=>this.emit("peerDisconnected",e)),this.peerManager.on("peerClosed",e=>this.emit("peerClosed",e)),this.peerManager.on("error",e=>this.emit("error",e)),this.peerManager.on("iceCandidate",({peerId:e,candidate:t})=>{this._sendSignal({type:a.ICE_CANDIDATE,receiverId:e,data:{candidate:t}})}),this.peerManager.on("negotiationNeeded",({peerId:e,description:t})=>{this._sendSignal({type:a.CALL_OFFER,receiverId:e,data:{sdp:t}})}),this.peerManager.on("answerCreated",({peerId:e,answer:t})=>{this._sendSignal({type:a.CALL_ANSWER,receiverId:e,data:{sdp:t}})})}async getTurnCredentials(){return(await this.apiClient.get("/api/v1/webrtc/turn-credentials")).data}async createCallRoom(e){return this.apiClient.post("/api/v1/webrtc/rooms",e)}async getCallRoom(e){return this.apiClient.get(`/api/v1/webrtc/rooms/${e}`)}async joinCallRoomApi(e){return this.apiClient.post(`/api/v1/webrtc/rooms/${e}/join`)}async leaveCallRoomApi(e){return this.apiClient.delete(`/api/v1/webrtc/rooms/${e}/leave`)}async enableIncomingCalls(){this._incomingCallsEnabled?this.logger.debug("Incoming calls already enabled"):(await this._subscribeToDirectSignaling(),this._incomingCallsEnabled=!0,this.logger.info("Incoming calls enabled - now listening for call invitations"))}disableIncomingCalls(){if(!this._incomingCallsEnabled)return;const e=l.getWebRTCUserDestination();this.connectionManager.unsubscribe(e),this._incomingCallsEnabled=!1,this.logger.info("Incoming calls disabled")}isIncomingCallsEnabled(){return this._incomingCallsEnabled||!1}async startCall(e){const{roomId:t,isGroup:s=!1,mediaConstraints:i={video:!0,audio:!0}}=e;try{await this.initializeIceServers();const e=await this.mediaManager.getUserMedia(i);return this.peerManager.setLocalStream(e),this.currentRoom=t,this.isGroupCall=s,await this._subscribeToSignaling(t,s),this._sendSignal({type:a.JOIN_ROOM,roomId:t}),this.logger.info(`Call started in room: ${t}`),this.emit("callStarted",{roomId:t,isGroup:s,localStream:e}),e}catch(e){throw this.logger.error("Failed to start call:",e),this.emit("error",{type:n.PEER_CONNECTION_FAILED,message:e.message,error:e}),e}}async callUser(e,t={video:!0,audio:!0}){try{await this.initializeIceServers();const s=await this.mediaManager.getUserMedia(t);this.peerManager.setLocalStream(s),await this._subscribeToDirectSignaling(),this._waitingForCallAccept=!0,this._pendingCallTarget=e,this._sendSignal({type:a.CALL_REQUEST,receiverId:e}),this.currentRoom=null,this.isGroupCall=!1,this.emit("callRequested",{targetUserId:e,localStream:s})}catch(e){throw this.logger.error("Failed to call user:",e),this._waitingForCallAccept=!1,this._pendingCallTarget=null,e}}async acceptCall(e,t={video:!0,audio:!0}){try{await this.initializeIceServers();const s=await this.mediaManager.getUserMedia(t);this.peerManager.setLocalStream(s),this.peerManager.createPeerConnection(e,!0,{skipAutoNegotiation:!0}),this._sendSignal({type:a.CALL_ACCEPT,receiverId:e}),this.emit("callAccepted",{callerId:e})}catch(e){throw this.logger.error("Failed to accept call:",e),e}}rejectCall(e){this._sendSignal({type:a.CALL_REJECT,receiverId:e}),this.emit("callRejected",{callerId:e})}cancelCall(){this._waitingForCallAccept&&this._pendingCallTarget&&(this._sendSignal({type:a.CALL_CANCEL,receiverId:this._pendingCallTarget}),this.emit("callCancelled",{targetUserId:this._pendingCallTarget}),this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.mediaManager.stopAll())}endCall(){this._waitingForCallAccept&&this._pendingCallTarget&&this._sendSignal({type:a.CALL_CANCEL,receiverId:this._pendingCallTarget}),this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.currentRoom&&(this._sendSignal({type:a.LEAVE_ROOM,roomId:this.currentRoom}),this._unsubscribeFromSignaling()),this.peerManager.getPeerIds().forEach(e=>{this._sendSignal({type:a.CALL_END,receiverId:e})}),this.peerManager.closeAllPeerConnections(),this.mediaManager.stopAll(),this.participants.clear(),this._pendingIceCandidates.clear();const e=this.currentRoom;this.currentRoom=null,this.isGroupCall=!1,this.emit("callEnded",{roomId:e}),this.logger.info("Call ended")}toggleVideo(){return this.mediaManager.toggleVideo()}toggleAudio(){return this.mediaManager.toggleAudio()}async startScreenShare(){const e=await this.mediaManager.getDisplayMedia(),t=e.getVideoTracks()[0],s=this.mediaManager.getTrack("video");return s&&t&&await this.peerManager.replaceTrack(s,t),t.onended=async()=>{const e=this.mediaManager.getTrack("video");e&&await this.peerManager.replaceTrack(t,e),this.emit("screenShareEnded",{})},e}stopScreenShare(){const e=this.mediaManager.getScreenStream();e&&e.getTracks().forEach(e=>e.stop())}getLocalStream(){return this.mediaManager.getLocalStream()}async getDevices(){return this.mediaManager.getDevices()}async switchDevice(e,t){const s=this.mediaManager.getTrack(t),i=await this.mediaManager.switchDevice(e,t);return s&&await this.peerManager.replaceTrack(s,i),i}setVideoEnabled(e){this.mediaManager.setVideoEnabled(e),this._sendMediaState()}setAudioEnabled(e){this.mediaManager.setAudioEnabled(e),this._sendMediaState()}startDeviceChangeDetection(){this.mediaManager.startDeviceChangeDetection(),this.mediaManager.on("deviceChange",e=>{this.emit("deviceChange",e)})}stopDeviceChangeDetection(){this.mediaManager.stopDeviceChangeDetection()}async applyVideoConstraints(e){return this.mediaManager.applyVideoConstraints(e)}getVideoSettings(){return this.mediaManager.getVideoSettings()}getAudioSettings(){return this.mediaManager.getAudioSettings()}async _subscribeToSignaling(e,t){if(t){const t=l.getWebRTCDestination(e);await this.connectionManager.subscribe(t,e=>{this._handleSignal(e)})}await this._subscribeToDirectSignaling()}async _subscribeToDirectSignaling(){const e=l.getWebRTCUserDestination();await this.connectionManager.subscribe(e,e=>{this._handleSignal(e)})}_unsubscribeFromSignaling(){if(this.currentRoom&&this.isGroupCall){const e=l.getWebRTCDestination(this.currentRoom);this.connectionManager.unsubscribe(e)}if(!this._incomingCallsEnabled){const e=l.getWebRTCUserDestination();this.connectionManager.unsubscribe(e)}}_sendSignal(e){const t={type:e.type,roomId:e.roomId||this.currentRoom,receiverId:e.receiverId,data:e.data};this.connectionManager.send(l.WEBRTC_SIGNAL,t),this.logger.debug("Signal sent:",t)}_sendMediaState(){if(!this.currentRoom&&0===this.peerManager.getPeerIds().length)return;const e={videoEnabled:this.mediaManager.isVideoEnabled(),audioEnabled:this.mediaManager.isAudioEnabled()};this._sendSignal({type:a.VIDEO_STATE_CHANGED,data:e}),this.emit("mediaStateChanged",e)}async _handleSignal(e){const{type:t,senderId:s,data:i}=e;if(s!==this.userId)switch(this.logger.debug(`Signal received: ${t} from ${s}`),t){case a.JOIN_ROOM:case a.PEER_JOINED:await this._handleUserJoined(s);break;case a.LEAVE_ROOM:case a.PEER_LEFT:this._handleUserLeft(s);break;case a.CALL_OFFER:await this._handleOffer(s,i.sdp);break;case a.CALL_ANSWER:await this._handleAnswer(s,i.sdp);break;case a.ICE_CANDIDATE:await this._handleIceCandidate(s,i.candidate);break;case a.VIDEO_STATE_CHANGED:case a.AUDIO_STATE_CHANGED:this._handleMediaState(s,i);break;case a.CALL_REQUEST:this.isInCall()||this._waitingForCallAccept?(this._sendSignal({type:a.CALL_BUSY,receiverId:s}),this.emit("incomingCallWhileBusy",{callerId:s})):this.emit("incomingCall",{callerId:s});break;case a.CALL_BUSY:this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.mediaManager.stopAll(),this.emit("callBusy",{userId:s});break;case a.CALL_INVITATION:this.emit("callInvitation",{callRoomId:i?.callRoomId||e.roomId,title:i?.title,hostUserId:i?.hostUserId||s,maxParticipants:i?.maxParticipants,createdAt:i?.createdAt});break;case a.CALL_ACCEPT:await this._handleCallAccepted(s);break;case a.CALL_REJECT:this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.mediaManager.stopAll(),this.emit("callRejected",{userId:s});break;case a.CALL_CANCEL:this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.emit("callCancelled",{userId:s});break;case a.CALL_END:this._handleCallEnded(s);break;default:this.logger.warn(`Unknown signal type: ${t}`)}}async _handleUserJoined(e){this.participants.set(e,{joinedAt:new Date});const t=this.userId<e;this.peerManager.createPeerConnection(e,t),this.emit("userJoined",{userId:e}),this.logger.info(`User joined: ${e} (I am ${t?"polite":"impolite"})`)}_handleUserLeft(e){this.participants.delete(e),this.peerManager.closePeerConnection(e),this.emit("userLeft",{userId:e}),this.logger.info(`User left: ${e}`)}async _handleOffer(e,t){if(!this.peerManager.getPeerConnection(e)){const t=this.userId<e;this.peerManager.createPeerConnection(e,t,{skipAutoNegotiation:!0})}await this.peerManager.handleRemoteDescription(e,t),await this._processPendingIceCandidates(e)}async _handleAnswer(e,t){await this.peerManager.handleRemoteDescription(e,t),await this._processPendingIceCandidates(e)}async _handleIceCandidate(e,t){if(!this.peerManager.getPeerConnection(e))return void this._queueIceCandidate(e,t);await this.peerManager.addIceCandidate(e,new RTCIceCandidate(t))||this._queueIceCandidate(e,t)}_queueIceCandidate(e,t){this._pendingIceCandidates.has(e)||this._pendingIceCandidates.set(e,[]),this._pendingIceCandidates.get(e).push(t),this.logger.debug(`ICE candidate queued for ${e}`)}async _processPendingIceCandidates(e){const t=this._pendingIceCandidates.get(e);if(t&&0!==t.length){this.logger.debug(`Processing ${t.length} pending ICE candidates for ${e}`);for(const s of t)await this.peerManager.addIceCandidate(e,new RTCIceCandidate(s));this._pendingIceCandidates.delete(e)}}_handleMediaState(e,t){this.emit("participantMediaState",{userId:e,videoEnabled:t.videoEnabled,audioEnabled:t.audioEnabled})}async _handleCallAccepted(e){this._waitingForCallAccept=!1,this._pendingCallTarget=null,this.peerManager.createPeerConnection(e,!1,{skipAutoNegotiation:!0});const t=await this.peerManager.createOffer(e);this._sendSignal({type:a.CALL_OFFER,receiverId:e,data:{sdp:t}}),this.emit("callAccepted",{userId:e})}_handleCallEnded(e){this.peerManager.closePeerConnection(e),this.participants.delete(e),this.emit("participantLeft",{userId:e}),0===this.peerManager.getPeerIds().length&&this.endCall()}isInCall(){return null!==this.currentRoom||this.peerManager.getPeerIds().length>0}getCurrentRoom(){return this.currentRoom}getParticipants(){return Array.from(this.participants.keys())}getConnectionSummary(){return this.peerManager.getConnectionSummary()}async getStats(e){return this.peerManager.getStats(e)}getMediaState(){return{videoEnabled:this.mediaManager.isVideoEnabled(),audioEnabled:this.mediaManager.isAudioEnabled(),streamInfo:this.mediaManager.getStreamInfo()}}setLogLevel(e){this.logger.setLevel(e),this.mediaManager.setLogLevel(e),this.peerManager.setLogLevel(e)}destroy(){this.endCall(),this.mediaManager.destroy(),this.peerManager.destroy(),this.removeAllListeners(),this.logger.info("WebRTCClient destroyed")}}const A=Object.freeze({UNSUPPORTED_BROWSER:"UNSUPPORTED_BROWSER",FIREBASE_NOT_INSTALLED:"FIREBASE_NOT_INSTALLED",SW_REGISTER_FAILED:"SW_REGISTER_FAILED",PERMISSION_DENIED:"PERMISSION_DENIED",TOKEN_FAILED:"TOKEN_FAILED",SERVER_REGISTER_FAILED:"SERVER_REGISTER_FAILED"});class M extends Error{constructor(e,t,s){super(t),this.name="PushError",this.code=e,void 0!==s&&(this.cause=s)}}const L={apiKey:"AIzaSyB8VhRg6rSvUI7K3Ua7h6sBpLvaQGmIkRc",authDomain:"chatting-c3e5d.firebaseapp.com",projectId:"chatting-c3e5d",storageBucket:"chatting-c3e5d.firebasestorage.app",messagingSenderId:"1020496565673",appId:"1:1020496565673:web:5039167257fd83f5ce20b8"},N="pendingNavigation",D="pending-room",O="deviceInfo",P="device-id",k="talkflow_device_id";function U(){return"undefined"==typeof indexedDB?Promise.resolve(null):new Promise((e,t)=>{const s=indexedDB.open("talkflowPush",2);s.onupgradeneeded=()=>{const e=s.result;e.objectStoreNames.contains(N)||e.createObjectStore(N,{keyPath:"id"}),e.objectStoreNames.contains(O)||e.createObjectStore(O,{keyPath:"id"})},s.onsuccess=()=>e(s.result),s.onerror=()=>t(s.error||new Error("IndexedDB open failed"))})}function $(){return U().then(e=>e?new Promise((t,s)=>{const i=e.transaction(O,"readonly"),r=i.objectStore(O).get(P);r.onsuccess=()=>{const e=r.result;t(e&&e.value?e.value:null)},r.onerror=()=>s(r.error||new Error("IndexedDB read failed")),i.oncomplete=()=>e.close(),i.onerror=()=>e.close(),i.onabort=()=>e.close()}):null)}function F(e){return U().then(t=>!!t&&new Promise(s=>{try{const i=t.transaction(O,"readwrite");i.objectStore(O).put({id:P,value:e}),i.oncomplete=()=>{t.close(),s(!0)},i.onerror=()=>{t.close(),s(!1)},i.onabort=()=>{t.close(),s(!1)}}catch(e){try{t.close()}catch(e){}s(!1)}})).catch(()=>!1)}function W(){return"web-"+("undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():Date.now().toString(36)+Math.random().toString(36).substring(2))}async function j(e){const t=await U();if(!t)return null;const s=function(e){return e&&"string"==typeof e&&""!==e.trim()?"pending-room:"+e:D}(e),i=!!e&&s!==D;return new Promise((r,n)=>{const a=t.transaction(N,"readwrite"),o=a.objectStore(N);let c=null;const l=o.get(s);l.onsuccess=()=>{const t=l.result;if(t&&t.roomId){if((t.projectId||null)===(e||null))return c=t.roomId,void o.delete(s)}if(!i)return;const r=o.get(D);r.onsuccess=()=>{const e=r.result;e&&e.roomId&&!e.projectId&&(c=e.roomId,o.delete(D))}},l.onerror=()=>{n(l.error||new Error("IndexedDB read failed"))},a.oncomplete=()=>{t.close(),r(c)},a.onerror=()=>{t.close(),n(a.error||new Error("IndexedDB transaction failed"))},a.onabort=()=>{t.close(),n(a.error||new Error("IndexedDB transaction aborted"))}})}class x{constructor(e){this.apiClient=e.apiClient,this.projectId=e.projectId||null,this.firebaseConfig=e.firebaseConfig||L,this.vapidKey=e.vapidKey||"BGP8qaSm1ntjWd4n9pc0lX_rw4BmMxm9u4pvRIANCitbmYaV0iy-gn05suTKBo88kUBejxdxM8sb6x2nt3avu8c",this.serviceWorkerPath=e.serviceWorkerPath||"/firebase-messaging-sw.js",this.logger=new m(e.logLevel||p.WARN,"PushManager"),this._messaging=null,this._currentToken=null,this._enabled=!1,this._enablePromise=null,this._foregroundMessageUnsubscribe=null,this._swRegistration=null,this._projectRegisteredToSW=!1,this._visibilityHandler=null,this._deviceIdCache=null}static async consumePendingRoom(e){return j(e)}static getPermissionState(){return"undefined"==typeof window||"undefined"==typeof Notification?"unsupported":"undefined"!=typeof navigator&&"serviceWorker"in navigator?Notification.permission:"unsupported"}async enable(){if(this._enabled)this.logger.debug("푸시 알림이 이미 활성화되어 있습니다.");else{if(this._enablePromise)return this.logger.debug("푸시 알림 활성화가 이미 진행 중입니다."),this._enablePromise;this._enablePromise=this._enableInternal();try{return await this._enablePromise}finally{this._enablePromise=null}}}async _enableInternal(){if("undefined"==typeof window||!("Notification"in window))throw new M(A.UNSUPPORTED_BROWSER,"푸시 알림은 브라우저 환경에서만 사용 가능합니다");if(!("serviceWorker"in navigator))throw new M(A.UNSUPPORTED_BROWSER,"이 브라우저는 서비스 워커를 지원하지 않습니다");if("denied"===Notification.permission)throw new M(A.PERMISSION_DENIED,"알림 권한이 이미 거부되어 있습니다. 브라우저 사이트 설정에서 허용해주세요.");let e,t,s;try{e=await import("firebase/app"),t=await import("firebase/messaging")}catch(e){throw new M(A.FIREBASE_NOT_INSTALLED,"firebase 패키지가 설치되지 않았습니다. npm install firebase 를 실행하세요.",e)}try{s=e.getApp("talkflow")}catch{s=e.initializeApp(this.firebaseConfig,"talkflow")}const i=await this._registerServiceWorker();if("granted"!==await Notification.requestPermission())throw new M(A.PERMISSION_DENIED,"알림 권한이 거부되었습니다. 브라우저 설정에서 허용해주세요.");this._messaging=t.getMessaging(s);const r={serviceWorkerRegistration:i};this.vapidKey&&(r.vapidKey=this.vapidKey);try{this._currentToken=await t.getToken(this._messaging,r)}catch(e){throw new M(A.TOKEN_FAILED,"FCM 토큰 획득 중 오류 발생: "+(e?.message||e),e)}if(!this._currentToken)throw new M(A.TOKEN_FAILED,"FCM 토큰 획득 실패 (응답이 비어있음)");await this._registerTokenToServer(this._currentToken),this._foregroundMessageUnsubscribe&&this._foregroundMessageUnsubscribe(),this._foregroundMessageUnsubscribe=t.onMessage(this._messaging,e=>{this.logger.debug("포그라운드 푸시 수신:",e),this._onForegroundMessage(e)}),this._swRegistration=i,await this._registerProjectToSW(),this._installVisibilityReRegister();try{await i.update()}catch(e){this.logger.debug("SW 업데이트 체크 실패 (무시):",e.message)}const n=await this._getSWVersion(i);n?this.logger.debug("SW 버전:",n):this.logger.warn("[TalkFlow] 서비스 워커가 구버전(v1)입니다. 이미지 미리보기 등 리치 알림 기능을 사용하려면 firebase-messaging-sw.js 를 v2 로 업데이트하세요. 가이드: https://docs.talkflow.ai/push/sw-upgrade"),this._enabled=!0,this.logger.info("푸시 알림 활성화 완료")}async _registerServiceWorker(){try{const e=await navigator.serviceWorker.register(this.serviceWorkerPath,{updateViaCache:"none"});return this.logger.debug("서비스 워커 등록 완료:",this.serviceWorkerPath),e}catch(e){throw new M(A.SW_REGISTER_FAILED,`서비스 워커 등록 실패: ${this.serviceWorkerPath}\n프로젝트 public 폴더에 firebase-messaging-sw.js 파일을 배치하세요.`,e)}}async _registerTokenToServer(e){try{const t=await this._getDeviceId();await this.apiClient.post("/api/v1/push/devices",{deviceToken:e,deviceType:"WEB",deviceId:t}),this.logger.info("디바이스 토큰 서버 등록 완료")}catch(e){if(this.logger.error("디바이스 토큰 서버 등록 실패:",e),e instanceof M)throw e;throw new M(A.SERVER_REGISTER_FAILED,"디바이스 토큰 서버 등록 실패: "+(e?.message||e),e)}}async _getDeviceId(){if(this._deviceIdCache)return this._deviceIdCache;try{let e=await $();if(!e&&"undefined"!=typeof localStorage){const t=localStorage.getItem(k);if(t){e=t;if(await F(e)){if(await $()===e)try{localStorage.removeItem(k),this.logger.debug("deviceId migrated from localStorage to IndexedDB")}catch(e){}else this.logger.warn("deviceId IDB read-back 불일치, localStorage 보존")}else this.logger.warn("deviceId IDB 저장 실패 (private mode / quota 등), localStorage 보존")}}if(!e){e=W();await F(e)||this.logger.warn("신규 deviceId IDB 저장 실패, 이번 세션만 유효")}return this._deviceIdCache=e,e}catch(e){if(this.logger.warn("IndexedDB deviceId 조회/저장 실패, fallback:",e),!this._deviceIdCache){let e=null;if("undefined"!=typeof localStorage)try{e=localStorage.getItem(k)}catch(e){}this._deviceIdCache=e||W()}return this._deviceIdCache}}_onForegroundMessage(e){}async consumePendingRoom(){return x.consumePendingRoom(this.projectId)}reset(){if(this._foregroundMessageUnsubscribe)try{this._foregroundMessageUnsubscribe()}catch(e){this.logger.debug("포그라운드 푸시 리스너 해제 실패 (무시):",e?.message||e)}this._uninstallVisibilityReRegister(),this._unregisterProjectFromSW(),this._foregroundMessageUnsubscribe=null,this._enablePromise=null,this._messaging=null,this._currentToken=null,this._enabled=!1,this._swRegistration=null,this._projectRegisteredToSW=!1}async _registerProjectToSW(){if(this.projectId)try{const e=(await navigator.serviceWorker.ready).active||this._swRegistration&&this._swRegistration.active;if(!e)return void this.logger.debug("SW active worker 없음 — projectId 등록 스킵");e.postMessage({type:"TALKFLOW_REGISTER_PROJECT",projectId:this.projectId}),this._projectRegisteredToSW=!0}catch(e){this.logger.debug("SW projectId 등록 실패 (무시):",e?.message||e)}}_unregisterProjectFromSW(){if(this._projectRegisteredToSW)try{if("undefined"==typeof navigator||!navigator.serviceWorker)return;const e=navigator.serviceWorker.controller;if(!e)return;e.postMessage({type:"TALKFLOW_UNREGISTER_PROJECT"})}catch(e){this.logger.debug("SW projectId 해제 실패 (무시):",e?.message||e)}}_installVisibilityReRegister(){"undefined"==typeof document||this._visibilityHandler||(this._visibilityHandler=()=>{"visible"===document.visibilityState&&this._enabled&&this.projectId&&this._registerProjectToSW().catch(()=>{})},document.addEventListener("visibilitychange",this._visibilityHandler))}_uninstallVisibilityReRegister(){if("undefined"!=typeof document&&this._visibilityHandler){try{document.removeEventListener("visibilitychange",this._visibilityHandler)}catch(e){}this._visibilityHandler=null}}getToken(){return this._currentToken}isEnabled(){return this._enabled}async getMyDevices(){const e=await this.apiClient.get("/api/v1/push/devices/me");return e&&"object"==typeof e&&"data"in e?e.data:e}async setDeviceEnabled(e,t){await this.apiClient.patch("/api/v1/push/devices/me/enabled",{deviceId:e,enabled:t}),this.logger.info(`디바이스 푸시 enabled=${t}: deviceId=${e}`)}async setCurrentDeviceEnabled(e){const t=await this._getDeviceId();await this.setDeviceEnabled(t,e)}async _getSWVersion(e){try{const t=await navigator.serviceWorker.ready,s=t.waiting||t.installing||t.active||e.waiting||e.installing||e.active;return s?new Promise(e=>{const t=setTimeout(()=>e(null),2e3),i=new MessageChannel;i.port1.onmessage=s=>{clearTimeout(t),e(s.data?.version||null)};try{s.postMessage({type:"TALKFLOW_SW_VERSION"},[i.port2])}catch(s){clearTimeout(t),e(null)}}):null}catch{return null}}setLogLevel(e){this.logger.setLevel(e)}}const G=[["connected","connected"],["disconnected","disconnected"],["reconnecting","reconnecting"],["error","connectionError"]],J=[["message","chatMessage"],["newMessage","newChatMessage"],["messageUpdated","messageUpdated"],["messageDeleted","messageDeleted"],["reactionChanged","reactionChanged"],["linkPreviewAttached","linkPreviewAttached"],["messageTranslated","messageTranslated"],["messageRead","messageRead"],["typing","typing"],["assistantProgress","assistantProgress"],["memberJoined","memberJoined"],["memberLeft","memberLeft"],["roomSubscribed","roomSubscribed"],["roomUnsubscribed","roomUnsubscribed"],["roomListSubscribed","roomListSubscribed"],["roomListUnsubscribed","roomListUnsubscribed"],["roomListUpdate","roomListUpdate"],["roomListMessage","roomListMessage"],["roomListCreated","roomListCreated"],["roomListJoined","roomListJoined"],["roomListLeft","roomListLeft"],["roomListSelfLeft","roomListSelfLeft"],["roomListKicked","roomListKicked"],["roomListSelfKicked","roomListSelfKicked"],["roomListBanned","roomListBanned"],["roomListSelfBanned","roomListSelfBanned"],["roomListRoomUpdated","roomListRoomUpdated"],["retentionCleanup","retentionCleanup"]],V=[["localStreamStarted","localStreamStarted"],["localStreamStopped","localStreamStopped"],["remoteTrack","remoteTrack"],["screenShareStarted","screenShareStarted"],["screenShareEnded","screenShareEnded"],["deviceChange","deviceChange"],["mediaStateChanged","mediaStateChanged"],["callStarted","callStarted"],["callEnded","callEnded"],["callRequested","callRequested"],["callAccepted","callAccepted"],["callRejected","callRejected"],["callCancelled","callCancelled"],["callBusy","callBusy"],["incomingCall","incomingCall"],["incomingCallWhileBusy","incomingCallWhileBusy"],["callInvitation","callInvitation"],["userJoined","userJoined"],["userLeft","userLeft"],["participantLeft","participantLeft"],["participantMediaState","participantMediaState"],["peerConnected","peerConnected"],["peerDisconnected","peerDisconnected"],["peerClosed","peerClosed"],["error","webrtcError"]];function H(e,t,s){s.forEach(([s,i])=>{e.on(s,e=>{t.emit(i,e)})})}const B=["getRooms","getRoom","getRoomInfo","setMyRoomLanguage","createOneToOneRoom","createGroupRoom","joinGroupRoom","leaveRoom","updateGroupRoom","inviteToGroupRoom","kickMember","banMember","unbanMember","getBannedMembers","getAvailableGroupRooms","getAllGroupRooms","enterRoom","getMessages","fetchLinkPreview","sendMessage","sendMessageOptimistic","sendTextMessage","sendTextMessageOptimistic","sendReply","sendReplyOptimistic","uploadFile","sendFileMessage","sendFileMessageOptimistic","editMessage","deleteMessage","markAsRead","pinMessage","unpinMessage","toggleReaction","subscribeRoom","unsubscribeRoom","unsubscribeAllRooms","setActiveRoom","clearActiveRoom","getActiveRoom","subscribeRoomList","unsubscribeRoomList","isRoomListSubscribed","getSubscribedRooms","isSubscribed","startTyping","stopTyping","getAssistants","getRoomAiMeta","rateAssistantMessage","summarizeWithAssistant","translateWithAssistant","getRoomPmPrompt","upsertRoomPmPrompt","activateRoomPmPrompt","deactivateRoomPmPrompt","getRoomPmPromptVersions","activateRoomPmPromptVersion","previewRoomPmPrompt"],K=["initializeIceServers","getTurnCredentials","createCallRoom","getCallRoom","joinCallRoomApi","leaveCallRoomApi","enableIncomingCalls","disableIncomingCalls","isIncomingCallsEnabled","startCall","callUser","acceptCall","rejectCall","cancelCall","endCall","toggleVideo","toggleAudio","setVideoEnabled","setAudioEnabled","startScreenShare","stopScreenShare","getLocalStream","getDevices","switchDevice","startDeviceChangeDetection","stopDeviceChangeDetection","applyVideoConstraints","getVideoSettings","getAudioSettings","isInCall","getCurrentRoom","getParticipants","getMediaState","getConnectionSummary","getStats"];function z(e,t,s){s.forEach(s=>{e[s]=function(...e){return this[t][s](...e)}})}class q extends i{constructor(e){super(),this._validateOptions(e);const t=e.env||g.environment,s=(e.serverUrl||u(t)).replace(/\/$/,"");this.options={serverUrl:s,env:t,apiKey:e.apiKey,projectId:e.projectId,jwtToken:e.jwtToken||null,useSockJS:!1!==e.useSockJS,reconnectDelay:e.reconnectDelay||g.reconnectDelay,maxReconnectAttempts:e.maxReconnectAttempts||g.maxReconnectAttempts,iceServers:e.iceServers||g.iceServers,autoSubscribeRoomList:!1!==e.autoSubscribeRoomList,logLevel:void 0!==e.logLevel?e.logLevel:p.WARN},this.logger=new m(this.options.logLevel,"TalkFlowClient"),this._initialized=!1,this._state=r.DISCONNECTED,this._userId=null,this.options.jwtToken&&(this._userId=S(this.options.jwtToken)),this.apiClient=new E({baseUrl:this.options.serverUrl,apiKey:this.options.apiKey,projectId:this.options.projectId,jwtToken:this.options.jwtToken,logLevel:this.options.logLevel}),this.connectionManager=null,this._chat=null,this._webrtc=null,this._pushManager=null,this._pushEnablePromise=null,this.options.jwtToken&&this._userId&&this._initializeSubClients(),this._initialized=!0,this.logger.info("TalkFlowClient initialized",{userId:this._userId,hasToken:!!this.options.jwtToken})}get chat(){return this._chat}get webrtc(){return this._webrtc}get pushManager(){return this._pushManager}get userId(){return this._userId}_validateOptions(e){if(!e)throw new Error("Options are required");if(!e.apiKey)throw new Error("apiKey is required");if(!e.projectId)throw new Error("projectId is required");if(e.apiKey.startsWith("sk-")){const t=e.env||"production";"development"===t?console.warn("⚠️ [TalkFlow] Server Key (sk-) 를 개발 모드에서 사용 중입니다. 프로덕션 배포 전에 반드시 Client Key (ck-) 로 교체하세요."):console.error("🚨 [TalkFlow] Server Key (sk-) 가 비개발 환경 ("+t+") 에서 감지되었습니다. 보안 위험: Server Key 를 브라우저에 포함하면 공격자가 임의 사용자로 JWT 를 발급받을 수 있습니다. 반드시 Client Key (ck-) 로 교체하세요. 서버에서도 프로덕션 모드에서 Server Key 의 브라우저 호출을 차단합니다.")}}_initializeSubClients(){!function(e){if(!e.connectionManager){if(!e.userId)throw new Error("userId is required to initialize sub-clients. Please set JWT token first.");e.connectionManager=new v({serverUrl:e.options.serverUrl,jwtToken:e.options.jwtToken,apiKey:e.options.apiKey,projectId:e.options.projectId,useSockJS:e.options.useSockJS,reconnectDelay:e.options.reconnectDelay,maxReconnectAttempts:e.options.maxReconnectAttempts,logLevel:e.options.logLevel}),e._chat=new f({connectionManager:e.connectionManager,apiClient:e.apiClient,userId:e._userId,logLevel:e.options.logLevel}),e._webrtc=new R({connectionManager:e.connectionManager,apiClient:e.apiClient,userId:e._userId,iceServers:e.options.iceServers,logLevel:e.options.logLevel}),e._setupEventForwarding(),e.logger.debug("Sub-clients initialized")}}(this)}_setupEventForwarding(){var e;(e=this).connectionManager&&(e.connectionManager.on("stateChange",({state:t,prevState:s})=>{e._state=t,e.emit("stateChange",{state:t,prevState:s})}),H(e.connectionManager,e,G),e.chat&&H(e.chat,e,J),e.webrtc&&H(e.webrtc,e,V))}isConnected(){return!!this.connectionManager&&this.connectionManager.isConnected()}getState(){return this._state}hasToken(){return!!this.options.jwtToken}async registerUser(e){const t={...e};if(void 0===t.preferredLanguage){const e=q.detectBrowserLanguage();e&&(t.preferredLanguage=e)}const s=await this.apiClient.post("/api/v1/users/auth",t);return s.data&&s.data.accessToken&&(await this.setToken(s.data.accessToken),this.logger.info("User authenticated, token auto-set")),s}async updateMyInfo(e){return this._checkToken(),this.apiClient.put("/api/v1/users/update",e)}async setPreferredLanguage(e){return this.updateMyInfo({preferredLanguage:e})}static detectBrowserLanguage(){return"undefined"!=typeof navigator&&navigator.language&&navigator.language.trim().toLowerCase()||null}static displayText(e,t){return e&&e.translations&&t&&e.translations[t]?e.translations[t]:e?e.content:null}async checkUserExists(e){return this.apiClient.get(`/api/v1/users/${e}/exists`)}async getUsers(e={}){this._checkToken();const{size:t=50,lastId:s,lastSortValue:i}=e;return this.apiClient.get("/api/v1/users",{size:t,lastId:s,lastSortValue:i})}async searchUsers(e){this._checkToken();const{keyword:t,limit:s=20}=e;return this.apiClient.get("/api/v1/users/search",{keyword:t,limit:s})}_checkToken(){if(!this.options.jwtToken)throw new Error('JWT token is required for this operation. Obtain it from your backend (POST /api/v1/users/auth with Server API Key) and pass it via setToken(). See README "프로덕션 인증 플로우".')}getUserId(){return this.userId}isInitialized(){return this._initialized}isReady(){return this._initialized&&!!this.connectionManager}setLogLevel(e){this.options.logLevel=e,this.logger.setLevel(e),this.apiClient.logger?.setLevel(e),this.connectionManager&&this.connectionManager.setLogLevel(e),this.chat&&this.chat.setLogLevel(e),this.webrtc&&this.webrtc.setLogLevel(e)}getStatus(){return{initialized:this._initialized,ready:this.isReady(),hasToken:this.hasToken(),connectionState:this._state,isConnected:this.isConnected(),userId:this.userId,chat:this.chat?{subscribedRooms:this.chat.getSubscribedRooms()}:null,webrtc:this.webrtc?{isInCall:this.webrtc.isInCall(),currentRoom:this.webrtc.getCurrentRoom(),participants:this.webrtc.getParticipants(),mediaState:this.webrtc.getMediaState()}:null}}async destroy(){await this.disconnect(),this.chat&&this.chat.destroy(),this.webrtc&&this.webrtc.destroy(),this.connectionManager&&await this.connectionManager.destroy(),this.removeAllListeners(),this._initialized=!1,this.logger.info("TalkFlowClient destroyed")}}q.ConnectionState=r,q.ErrorTypes=n,q.LogLevel=p,q.Environment=d,function(e){e.prototype.connect=async function(e,t={}){const{enablePush:s=!1}=t;if(e&&await this.setToken(e),!this.options.jwtToken)throw new Error('JWT token is required. Obtain it from your backend (which calls POST /api/v1/users/auth with a Server API Key) and pass it to the SDK via the jwtToken option, setToken(), or connect(jwt). See README "프로덕션 인증 플로우".');if(T(this.options.jwtToken))throw new Error("JWT token has expired. Please update the token before connecting.");if(this.connectionManager||this._initializeSubClients(),await this.connectionManager.connect(),this.webrtc&&await this.webrtc.enableIncomingCalls(),this.chat&&this.options.autoSubscribeRoomList)try{await this.chat.subscribeRoomList()}catch(e){this.logger.warn("Failed to auto-subscribe room list (non-fatal):",e)}s&&this.enablePushNotifications(),this.logger.info("Connected to server")},e.prototype.disconnect=async function(){this.connectionManager&&(this.webrtc&&this.webrtc.isInCall()&&this.webrtc.endCall(),this.webrtc&&this.webrtc.disableIncomingCalls(),this.chat&&(this.chat.unsubscribeAllRooms(),this.chat.unsubscribeRoomList()),await this.connectionManager.disconnect(),this.logger.info("Disconnected from server"))},e.prototype.logout=async function(){try{await this.apiClient.post("/v1/auth/signout"),this.logger.info("Logged out from server")}catch(e){this.logger.warn("Server logout failed (proceeding with local cleanup):",e.message)}await this.disconnect(),this.pushManager&&(this.pushManager.reset(),this._pushEnablePromise=null),this.options.jwtToken=null,this._userId=null,this.apiClient.setJwtToken(null),this.emit("loggedOut")},e.prototype.enablePushNotifications=async function(e={}){if(!this.options.jwtToken){const e=new Error("JWT token is required to enable push notifications.");return this.emit("pushFailed",{reason:"NO_TOKEN",error:e}),{ok:!1,reason:"NO_TOKEN",error:e}}if(!this.pushManager){this._pushManager=new x({apiClient:this.apiClient,projectId:this.options.projectId,firebaseConfig:e.firebaseConfig,vapidKey:e.vapidKey,serviceWorkerPath:e.serviceWorkerPath,logLevel:this.options.logLevel});const t=3e5;this.pushManager._onForegroundMessage=e=>{const s=e.data||{};if(s.projectId&&s.projectId!==this.options.projectId)return void this.logger.debug("다른 프로젝트 푸시 무시:",s.projectId);const i=this.chat?.getActiveRoom();if(i&&i===s.roomId)this.logger.debug("포그라운드 푸시 suppress (현재 방):",s.roomId);else{if(s.messageId){if(this._recentPushMessageIds||(this._recentPushMessageIds=new Map),this._recentPushMessageIds.has(s.messageId))return void this.logger.debug("포그라운드 푸시 dedup:",s.messageId);const e=setTimeout(()=>{this._recentPushMessageIds.delete(s.messageId)},t);this._recentPushMessageIds.set(s.messageId,e)}this.emit("pushNotification",{title:e.notification?.title||s.title,body:e.notification?.body||s.body,data:s})}}}if(this.pushManager.isEnabled())return this.logger.debug("웹 푸시가 이미 활성화되어 있어 중복 호출을 건너뜁니다."),{ok:!0,alreadyEnabled:!0};if(this._pushEnablePromise)return this.logger.debug("웹 푸시 활성화가 이미 진행 중입니다."),this._pushEnablePromise;const t=(async()=>{try{return await this.pushManager.enable(),this.emit("pushEnabled"),{ok:!0}}catch(e){const t=e&&e.code?e.code:"UNKNOWN";return this.logger.warn("Failed to enable push notifications:",t,e?.message),this.emit("pushFailed",{reason:t,error:e}),{ok:!1,reason:t,error:e}}})();return this._pushEnablePromise=t,t.finally(()=>{this._pushEnablePromise===t&&(this._pushEnablePromise=null)}),t},e.prototype.consumePendingRoom=async function(){return x.consumePendingRoom(this.options.projectId)},e.prototype.setCurrentDeviceEnabled=function(e){return this.pushManager?.setCurrentDeviceEnabled(e)},e.prototype.setDeviceEnabled=function(e,t){return this.pushManager?.setDeviceEnabled(e,t)},e.prototype.getMyDevices=function(){return this.pushManager?.getMyDevices()},e.prototype.getPushPermissionState=function(){return x.getPermissionState()},e.prototype.isPushEnabled=function(){return this.pushManager?.isEnabled()??!1},e.prototype.getPushToken=function(){return this.pushManager?.getToken()??null},e.prototype.resetPush=function(){this.pushManager?.reset(),this._pushEnablePromise=null},e.prototype.setToken=async function(e){const{userId:t}=b(e,{validateExpiry:!1}),s=this.userId&&this.userId!==t;this.options.jwtToken=e,this._userId=t,this.apiClient.setJwtToken(e),s&&this.connectionManager?(await this.disconnect(),this.pushManager&&(this.pushManager.reset(),this._pushEnablePromise=null),this.connectionManager=null,this._chat=null,this._webrtc=null,this._initializeSubClients()):this.connectionManager?this.connectionManager.updateToken(e):this._initializeSubClients(),this.logger.info("JWT token set",{userId:t}),this.emit("tokenSet",{userId:t})},e.prototype.updateToken=async function(e){await this.setToken(e)}}(q),function(e){z(e.prototype,"chat",B),z(e.prototype,"webrtc",K)}(q),e.AssistantMode={GENERAL:"GENERAL",PEOPLE_ONLY:"PEOPLE_ONLY",CALL_ONLY:"CALL_ONLY"},e.ChatClient=f,e.ChatMessageType={TEXT:"TEXT",IMAGE:"IMAGE",FILE:"FILE",VIDEO:"VIDEO",AUDIO:"AUDIO",SYSTEM:"SYSTEM"},e.ChatRoomType=o,e.ConnectionManager=v,e.ConnectionState=r,e.DefaultConfig=g,e.Endpoints=h,e.EngagementIntensity={QUIET:"QUIET",NORMAL:"NORMAL",ACTIVE:"ACTIVE"},e.Environment=d,e.ErrorTypes=n,e.EventEmitter=i,e.LogLevel=p,e.Logger=m,e.MediaStreamManager=y,e.PeerConnectionManager=w,e.PersonaRole={LEGAL_ADVISOR:"LEGAL_ADVISOR",MARKETING:"MARKETING",PRODUCT_PLANNING:"PRODUCT_PLANNING",HR:"HR",FINANCE:"FINANCE",CUSTOMER_SUPPORT:"CUSTOMER_SUPPORT",SALES:"SALES",ENGINEERING:"ENGINEERING",DATA_ANALYST:"DATA_ANALYST",PROJECT_MANAGEMENT:"PROJECT_MANAGEMENT",RESEARCH:"RESEARCH",TRANSLATION:"TRANSLATION",DESIGN:"DESIGN",PM:"PM"},e.PmPromptLayerEditorType={SUPER_ADMIN:"SUPER_ADMIN",PROJECT_ADMIN:"PROJECT_ADMIN",ROOM_OWNER:"ROOM_OWNER"},e.PmPromptLayerScope={PROJECT:"PROJECT",ROOM:"ROOM"},e.PushError=M,e.PushErrorCode=A,e.PushManager=x,e.RoomAiType={NONE:"NONE",PERSONA_MULTI:"PERSONA_MULTI",PM_BACKSTAGE:"PM_BACKSTAGE"},e.RoomListEventType=c,e.SenderType={USER:"USER",ASSISTANT:"ASSISTANT"},e.SignalTypes=a,e.SummarizeFormat={MINUTES:"MINUTES",SHORT:"SHORT",TIMELINE:"TIMELINE",ACTIONS:"ACTIONS",OPTIONS:"OPTIONS"},e.WebRTCClient=R,e.WebSocketPaths=l,e.decodeJWTPayload=function(e){try{const t=I(e).split(".");return 3!==t.length?null:JSON.parse(_(t[1]))}catch(e){return console.error("Failed to decode JWT payload:",e),null}},e.default=q,e.extractUserIdFromJWT=S,e.getJWTRemainingTime=function(e){try{const t=I(e).split(".");if(3!==t.length)return-1;const s=JSON.parse(_(t[1]));return s.exp?1e3*s.exp-Date.now():1/0}catch(e){return console.error("Failed to get JWT remaining time:",e),-1}},e.getServerUrl=u,e.isJWTExpired=T,e.validateAndParseJWT=b,Object.defineProperty(e,"__esModule",{value:!0})});
|
|
2
2
|
//# sourceMappingURL=talkflow-sdk.umd.js.map
|