@scalemule/chat 0.0.4 → 0.0.7

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.
Files changed (85) hide show
  1. package/dist/{ChatClient-COmdEJ11.d.ts → ChatClient-DQPHdUHX.d.cts} +21 -2
  2. package/dist/{ChatClient-BoZaTtyM.d.cts → ChatClient-DtUKF-4c.d.ts} +21 -2
  3. package/dist/chat.embed.global.js +1 -1
  4. package/dist/chat.umd.global.js +288 -12
  5. package/dist/{chunk-ZLMMNFZL.js → chunk-5O5YLRJL.js} +386 -16
  6. package/dist/chunk-GTMAK3IA.js +285 -0
  7. package/dist/chunk-TRCELAZQ.cjs +287 -0
  8. package/dist/{chunk-YDLRISR7.cjs → chunk-W2PWFS3E.cjs} +386 -15
  9. package/dist/constants.d.ts +9 -0
  10. package/dist/constants.d.ts.map +1 -0
  11. package/dist/core/ChatClient.d.ts +96 -0
  12. package/dist/core/ChatClient.d.ts.map +1 -0
  13. package/dist/core/EventEmitter.d.ts +11 -0
  14. package/dist/core/EventEmitter.d.ts.map +1 -0
  15. package/dist/core/MessageCache.d.ts +18 -0
  16. package/dist/core/MessageCache.d.ts.map +1 -0
  17. package/dist/core/OfflineQueue.d.ts +19 -0
  18. package/dist/core/OfflineQueue.d.ts.map +1 -0
  19. package/dist/element.cjs +542 -51
  20. package/dist/element.d.ts +2 -2
  21. package/dist/element.d.ts.map +1 -0
  22. package/dist/element.js +541 -50
  23. package/dist/embed/index.d.ts +2 -0
  24. package/dist/embed/index.d.ts.map +1 -0
  25. package/dist/factory.d.ts +8 -0
  26. package/dist/factory.d.ts.map +1 -0
  27. package/dist/iframe.d.cts +1 -1
  28. package/dist/iframe.d.ts +3 -5
  29. package/dist/iframe.d.ts.map +1 -0
  30. package/dist/index.cjs +34 -5
  31. package/dist/index.d.cts +93 -4
  32. package/dist/index.d.ts +8 -77
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +29 -4
  35. package/dist/react-components/ChatInput.d.ts +16 -0
  36. package/dist/react-components/ChatInput.d.ts.map +1 -0
  37. package/dist/react-components/ChatMessageItem.d.ts +13 -0
  38. package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
  39. package/dist/react-components/ChatMessageList.d.ts +15 -0
  40. package/dist/react-components/ChatMessageList.d.ts.map +1 -0
  41. package/dist/react-components/ChatThread.d.ts +12 -0
  42. package/dist/react-components/ChatThread.d.ts.map +1 -0
  43. package/dist/react-components/ConversationList.d.ts +13 -0
  44. package/dist/react-components/ConversationList.d.ts.map +1 -0
  45. package/dist/react-components/EmojiPicker.d.ts +8 -0
  46. package/dist/react-components/EmojiPicker.d.ts.map +1 -0
  47. package/dist/react-components/index.d.ts +8 -0
  48. package/dist/react-components/index.d.ts.map +1 -0
  49. package/dist/react-components/theme.d.ts +19 -0
  50. package/dist/react-components/theme.d.ts.map +1 -0
  51. package/dist/react-components/utils.d.ts +4 -0
  52. package/dist/react-components/utils.d.ts.map +1 -0
  53. package/dist/react.cjs +1213 -53
  54. package/dist/react.d.cts +100 -4
  55. package/dist/react.d.ts +38 -15
  56. package/dist/react.d.ts.map +1 -0
  57. package/dist/react.js +1167 -18
  58. package/dist/shared/ChatController.d.ts +65 -0
  59. package/dist/shared/ChatController.d.ts.map +1 -0
  60. package/dist/shared/upload.d.ts +3 -0
  61. package/dist/shared/upload.d.ts.map +1 -0
  62. package/dist/support-widget.global.js +485 -157
  63. package/dist/support.d.ts +99 -0
  64. package/dist/support.d.ts.map +1 -0
  65. package/dist/transport/HttpTransport.d.ts +20 -0
  66. package/dist/transport/HttpTransport.d.ts.map +1 -0
  67. package/dist/transport/WebSocketTransport.d.ts +78 -0
  68. package/dist/transport/WebSocketTransport.d.ts.map +1 -0
  69. package/dist/{types-BmD7f1gV.d.cts → types-COPVrm3K.d.cts} +25 -1
  70. package/dist/{types-BmD7f1gV.d.ts → types-COPVrm3K.d.ts} +25 -1
  71. package/dist/types.d.ts +271 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/umd.d.ts +6 -0
  74. package/dist/umd.d.ts.map +1 -0
  75. package/dist/version.d.ts +2 -0
  76. package/dist/version.d.ts.map +1 -0
  77. package/dist/widget/icons.d.ts +8 -0
  78. package/dist/widget/icons.d.ts.map +1 -0
  79. package/dist/widget/index.d.ts +2 -0
  80. package/dist/widget/index.d.ts.map +1 -0
  81. package/dist/widget/storage.d.ts +5 -0
  82. package/dist/widget/storage.d.ts.map +1 -0
  83. package/dist/widget/styles.d.ts +3 -0
  84. package/dist/widget/styles.d.ts.map +1 -0
  85. package/package.json +5 -2
@@ -1,18 +1,294 @@
1
- "use strict";var ScaleMuleChat=(()=>{var b=Object.defineProperty;var S=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var R=(c,n)=>{for(var e in n)b(c,e,{get:n[e],enumerable:!0})},x=(c,n,e,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of M(n))!A.call(c,s)&&s!==e&&b(c,s,{get:()=>n[s],enumerable:!(t=S(n,s))||t.enumerable});return c};var P=c=>x(b({},"__esModule",{value:!0}),c);var H={};R(H,{ChatClient:()=>d,create:()=>K,version:()=>W});var u=class{constructor(){this.listeners=new Map}on(n,e){return this.listeners.has(n)||this.listeners.set(n,new Set),this.listeners.get(n).add(e),()=>this.off(n,e)}off(n,e){this.listeners.get(n)?.delete(e)}once(n,e){let t=(s=>{this.off(n,t),e(s)});return this.on(n,t)}emit(n,...[e]){let t=this.listeners.get(n);if(t)for(let s of t)try{s(e)}catch(i){console.error(`[ScaleMuleChat] Error in ${String(n)} listener:`,i)}}removeAllListeners(n){n?this.listeners.delete(n):this.listeners.clear()}};var w="https://api.scalemule.com";var l=class{constructor(n,e){this.cache=new Map;this.maxMessages=n??200,this.maxConversations=e??50}getMessages(n){return this.cache.get(n)??[]}setMessages(n,e){this.cache.set(n,e.slice(0,this.maxMessages)),this.evictOldConversations()}addMessage(n,e){let t=this.cache.get(n)??[];t.some(s=>s.id===e.id)||(t.push(e),t.sort((s,i)=>s.created_at.localeCompare(i.created_at)),t.length>this.maxMessages&&t.splice(0,t.length-this.maxMessages),this.cache.set(n,t))}updateMessage(n,e){let t=this.cache.get(n);if(!t)return;let s=t.findIndex(i=>i.id===e.id);s>=0&&(t[s]=e)}removeMessage(n,e){let t=this.cache.get(n);if(!t)return;let s=t.findIndex(i=>i.id===e);s>=0&&t.splice(s,1)}clear(n){n?this.cache.delete(n):this.cache.clear()}evictOldConversations(){for(;this.cache.size>this.maxConversations;){let n=this.cache.keys().next().value;n&&this.cache.delete(n)}}};var _="scalemule_chat_offline_queue",g=class{constructor(n=!0){this.queue=[];this.enabled=n,this.enabled&&this.load()}enqueue(n,e,t="text",s){this.enabled&&(this.queue.push({conversationId:n,content:e,message_type:t,attachments:s,timestamp:Date.now()}),this.save())}drain(){let n=[...this.queue];return this.queue=[],this.save(),n}get size(){return this.queue.length}save(){try{typeof localStorage<"u"&&localStorage.setItem(_,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let n=localStorage.getItem(_);n&&(this.queue=JSON.parse(n))}}catch{this.queue=[]}}};var m=class{constructor(n){this.baseUrl=n.baseUrl.replace(/\/$/,""),this.apiKey=n.apiKey,this.getToken=n.getToken,this.timeout=n.timeout??1e4}async get(n){return this.request("GET",n)}async post(n,e){return this.request("POST",n,e)}async patch(n,e){return this.request("PATCH",n,e)}async del(n){return this.request("DELETE",n)}async request(n,e,t){let s={"Content-Type":"application/json"};if(this.apiKey&&(s["x-api-key"]=this.apiKey),this.getToken){let r=await this.getToken();r&&(s.Authorization=`Bearer ${r}`)}let i=new AbortController,o=setTimeout(()=>i.abort(),this.timeout);try{let r=await fetch(`${this.baseUrl}${e}`,{method:n,headers:s,body:t?JSON.stringify(t):void 0,signal:i.signal,credentials:"include"});if(clearTimeout(o),r.status===204)return{data:null,error:null};let a=await r.json().catch(()=>null);return r.ok?{data:a?.data!==void 0?a.data:a,error:null}:{data:null,error:{code:a?.error?.code??a?.code??"unknown",message:a?.error?.message??a?.message??r.statusText,status:r.status,details:a?.error?.details??a?.details}}}catch(r){return clearTimeout(o),{data:null,error:{code:"network_error",message:r instanceof Error?r.message:"Network error",status:0}}}}};var v=class extends u{constructor(e){super();this.ws=null;this.status="disconnected";this.subscriptions=new Set;this.presenceChannels=new Map;this.reconnectAttempt=0;this.reconnectTimer=null;this.heartbeatTimer=null;this.config=e,this.maxRetries=e.reconnect?.maxRetries??1/0,this.baseDelay=e.reconnect?.baseDelay??1e3,this.maxDelay=e.reconnect?.maxDelay??3e4;let t=e.baseUrl.replace(/\/$/,"");this.ticketUrl=`${t}/v1/realtime/ws/ticket`;let s=e.wsUrl?e.wsUrl.replace(/\/$/,""):t;this.wsBaseUrl=s.replace(/^http/,"ws")+"/v1/realtime/ws"}getStatus(){return this.status}async connect(){if(!(this.status==="connected"||this.status==="connecting")){this.setStatus("connecting");try{let e=await this.obtainTicket();if(!e){this.setStatus("disconnected"),this.emit("error",{message:"Failed to obtain WS ticket"});return}let t=`${this.wsBaseUrl}?ticket=${encodeURIComponent(e)}`;this.ws=new WebSocket(t),this.ws.onopen=()=>{this.setStatus("connected"),this.reconnectAttempt=0,this.startHeartbeat(),this.resubscribeAll()},this.ws.onmessage=s=>{this.handleMessage(s.data)},this.ws.onclose=()=>{this.stopHeartbeat(),this.status!=="disconnected"&&this.scheduleReconnect()},this.ws.onerror=()=>{}}catch{this.setStatus("disconnected"),this.scheduleReconnect()}}}disconnect(){this.setStatus("disconnected"),this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.onclose=null,this.ws.close(),this.ws=null)}subscribe(e){return this.subscriptions.add(e),this.status==="connected"?this.send({type:"subscribe",channel:e}):this.status==="disconnected"&&this.connect(),()=>this.unsubscribe(e)}unsubscribe(e){this.subscriptions.delete(e),this.status==="connected"&&this.send({type:"unsubscribe",channel:e})}publish(e,t){this.status==="connected"&&this.send({type:"publish",channel:e,data:t})}joinPresence(e,t){this.presenceChannels.set(e,t),this.status==="connected"&&this.send({type:"presence_join",channel:e,user_data:t??{}})}leavePresence(e){this.presenceChannels.delete(e),this.status==="connected"&&this.send({type:"presence_leave",channel:e})}async obtainTicket(){let e={"Content-Type":"application/json"};if(this.config.apiKey&&(e["x-api-key"]=this.config.apiKey),this.config.getToken){let t=await this.config.getToken();t&&(e.Authorization=`Bearer ${t}`)}try{let t=await fetch(this.ticketUrl,{method:"POST",headers:e,credentials:"include"});if(!t.ok)return null;let s=await t.json();return s.ticket??s.data?.ticket??null}catch{return null}}send(e){this.ws?.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}handleMessage(e){if(e!=="pong")try{let t=JSON.parse(e);switch(t.type){case"auth_success":break;case"subscribed":break;case"message":this.emit("message",{channel:t.channel,data:t.data});break;case"presence_state":this.emit("presence:state",{channel:t.channel,members:t.members??[]});break;case"presence_join":this.emit("presence:join",{channel:t.channel,user:t.user});break;case"presence_leave":this.emit("presence:leave",{channel:t.channel,userId:t.user_id});break;case"presence_update":this.emit("presence:update",{channel:t.channel,userId:t.user_id,status:t.status,userData:t.user_data});break;case"error":this.emit("error",{message:t.message??"Unknown error"});break;case"token_expiring":this.reconnectAttempt=0,this.scheduleReconnect();break}}catch{}}resubscribeAll(){for(let e of this.subscriptions)this.send({type:"subscribe",channel:e});for(let[e,t]of this.presenceChannels)this.send({type:"presence_join",channel:e,user_data:t??{}})}startHeartbeat(){this.heartbeatTimer=setInterval(()=>{this.ws?.readyState===WebSocket.OPEN&&this.ws.send("ping")},3e4)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.reconnectAttempt>=this.maxRetries){this.setStatus("disconnected");return}this.setStatus("reconnecting"),this.emit("reconnecting",{attempt:this.reconnectAttempt+1});let e=Math.min(this.baseDelay*Math.pow(2,this.reconnectAttempt)+Math.random()*this.baseDelay*.3,this.maxDelay);this.reconnectTimer=setTimeout(()=>{this.reconnectAttempt++,this.connect()},e)}setStatus(e){this.status!==e&&(this.status=e,this.emit("status",e))}};var d=class extends u{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??w;this.http=new m({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0)}),this.ws=new v({baseUrl:t,wsUrl:e.wsUrl,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0),reconnect:e.reconnect}),this.cache=new l(e.messageCache?.maxMessages,e.messageCache?.maxConversations),this.offlineQueue=new g(e.offlineQueue??!0),this.ws.on("status",s=>{switch(s){case"connected":this.emit("connected"),this.flushOfflineQueue();break;case"disconnected":this.emit("disconnected");break}}),this.ws.on("reconnecting",s=>{this.emit("reconnecting",s)}),this.ws.on("message",({channel:s,data:i})=>{this.handleRealtimeMessage(s,i)}),this.ws.on("presence:state",({channel:s,members:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:state",{conversationId:o,members:i})}),this.ws.on("presence:join",({channel:s,user:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:join",{userId:i.user_id,conversationId:o,userData:i.user_data})}),this.ws.on("presence:leave",({channel:s,userId:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:leave",{userId:i,conversationId:o})}),this.ws.on("presence:update",({channel:s,userId:i,status:o,userData:r})=>{let a=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:update",{userId:i,conversationId:a,status:o,userData:r})}),this.ws.on("error",({message:s})=>{this.emit("error",{code:"ws_error",message:s})})}get status(){return this.ws.getStatus()}connect(){this.ws.connect()}disconnect(){for(let e of this.conversationSubs.values())e();this.conversationSubs.clear(),this.ws.disconnect()}async createConversation(e){let t={...e,conversation_type:e.conversation_type??"direct"},s=await this.http.post("/v1/chat/conversations",t);return s.data&&this.trackConversationType(s.data),s}async listConversations(e){let t=new URLSearchParams;e?.page&&t.set("page",String(e.page)),e?.per_page&&t.set("per_page",String(e.per_page)),e?.conversation_type&&t.set("conversation_type",e.conversation_type);let s=t.toString(),i=await this.http.get(`/v1/chat/conversations${s?"?"+s:""}`);return i.data&&i.data.forEach(o=>this.trackConversationType(o)),i}async getConversation(e){let t=await this.http.get(`/v1/chat/conversations/${e}`);return t.data&&this.trackConversationType(t.data),t}trackConversationType(e){e.conversation_type!=="direct"&&e.conversation_type!=="group"&&this.conversationTypes.set(e.id,e.conversation_type)}async sendMessage(e,t){let s=await this.http.post(`/v1/chat/conversations/${e}/messages`,{content:t.content,message_type:t.message_type??"text",attachments:t.attachments});return s.data?this.cache.addMessage(e,s.data):s.error?.status===0&&this.offlineQueue.enqueue(e,t.content,t.message_type??"text"),s}async getMessages(e,t){let s=new URLSearchParams;t?.limit&&s.set("limit",String(t.limit)),t?.before&&s.set("before",t.before),t?.after&&s.set("after",t.after);let i=s.toString(),o=await this.http.get(`/v1/chat/conversations/${e}/messages${i?"?"+i:""}`);return o.data?.messages&&(t?.after||(o.data.messages=o.data.messages.slice().reverse()),!t?.before&&!t?.after&&this.cache.setMessages(e,o.data.messages)),o}async editMessage(e,t){return this.http.patch(`/v1/chat/messages/${e}`,{content:t})}async deleteMessage(e){return this.http.del(`/v1/chat/messages/${e}`)}getCachedMessages(e){return this.cache.getMessages(e)}async addReaction(e,t){return this.http.post(`/v1/chat/messages/${e}/reactions`,{emoji:t})}async removeReaction(e,t){return this.http.del(`/v1/chat/messages/${e}/reactions/${encodeURIComponent(t)}`)}async getUnreadTotal(){return this.http.get("/v1/chat/conversations/unread-total")}async sendTyping(e,t=!0){await this.http.post(`/v1/chat/conversations/${e}/typing`,{is_typing:t})}async markRead(e){await this.http.post(`/v1/chat/conversations/${e}/read`)}async getReadStatus(e){return this.http.get(`/v1/chat/conversations/${e}/read-status`)}async addParticipant(e,t){return this.http.post(`/v1/chat/conversations/${e}/participants`,{user_id:t})}async removeParticipant(e,t){return this.http.del(`/v1/chat/conversations/${e}/participants/${t}`)}joinPresence(e,t){let s=this.channelName(e);this.ws.joinPresence(s,t)}leavePresence(e){let t=this.channelName(e);this.ws.leavePresence(t)}updatePresence(e,t,s){let i=this.channelName(e);this.ws.send({type:"presence_update",channel:i,status:t,user_data:s})}async getChannelSettings(e){return this.http.get(`/v1/chat/channels/${e}/settings`)}setConversationType(e,t){this.conversationTypes.set(e,t)}async findChannelBySessionId(e){let t=await this.http.get(`/v1/chat/channels/by-session?linked_session_id=${encodeURIComponent(e)}`);return t.error?.status===404?null:(t.data&&this.conversationTypes.set(t.data.id,t.data.channel_type),t.data)}async joinChannel(e){return this.http.post(`/v1/chat/channels/${e}/join`,{})}async createEphemeralChannel(e){let t=await this.http.post("/v1/chat/channels/ephemeral",e);return t.data&&this.conversationTypes.set(t.data.id,"ephemeral"),t}async createLargeRoom(e){let t=await this.http.post("/v1/chat/channels/large-room",e);return t.data&&this.conversationTypes.set(t.data.id,"large_room"),t}async getSubscriberCount(e){return(await this.http.get(`/v1/chat/conversations/${e}/subscriber-count`)).data?.count??0}subscribeToConversation(e){if(this.conversationSubs.has(e))return this.conversationSubs.get(e);let t=this.channelName(e),s=this.ws.subscribe(t);return this.conversationSubs.set(e,s),()=>{this.conversationSubs.delete(e),s()}}channelName(e){switch(this.conversationTypes.get(e)){case"large_room":return`conversation:lr:${e}`;case"broadcast":return`conversation:bc:${e}`;case"support":return`conversation:support:${e}`;default:return`conversation:${e}`}}destroy(){this.disconnect(),this.cache.clear(),this.removeAllListeners()}handleRealtimeMessage(e,t){if(e.startsWith("conversation:")){this.handleConversationMessage(e,t);return}if(e.startsWith("private:")){this.handlePrivateMessage(t);return}}handlePrivateMessage(e){let t=e;if(!t)return;let s=t.event??t.type,i=t.data??t;switch(s){case"new_message":{let o=i.conversation_id,r=i.id??i.message_id,a=i.sender_id,h=i.content??"";o&&this.emit("inbox:update",{conversationId:o,messageId:r,senderId:a,preview:h});break}case"support:new_conversation":{let o=i.conversation_id,r=i.visitor_name;o&&this.emit("support:new",{conversationId:o,visitorName:r});break}case"support:assigned":{let o=i.conversation_id,r=i.visitor_name,a=i.visitor_email;o&&this.emit("support:assigned",{conversationId:o,visitorName:r,visitorEmail:a});break}}}handleConversationMessage(e,t){let s=e.replace(/^conversation:(?:lr:|bc:|support:)?/,""),i=t;if(!i)return;let o=i.event??i.type,r=i.data??i;switch(o){case"new_message":{let a=r;this.cache.addMessage(s,a),this.emit("message",{message:a,conversationId:s});break}case"message_edited":{let a=r;this.cache.updateMessage(s,a),this.emit("message:updated",{message:a,conversationId:s});break}case"message_deleted":{let a=r.message_id??r.id;a&&(this.cache.removeMessage(s,a),this.emit("message:deleted",{messageId:a,conversationId:s}));break}case"user_typing":{let a=r.user_id;a&&this.emit("typing",{userId:a,conversationId:s});break}case"user_stopped_typing":{let a=r.user_id;a&&this.emit("typing:stop",{userId:a,conversationId:s});break}case"typing_batch":{let a=r.users??[];for(let h of a)this.emit("typing",{userId:h,conversationId:s});break}case"messages_read":{let a=r.user_id,h=r.last_read_at;a&&h&&this.emit("read",{userId:a,conversationId:s,lastReadAt:h});break}case"room_upgraded":{if(r.new_type==="large_room"){this.conversationTypes.set(s,"large_room");let h=this.conversationSubs.get(s);h&&(h(),this.conversationSubs.delete(s),this.subscribeToConversation(s)),this.emit("room_upgraded",{conversationId:s,newType:"large_room"})}break}}}async flushOfflineQueue(){let e=this.offlineQueue.drain();for(let t of e)await this.sendMessage(t.conversationId,{content:t.content,message_type:t.message_type})}};var T="0.0.1";var f={create(c){return new d(c)},version:T};var C=class extends HTMLElement{constructor(){super();this.client=null;this.unsub=null;this.shadow=this.attachShadow({mode:"open"})}static get observedAttributes(){return["api-key","conversation-id","api-base-url","embed-token"]}connectedCallback(){this.initialize()}disconnectedCallback(){this.cleanup()}attributeChangedCallback(){this.cleanup(),this.initialize()}initialize(){let e=this.getAttribute("api-key")??void 0,t=this.getAttribute("conversation-id"),s=this.getAttribute("api-base-url")??void 0,i=this.getAttribute("embed-token")??void 0;if(!e&&!i)return;let o={apiKey:e,embedToken:i,apiBaseUrl:s};this.client=new d(o),this.shadow.innerHTML=`
1
+ "use strict";var ScaleMuleChat=(()=>{var U=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var ee=Object.prototype.hasOwnProperty;var te=(c,n)=>{for(var e in n)U(c,e,{get:n[e],enumerable:!0})},se=(c,n,e,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of Z(n))!ee.call(c,s)&&s!==e&&U(c,s,{get:()=>n[s],enumerable:!(t=J(n,s))||t.enumerable});return c};var ne=c=>se(U({},"__esModule",{value:!0}),c);var xe={};te(xe,{ChatClient:()=>_,create:()=>Se,version:()=>Me});var y=class{constructor(){this.listeners=new Map}on(n,e){return this.listeners.has(n)||this.listeners.set(n,new Set),this.listeners.get(n).add(e),()=>this.off(n,e)}off(n,e){this.listeners.get(n)?.delete(e)}once(n,e){let t=(s=>{this.off(n,t),e(s)});return this.on(n,t)}emit(n,...[e]){let t=this.listeners.get(n);if(t)for(let s of t)try{s(e)}catch(i){console.error(`[ScaleMuleChat] Error in ${String(n)} listener:`,i)}}removeAllListeners(n){n?this.listeners.delete(n):this.listeners.clear()}};var H="https://api.scalemule.com";var M=class{constructor(n,e){this.cache=new Map;this.maxMessages=n??200,this.maxConversations=e??50}getMessages(n){return this.cache.get(n)??[]}getMessage(n,e){return this.getMessages(n).find(t=>t.id===e)}setMessages(n,e){this.cache.set(n,e.slice(0,this.maxMessages)),this.evictOldConversations()}addMessage(n,e){let t=this.cache.get(n)??[];t.some(s=>s.id===e.id)||(t.push(e),t.sort((s,i)=>s.created_at.localeCompare(i.created_at)),t.length>this.maxMessages&&t.splice(0,t.length-this.maxMessages),this.cache.set(n,t))}upsertMessage(n,e){if(this.getMessage(n,e.id)){this.updateMessage(n,e);return}this.addMessage(n,e)}updateMessage(n,e){let t=this.cache.get(n);if(!t)return;let s=t.findIndex(i=>i.id===e.id);s>=0&&(t[s]=e)}reconcileOptimisticMessage(n,e){let t=this.cache.get(n);if(!t)return e;let s=(e.attachments??[]).map(r=>r.file_id).sort(),i=t.findIndex(r=>{if(!r.id.startsWith("pending-")||r.sender_id!==e.sender_id||r.content!==e.content)return!1;let o=(r.attachments??[]).map(a=>a.file_id).sort();return o.length!==s.length?!1:o.every((a,d)=>a===s[d])});return i>=0&&(t[i]=e),e}removeMessage(n,e){let t=this.cache.get(n);if(!t)return;let s=t.findIndex(i=>i.id===e);s>=0&&t.splice(s,1)}clear(n){n?this.cache.delete(n):this.cache.clear()}evictOldConversations(){for(;this.cache.size>this.maxConversations;){let n=this.cache.keys().next().value;n&&this.cache.delete(n)}}};var j="scalemule_chat_offline_queue",x=class{constructor(n=!0){this.queue=[];this.enabled=n,this.enabled&&this.load()}enqueue(n,e,t="text",s){this.enabled&&(this.queue.push({conversationId:n,content:e,message_type:t,attachments:s,timestamp:Date.now()}),this.save())}drain(){let n=[...this.queue];return this.queue=[],this.save(),n}get size(){return this.queue.length}save(){try{typeof localStorage<"u"&&localStorage.setItem(j,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let n=localStorage.getItem(j);n&&(this.queue=JSON.parse(n))}}catch{this.queue=[]}}};var E=class{constructor(n){this.baseUrl=n.baseUrl.replace(/\/$/,""),this.apiKey=n.apiKey,this.getToken=n.getToken,this.timeout=n.timeout??1e4}async get(n){return this.request("GET",n)}async post(n,e){return this.request("POST",n,e)}async patch(n,e){return this.request("PATCH",n,e)}async del(n){return this.request("DELETE",n)}async request(n,e,t){let s={"Content-Type":"application/json"};if(this.apiKey&&(s["x-api-key"]=this.apiKey),this.getToken){let o=await this.getToken();o&&(s.Authorization=`Bearer ${o}`)}let i=new AbortController,r=setTimeout(()=>i.abort(),this.timeout);try{let o=await fetch(`${this.baseUrl}${e}`,{method:n,headers:s,body:t?JSON.stringify(t):void 0,signal:i.signal});if(clearTimeout(r),o.status===204)return{data:null,error:null};let a=await o.json().catch(()=>null);return o.ok?{data:a?.data!==void 0?a.data:a,error:null}:{data:null,error:{code:a?.error?.code??a?.code??"unknown",message:a?.error?.message??a?.message??o.statusText,status:o.status,details:a?.error?.details??a?.details}}}catch(o){return clearTimeout(r),{data:null,error:{code:"network_error",message:o instanceof Error?o.message:"Network error",status:0}}}}};var k=class extends y{constructor(e){super();this.ws=null;this.status="disconnected";this.subscriptions=new Set;this.presenceChannels=new Map;this.reconnectAttempt=0;this.reconnectTimer=null;this.heartbeatTimer=null;this.config=e,this.maxRetries=e.reconnect?.maxRetries??1/0,this.baseDelay=e.reconnect?.baseDelay??1e3,this.maxDelay=e.reconnect?.maxDelay??3e4;let t=e.baseUrl.replace(/\/$/,"");this.ticketUrl=`${t}/v1/realtime/ws/ticket`;let s=e.wsUrl?e.wsUrl.replace(/\/$/,""):t;this.wsBaseUrl=s.replace(/^http/,"ws")+"/v1/realtime/ws"}getStatus(){return this.status}async connect(){if(!(this.status==="connected"||this.status==="connecting")){this.setStatus("connecting");try{let e=await this.obtainTicket();if(!e){this.setStatus("disconnected"),this.emit("error",{message:"Failed to obtain WS ticket"});return}let t=`${this.wsBaseUrl}?ticket=${encodeURIComponent(e)}`;this.ws=new WebSocket(t),this.ws.onopen=()=>{this.setStatus("connected"),this.reconnectAttempt=0,this.startHeartbeat(),this.resubscribeAll()},this.ws.onmessage=s=>{this.handleMessage(s.data)},this.ws.onclose=()=>{this.stopHeartbeat(),this.status!=="disconnected"&&this.scheduleReconnect()},this.ws.onerror=()=>{}}catch{this.setStatus("disconnected"),this.scheduleReconnect()}}}disconnect(){this.setStatus("disconnected"),this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.onclose=null,this.ws.close(),this.ws=null)}subscribe(e){return this.subscriptions.add(e),this.status==="connected"?this.send({type:"subscribe",channel:e}):this.status==="disconnected"&&this.connect(),()=>this.unsubscribe(e)}unsubscribe(e){this.subscriptions.delete(e),this.status==="connected"&&this.send({type:"unsubscribe",channel:e})}publish(e,t){this.status==="connected"&&this.send({type:"publish",channel:e,data:t})}joinPresence(e,t){this.presenceChannels.set(e,t),this.status==="connected"&&this.send({type:"presence_join",channel:e,user_data:t??{}})}leavePresence(e){this.presenceChannels.delete(e),this.status==="connected"&&this.send({type:"presence_leave",channel:e})}async obtainTicket(){let e={"Content-Type":"application/json"};if(this.config.apiKey&&(e["x-api-key"]=this.config.apiKey),this.config.getToken){let t=await this.config.getToken();t&&(e.Authorization=`Bearer ${t}`)}try{let t=await fetch(this.ticketUrl,{method:"POST",headers:e});if(!t.ok)return null;let s=await t.json();return s.ticket??s.data?.ticket??null}catch{return null}}send(e){this.ws?.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}handleMessage(e){if(e!=="pong")try{let t=JSON.parse(e);switch(t.type){case"auth_success":break;case"subscribed":break;case"message":this.emit("message",{channel:t.channel,data:t.data});break;case"presence_state":this.emit("presence:state",{channel:t.channel,members:t.members??[]});break;case"presence_join":this.emit("presence:join",{channel:t.channel,user:t.user});break;case"presence_leave":this.emit("presence:leave",{channel:t.channel,userId:t.user_id});break;case"presence_update":this.emit("presence:update",{channel:t.channel,userId:t.user_id,status:t.status,userData:t.user_data});break;case"error":this.emit("error",{message:t.message??"Unknown error"});break;case"token_expiring":this.reconnectAttempt=0,this.scheduleReconnect();break}}catch{}}resubscribeAll(){for(let e of this.subscriptions)this.send({type:"subscribe",channel:e});for(let[e,t]of this.presenceChannels)this.send({type:"presence_join",channel:e,user_data:t??{}})}startHeartbeat(){this.heartbeatTimer=setInterval(()=>{this.ws?.readyState===WebSocket.OPEN&&this.ws.send("ping")},3e4)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.reconnectAttempt>=this.maxRetries){this.setStatus("disconnected");return}this.setStatus("reconnecting"),this.emit("reconnecting",{attempt:this.reconnectAttempt+1});let e=Math.min(this.baseDelay*Math.pow(2,this.reconnectAttempt)+Math.random()*this.baseDelay*.3,this.maxDelay);this.reconnectTimer=setTimeout(()=>{this.reconnectAttempt++,this.connect()},e)}setStatus(e){this.status!==e&&(this.status=e,this.emit("status",e))}};var he=[0,1e3,3e3],B=45e3,pe=new Set([0,408,429,500,502,503,504]);function ue(c){return new Promise(n=>setTimeout(n,c))}function ge(c,n){return n==="aborted"?!1:pe.has(c)||n==="upload_stalled"}function me(c,n,e,t){return new Promise(s=>{if(typeof XMLHttpRequest>"u"){s({data:null,error:{code:"unsupported_environment",message:"XMLHttpRequest is not available in this environment",status:0}});return}let i=new XMLHttpRequest,r=!1,o=null,a=0,d=n.size,v=b=>{r||(r=!0,o&&(clearTimeout(o),o=null),s(b))},C=()=>{o&&clearTimeout(o),o=setTimeout(()=>{i.abort(),v({data:null,error:{code:"upload_stalled",message:`Upload stalled (no progress for ${B/1e3}s)`,status:0,details:{bytes_sent:a,total_bytes:d}}})},B)};if(t){if(t.aborted){v({data:null,error:{code:"aborted",message:"Upload aborted",status:0}});return}t.addEventListener("abort",()=>{i.abort(),v({data:null,error:{code:"aborted",message:"Upload aborted",status:0}})},{once:!0})}i.upload.addEventListener("progress",b=>{C(),a=b.loaded,d=b.total||d,b.lengthComputable&&e?.(Math.round(b.loaded/b.total*100))}),i.addEventListener("load",()=>{if(i.status>=200&&i.status<300){e?.(100),v({data:null,error:null});return}v({data:null,error:{code:"upload_error",message:`S3 upload failed: ${i.status}`,status:i.status,details:{bytes_sent:a,total_bytes:d}}})}),i.addEventListener("error",()=>{v({data:null,error:{code:"upload_error",message:"S3 upload failed",status:i.status||0,details:{bytes_sent:a,total_bytes:d}}})}),i.addEventListener("abort",()=>{r||v({data:null,error:{code:"aborted",message:"Upload aborted",status:0}})}),i.open("PUT",c,!0),n.type&&i.setRequestHeader("Content-Type",n.type),C(),i.send(n)})}async function W(c,n,e,t){let s=null;for(let[i,r]of he.entries()){r>0&&await ue(r);let o=await me(c,n,e,t);if(!o.error)return o;if(s={...o.error,details:{...o.error.details,attempt:i+1}},!ge(o.error.status,o.error.code))break}return{data:null,error:s??{code:"upload_error",message:"Upload failed",status:0}}}var _=class extends y{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??H;this.currentUserId=e.userId,this.http=new E({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0)}),this.ws=new k({baseUrl:t,wsUrl:e.wsUrl,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0),reconnect:e.reconnect}),this.cache=new M(e.messageCache?.maxMessages,e.messageCache?.maxConversations),this.offlineQueue=new x(e.offlineQueue??!0),this.ws.on("status",s=>{switch(s){case"connected":this.emit("connected"),this.flushOfflineQueue();break;case"disconnected":this.emit("disconnected");break}}),this.ws.on("reconnecting",s=>{this.emit("reconnecting",s)}),this.ws.on("message",({channel:s,data:i})=>{this.handleRealtimeMessage(s,i)}),this.ws.on("presence:state",({channel:s,members:i})=>{let r=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:state",{conversationId:r,members:i})}),this.ws.on("presence:join",({channel:s,user:i})=>{let r=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:join",{userId:i.user_id,conversationId:r,userData:i.user_data})}),this.ws.on("presence:leave",({channel:s,userId:i})=>{let r=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:leave",{userId:i,conversationId:r})}),this.ws.on("presence:update",({channel:s,userId:i,status:r,userData:o})=>{let a=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:update",{userId:i,conversationId:a,status:r,userData:o})}),this.ws.on("error",({message:s})=>{this.emit("error",{code:"ws_error",message:s})})}get status(){return this.ws.getStatus()}get userId(){return this.currentUserId}connect(){this.ws.connect()}disconnect(){for(let e of this.conversationSubs.values())e();this.conversationSubs.clear(),this.ws.disconnect()}async createConversation(e){let t={...e,conversation_type:e.conversation_type??"direct"},s=await this.http.post("/v1/chat/conversations",t);return s.data&&this.trackConversationType(s.data),s}async listConversations(e){let t=new URLSearchParams;e?.page&&t.set("page",String(e.page)),e?.per_page&&t.set("per_page",String(e.per_page)),e?.conversation_type&&t.set("conversation_type",e.conversation_type);let s=t.toString(),i=await this.http.get(`/v1/chat/conversations${s?"?"+s:""}`);return i.data&&i.data.forEach(r=>this.trackConversationType(r)),i}async getConversation(e){let t=await this.http.get(`/v1/chat/conversations/${e}`);return t.data&&this.trackConversationType(t.data),t}trackConversationType(e){e.conversation_type!=="direct"&&e.conversation_type!=="group"&&this.conversationTypes.set(e.id,e.conversation_type)}async sendMessage(e,t){let s=await this.http.post(`/v1/chat/conversations/${e}/messages`,{content:t.content,message_type:t.message_type??"text",attachments:t.attachments});if(s.data){let i=this.cache.reconcileOptimisticMessage(e,s.data);this.cache.upsertMessage(e,i)}else s.error?.status===0&&this.offlineQueue.enqueue(e,t.content,t.message_type??"text",t.attachments);return s}async getMessages(e,t){let s=new URLSearchParams;t?.limit&&s.set("limit",String(t.limit)),t?.before&&s.set("before",t.before),t?.after&&s.set("after",t.after);let i=s.toString(),r=await this.http.get(`/v1/chat/conversations/${e}/messages${i?"?"+i:""}`);return r.data?.messages&&(t?.after||(r.data.messages=r.data.messages.slice().reverse()),!t?.before&&!t?.after&&this.cache.setMessages(e,r.data.messages)),r}async editMessage(e,t){return this.http.patch(`/v1/chat/messages/${e}`,{content:t})}async deleteMessage(e){return this.http.del(`/v1/chat/messages/${e}`)}async uploadAttachment(e,t,s){let i=typeof File<"u"&&e instanceof File?e.name:"attachment",r=e.type||"application/octet-stream",o=await this.http.post("/v1/storage/signed-url/upload",{filename:i,content_type:r,size_bytes:e.size,is_public:!1,metadata:{source:"chat_sdk"}});if(o.error||!o.data)return{data:null,error:o.error};let a=await W(o.data.upload_url,e,t,s);if(a.error)return{data:null,error:a.error};let d=await this.http.post("/v1/storage/signed-url/complete",{file_id:o.data.file_id,completion_token:o.data.completion_token});return d.error||!d.data?{data:null,error:d.error}:{data:{file_id:d.data.file_id,file_name:d.data.filename,file_size:d.data.size_bytes,mime_type:d.data.content_type,presigned_url:d.data.url},error:null}}async refreshAttachmentUrl(e,t){return this.http.get(`/v1/chat/messages/${e}/attachment/${t}/url`)}getCachedMessages(e){return this.cache.getMessages(e)}stageOptimisticMessage(e,t){return this.cache.upsertMessage(e,t),t}async addReaction(e,t){return this.http.post(`/v1/chat/messages/${e}/reactions`,{emoji:t})}async removeReaction(e,t){return this.http.del(`/v1/chat/messages/${e}/reactions/${encodeURIComponent(t)}`)}async reportMessage(e,t,s){return this.http.post(`/v1/chat/messages/${e}/report`,{reason:t,description:s})}async muteConversation(e,t){return this.http.post(`/v1/chat/conversations/${e}/mute`,{muted_until:t})}async unmuteConversation(e){return this.http.del(`/v1/chat/conversations/${e}/mute`)}async getUnreadTotal(){return this.http.get("/v1/chat/conversations/unread-total")}async sendTyping(e,t=!0){await this.http.post(`/v1/chat/conversations/${e}/typing`,{is_typing:t})}async markRead(e){await this.http.post(`/v1/chat/conversations/${e}/read`)}async getReadStatus(e){return this.http.get(`/v1/chat/conversations/${e}/read-status`)}async addParticipant(e,t){return this.http.post(`/v1/chat/conversations/${e}/participants`,{user_id:t})}async removeParticipant(e,t){return this.http.del(`/v1/chat/conversations/${e}/participants/${t}`)}joinPresence(e,t){let s=this.channelName(e);this.ws.joinPresence(s,t)}leavePresence(e){let t=this.channelName(e);this.ws.leavePresence(t)}updatePresence(e,t,s){let i=this.channelName(e);this.ws.send({type:"presence_update",channel:i,status:t,user_data:s})}async getChannelSettings(e){return this.http.get(`/v1/chat/channels/${e}/settings`)}setConversationType(e,t){this.conversationTypes.set(e,t)}async findChannelBySessionId(e){let t=await this.http.get(`/v1/chat/channels/by-session?linked_session_id=${encodeURIComponent(e)}`);return t.error?.status===404?null:(t.data&&this.conversationTypes.set(t.data.id,t.data.channel_type),t.data)}async joinChannel(e){return this.http.post(`/v1/chat/channels/${e}/join`,{})}async createEphemeralChannel(e){let t=await this.http.post("/v1/chat/channels/ephemeral",e);return t.data&&this.conversationTypes.set(t.data.id,"ephemeral"),t}async createLargeRoom(e){let t=await this.http.post("/v1/chat/channels/large-room",e);return t.data&&this.conversationTypes.set(t.data.id,"large_room"),t}async getSubscriberCount(e){return(await this.http.get(`/v1/chat/conversations/${e}/subscriber-count`)).data?.count??0}subscribeToConversation(e){if(this.conversationSubs.has(e))return this.conversationSubs.get(e);let t=this.channelName(e),s=this.ws.subscribe(t);return this.conversationSubs.set(e,s),()=>{this.conversationSubs.delete(e),s()}}channelName(e){switch(this.conversationTypes.get(e)){case"large_room":return`conversation:lr:${e}`;case"broadcast":return`conversation:bc:${e}`;case"support":return`conversation:support:${e}`;default:return`conversation:${e}`}}destroy(){this.disconnect(),this.cache.clear(),this.removeAllListeners()}handleRealtimeMessage(e,t){if(e.startsWith("conversation:")){this.handleConversationMessage(e,t);return}if(e.startsWith("private:")){this.handlePrivateMessage(t);return}}handlePrivateMessage(e){let t=e;if(!t)return;let s=t.event??t.type,i=t.data??t;switch(s){case"new_message":{let r=i.conversation_id,o=i.id??i.message_id,a=i.sender_id,d=i.content??"";r&&this.emit("inbox:update",{conversationId:r,messageId:o,senderId:a,preview:d});break}case"support:new_conversation":{let r=i.conversation_id,o=i.visitor_name;r&&this.emit("support:new",{conversationId:r,visitorName:o});break}case"support:assigned":{let r=i.conversation_id,o=i.visitor_name,a=i.visitor_email;r&&this.emit("support:assigned",{conversationId:r,visitorName:o,visitorEmail:a});break}}}normalizeMessage(e){return{id:e.id??e.message_id??"",content:e.content??"",message_type:e.message_type??"text",sender_id:e.sender_id??e.sender_user_id??"",sender_type:e.sender_type,sender_agent_model:e.sender_agent_model,attachments:e.attachments,reactions:e.reactions,is_edited:!!(e.is_edited??!1),created_at:e.created_at??e.updated_at??e.timestamp??new Date().toISOString()}}buildEditedMessage(e,t){let s=t.message_id??t.id;if(!s)return null;let i=this.cache.getMessage(e,s);return{id:i?.id??s,content:t.content??t.new_content??i?.content??"",message_type:i?.message_type??"text",sender_id:i?.sender_id??"",sender_type:i?.sender_type,sender_agent_model:i?.sender_agent_model,attachments:i?.attachments,reactions:i?.reactions,is_edited:!0,created_at:i?.created_at??t.updated_at??t.timestamp??new Date().toISOString()}}applyReactionEvent(e,t){let s={id:`${t.message_id}:${t.user_id}:${t.emoji}`,message_id:t.message_id,user_id:t.user_id,emoji:t.emoji,action:t.action,timestamp:t.timestamp},i=this.cache.getMessage(e,t.message_id);if(!i)return s;let r=[...i.reactions??[]],o=r.findIndex(a=>a.emoji===t.emoji);if(t.action==="added")if(o>=0){let a=r[o];if(!a.user_ids.includes(t.user_id)){let d=[...a.user_ids,t.user_id];r[o]={...a,user_ids:d,count:d.length}}}else r.push({emoji:t.emoji,count:1,user_ids:[t.user_id]});else if(o>=0){let a=r[o],d=a.user_ids.filter(v=>v!==t.user_id);d.length===0?r.splice(o,1):r[o]={...a,user_ids:d,count:d.length}}return this.cache.updateMessage(e,{...i,reactions:r}),s}handleConversationMessage(e,t){let s=e.replace(/^conversation:(?:lr:|bc:|support:)?/,""),i=t;if(!i)return;let r=i.event??i.type,o=i.data??i;switch(r){case"new_message":{let a=this.normalizeMessage(o),d=this.cache.reconcileOptimisticMessage(s,a);this.cache.upsertMessage(s,d),this.emit("message",{message:d,conversationId:s});break}case"message_edited":{let a=o,d=this.buildEditedMessage(s,a);d&&(this.cache.upsertMessage(s,d),this.emit("message:updated",{message:d,conversationId:s,update:a}));break}case"reaction":{let a=o;if(!a.message_id||!a.user_id||!a.emoji)break;let d=this.applyReactionEvent(s,a);this.emit("reaction",{reaction:d,conversationId:s,action:a.action});break}case"message_deleted":{let a=o.message_id??o.id;a&&(this.cache.removeMessage(s,a),this.emit("message:deleted",{messageId:a,conversationId:s}));break}case"user_typing":{let a=o.user_id;a&&this.emit("typing",{userId:a,conversationId:s});break}case"user_stopped_typing":{let a=o.user_id;a&&this.emit("typing:stop",{userId:a,conversationId:s});break}case"typing_batch":{let a=o.users??[];for(let d of a)this.emit("typing",{userId:d,conversationId:s});break}case"messages_read":{let a=o.user_id,d=o.last_read_at;a&&d&&this.emit("read",{userId:a,conversationId:s,lastReadAt:d});break}case"room_upgraded":{if(o.new_type==="large_room"){this.conversationTypes.set(s,"large_room");let d=this.conversationSubs.get(s);d&&(d(),this.conversationSubs.delete(s),this.subscribeToConversation(s)),this.emit("room_upgraded",{conversationId:s,newType:"large_room"})}break}}}async flushOfflineQueue(){let e=this.offlineQueue.drain();for(let t of e)await this.sendMessage(t.conversationId,{content:t.content,message_type:t.message_type,attachments:t.attachments})}};var F="0.0.7";var A={create(c){return new _(c)},version:F};var R=class extends y{constructor(e,t){super();this.typingTimers=new Map;this.unsubscribers=[];this.client=e,this.conversationId=t,this.state={conversationId:t,messages:[],readStatuses:[],typingUsers:[],members:[],hasMore:!1,isLoading:!0,error:null}}getState(){return this.state}async init(e={}){let t=e.realtime??!0,s=e.presence??t;this.bindEvents(),t&&this.client.connect();try{await this.client.getConversation(this.conversationId);let[i,r]=await Promise.all([this.client.getMessages(this.conversationId),this.client.getReadStatus(this.conversationId)]);return this.state={...this.state,messages:i.data?.messages??[],readStatuses:r.data?.statuses??[],hasMore:i.data?.has_more??!1,isLoading:!1,error:i.error?.message??r.error?.message??null},t&&this.unsubscribers.push(this.client.subscribeToConversation(this.conversationId)),s&&this.client.joinPresence(this.conversationId),this.emit("state",this.state),this.emit("ready",this.state),this.state}catch(i){let r=i instanceof Error?i.message:"Failed to initialize chat controller";throw this.state={...this.state,isLoading:!1,error:r},this.emit("state",this.state),this.emit("error",{message:r}),i}}async loadMore(){let e=this.state.messages[0]?.id;if(!e)return;let t=await this.client.getMessages(this.conversationId,{before:e});t.data?.messages?.length&&(this.state={...this.state,messages:[...t.data.messages,...this.state.messages],hasMore:t.data.has_more??!1},this.emit("state",this.state))}async sendMessage(e,t=[]){let s=t.length>0?t.every(r=>r.mime_type.startsWith("image/"))&&!e?"image":"file":"text",i=await this.client.sendMessage(this.conversationId,{content:e,attachments:t,message_type:s});if(i.error)throw new Error(i.error.message)}stageOptimisticMessage(e){let t=this.client.stageOptimisticMessage(this.conversationId,e);return this.patchState({messages:[...this.client.getCachedMessages(this.conversationId)]}),t}async uploadAttachment(e,t,s){return this.client.uploadAttachment(e,t,s)}async refreshAttachmentUrl(e,t){return this.client.refreshAttachmentUrl(e,t)}async addReaction(e,t){let s=await this.client.addReaction(e,t);if(s.error)throw new Error(s.error.message)}async removeReaction(e,t){let s=await this.client.removeReaction(e,t);if(s.error)throw new Error(s.error.message)}async reportMessage(e,t,s){return this.client.reportMessage(e,t,s)}async muteConversation(e){return this.client.muteConversation(this.conversationId,e)}async unmuteConversation(){return this.client.unmuteConversation(this.conversationId)}async markRead(){await this.client.markRead(this.conversationId)}async refreshReadStatus(){let e=await this.client.getReadStatus(this.conversationId);if(e.data?.statuses)return this.patchState({readStatuses:e.data.statuses}),e.data.statuses;if(e.error)throw new Error(e.error.message);return this.state.readStatuses}sendTyping(e=!0){this.client.sendTyping(this.conversationId,e)}destroy(){this.client.leavePresence(this.conversationId);for(let e of this.typingTimers.values())clearTimeout(e);this.typingTimers.clear();for(let e of this.unsubscribers)e();this.unsubscribers=[],this.removeAllListeners()}bindEvents(){this.unsubscribers.length||(this.unsubscribers.push(this.client.on("message",({conversationId:e})=>{e===this.conversationId&&this.patchState({messages:[...this.client.getCachedMessages(this.conversationId)]})})),this.unsubscribers.push(this.client.on("message:updated",({conversationId:e})=>{e===this.conversationId&&this.patchState({messages:[...this.client.getCachedMessages(this.conversationId)]})})),this.unsubscribers.push(this.client.on("message:deleted",({conversationId:e})=>{e===this.conversationId&&this.patchState({messages:[...this.client.getCachedMessages(this.conversationId)]})})),this.unsubscribers.push(this.client.on("reaction",({conversationId:e})=>{e===this.conversationId&&this.patchState({messages:[...this.client.getCachedMessages(this.conversationId)]})})),this.unsubscribers.push(this.client.on("typing",({conversationId:e,userId:t})=>{if(e!==this.conversationId)return;this.patchState({typingUsers:this.state.typingUsers.includes(t)?this.state.typingUsers:[...this.state.typingUsers,t]});let s=this.typingTimers.get(t);s&&clearTimeout(s),this.typingTimers.set(t,setTimeout(()=>{this.patchState({typingUsers:this.state.typingUsers.filter(i=>i!==t)}),this.typingTimers.delete(t)},3e3))})),this.unsubscribers.push(this.client.on("typing:stop",({conversationId:e,userId:t})=>{if(e!==this.conversationId)return;let s=this.typingTimers.get(t);s&&(clearTimeout(s),this.typingTimers.delete(t)),this.patchState({typingUsers:this.state.typingUsers.filter(i=>i!==t)})})),this.unsubscribers.push(this.client.on("read",({conversationId:e,userId:t,lastReadAt:s})=>{if(e!==this.conversationId)return;let i=[...this.state.readStatuses],r=i.findIndex(o=>o.user_id===t);r>=0?i[r]={...i[r],last_read_at:s}:i.push({user_id:t,last_read_at:s}),this.patchState({readStatuses:i})})),this.unsubscribers.push(this.client.on("presence:state",({conversationId:e,members:t})=>{e===this.conversationId&&this.patchState({members:t.map(s=>({userId:s.user_id,status:s.status??"online",userData:s.user_data}))})})),this.unsubscribers.push(this.client.on("presence:join",({conversationId:e,userId:t,userData:s})=>{e===this.conversationId&&(this.state.members.some(i=>i.userId===t)||this.patchState({members:[...this.state.members,{userId:t,status:"online",userData:s}]}))})),this.unsubscribers.push(this.client.on("presence:leave",({conversationId:e,userId:t})=>{e===this.conversationId&&this.patchState({members:this.state.members.filter(s=>s.userId!==t)})})),this.unsubscribers.push(this.client.on("presence:update",({conversationId:e,userId:t,status:s,userData:i})=>{e===this.conversationId&&this.patchState({members:this.state.members.map(r=>r.userId===t?{...r,status:s,userData:i}:r)})})))}patchState(e){this.state={...this.state,...e,error:e.error!==void 0?e.error:this.state.error},this.emit("state",this.state)}};var fe=["\u{1F44D}","\u2764\uFE0F","\u{1F602}","\u{1F389}","\u{1F62E}","\u{1F440}"];function p(c){return String(c??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function ve(c){if(!c)return null;try{let n=new URL(c,typeof window<"u"?window.location.href:"https://scalemule.com");if(["http:","https:","blob:"].includes(n.protocol))return n.toString()}catch{return null}return null}function be(c){let n=c?.trim();if(!n)return"#2563eb";if(typeof document<"u"){let e=document.createElement("span").style;if(e.color="",e.color=n,e.color)return n}return/^#[0-9a-f]{3,8}$/i.test(n)?n:"#2563eb"}function ye(c){return new Date(c).toLocaleDateString([],{month:"short",day:"numeric",year:"numeric"})}function _e(c,n){let e=new Date(c),t=new Date(n);return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()&&e.getDate()===t.getDate()}function we(c,n){return n?c.findIndex(e=>new Date(e.created_at).getTime()>new Date(n).getTime()):-1}function Ce(c){let n=p(c.file_name),e=ve(c.presigned_url??void 0);return e?c.mime_type.startsWith("image/")?`<img class="attachment attachment-image" src="${p(e)}" alt="${n}" />`:c.mime_type.startsWith("video/")?`<video class="attachment attachment-video" src="${p(e)}" controls></video>`:c.mime_type.startsWith("audio/")?`<audio class="attachment attachment-audio" src="${p(e)}" controls></audio>`:`<a class="attachment attachment-link" href="${p(e)}" target="_blank" rel="noreferrer">${n}</a>`:`<div class="attachment attachment-link">${n}</div>`}function Te(c,n,e=!1){let t=!!(n&&c.sender_id===n),s=(c.attachments??[]).map(Ce).join(""),i=c.is_edited?'<span class="message-edited">edited</span>':"",r=(c.reactions??[]).map(a=>{let d=!!(n&&a.user_ids.includes(n));return`
2
+ <button
3
+ class="reaction-badge ${d?"reaction-badge-active":""}"
4
+ type="button"
5
+ data-action="toggle-reaction"
6
+ data-message-id="${p(c.id)}"
7
+ data-emoji="${p(a.emoji)}"
8
+ data-reacted="${d?"true":"false"}"
9
+ >
10
+ ${p(a.emoji)} ${a.count}
11
+ </button>
12
+ `}).join(""),o=e?`
13
+ <div class="reaction-picker">
14
+ ${fe.map(a=>`
15
+ <button
16
+ class="reaction-picker-btn"
17
+ type="button"
18
+ data-action="add-reaction"
19
+ data-message-id="${p(c.id)}"
20
+ data-emoji="${p(a)}"
21
+ >
22
+ ${p(a)}
23
+ </button>
24
+ `).join("")}
25
+ </div>
26
+ `:"";return`
27
+ <div class="message ${t?"message-own":"message-other"}">
28
+ <div class="message-bubble">
29
+ ${c.content?`<div class="message-content">${p(c.content)}</div>`:""}
30
+ ${s}
31
+ </div>
32
+ <div class="message-meta">
33
+ <span>${new Date(c.created_at).toLocaleTimeString([],{hour:"numeric",minute:"2-digit"})}</span>
34
+ ${i}
35
+ <button class="message-action" type="button" data-action="toggle-picker" data-message-id="${p(c.id)}">React</button>
36
+ </div>
37
+ ${r?`<div class="reactions">${r}</div>`:""}
38
+ ${o}
39
+ </div>
40
+ `}var L=class extends HTMLElement{constructor(){super();this.client=null;this.controller=null;this.cleanupFns=[];this.pendingAttachments=[];this.currentState=null;this.openReactionMessageId=null;this.didScrollToUnread=!1;this.shadow=this.attachShadow({mode:"open"})}static get observedAttributes(){return["api-key","conversation-id","api-base-url","embed-token","ws-url","theme-color"]}connectedCallback(){this.initialize()}disconnectedCallback(){this.cleanup()}attributeChangedCallback(){this.cleanup(),this.initialize()}initialize(){let e=this.getAttribute("api-key")??void 0,t=this.getAttribute("conversation-id"),s=this.getAttribute("api-base-url")??void 0,i=this.getAttribute("embed-token")??void 0,r=this.getAttribute("ws-url")??void 0,o=be(this.getAttribute("theme-color")??"#2563eb");if(!e&&!i||!t)return;let a={apiKey:e,embedToken:i,apiBaseUrl:s,wsUrl:r};this.client=new _(a),this.controller=new R(this.client,t),this.shadow.innerHTML=`
2
41
  <style>
3
- :host { display: block; width: 100%; height: 100%; }
4
- .chat-container { width: 100%; height: 100%; display: flex; flex-direction: column; font-family: system-ui, sans-serif; }
5
- .messages { flex: 1; overflow-y: auto; padding: 8px; }
6
- .message { margin: 4px 0; padding: 6px 10px; background: #f0f0f0; border-radius: 8px; max-width: 80%; }
7
- .input-area { display: flex; padding: 8px; border-top: 1px solid #e0e0e0; }
8
- .input-area input { flex: 1; padding: 8px; border: 1px solid #d0d0d0; border-radius: 6px; outline: none; }
9
- .input-area button { margin-left: 8px; padding: 8px 16px; background: #0066ff; color: white; border: none; border-radius: 6px; cursor: pointer; }
42
+ :host { display: block; width: 100%; height: 100%; color: #111827; }
43
+ .chat-container {
44
+ --sm-primary: ${o};
45
+ --sm-primary-muted: color-mix(in srgb, var(--sm-primary) 10%, white);
46
+ width: 100%;
47
+ height: 100%;
48
+ display: flex;
49
+ flex-direction: column;
50
+ font-family: system-ui, sans-serif;
51
+ background: #fff;
52
+ border: 1px solid #e5e7eb;
53
+ border-radius: 16px;
54
+ overflow: hidden;
55
+ }
56
+ .header {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: space-between;
60
+ padding: 14px 16px;
61
+ border-bottom: 1px solid #e5e7eb;
62
+ background: #fff;
63
+ }
64
+ .header-title {
65
+ font-size: 15px;
66
+ font-weight: 700;
67
+ }
68
+ .header-status-copy {
69
+ font-size: 12px;
70
+ color: #6b7280;
71
+ margin-top: 2px;
72
+ }
73
+ .header-presence {
74
+ display: inline-flex;
75
+ align-items: center;
76
+ gap: 8px;
77
+ font-size: 12px;
78
+ color: #6b7280;
79
+ white-space: nowrap;
80
+ }
81
+ .header-presence-dot {
82
+ width: 10px;
83
+ height: 10px;
84
+ border-radius: 999px;
85
+ background: #94a3b8;
86
+ }
87
+ .header-presence-dot.online {
88
+ background: #22c55e;
89
+ }
90
+ .messages {
91
+ flex: 1;
92
+ overflow-y: auto;
93
+ padding: 16px;
94
+ background: #f8fafc;
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 12px;
98
+ }
99
+ .message {
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 6px;
103
+ max-width: 82%;
104
+ }
105
+ .message-own { align-self: flex-end; }
106
+ .message-other { align-self: flex-start; }
107
+ .message-bubble {
108
+ padding: 10px 12px;
109
+ border-radius: 16px;
110
+ background: #f3f4f6;
111
+ color: #111827;
112
+ white-space: pre-wrap;
113
+ word-break: break-word;
114
+ }
115
+ .message-own .message-bubble {
116
+ background: var(--sm-primary);
117
+ color: #fff;
118
+ }
119
+ .message-meta {
120
+ display: flex;
121
+ gap: 8px;
122
+ align-items: center;
123
+ font-size: 12px;
124
+ color: #6b7280;
125
+ }
126
+ .message-edited { text-transform: lowercase; }
127
+ .message-action {
128
+ border: none;
129
+ background: transparent;
130
+ color: inherit;
131
+ cursor: pointer;
132
+ font-size: 12px;
133
+ padding: 0;
134
+ }
135
+ .date-divider,
136
+ .unread-divider {
137
+ align-self: center;
138
+ font-size: 12px;
139
+ color: #6b7280;
140
+ }
141
+ .date-divider {
142
+ padding: 4px 10px;
143
+ border-radius: 999px;
144
+ background: rgba(148, 163, 184, 0.12);
145
+ }
146
+ .unread-divider {
147
+ width: 100%;
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 10px;
151
+ color: var(--sm-primary);
152
+ font-weight: 600;
153
+ }
154
+ .unread-divider::before,
155
+ .unread-divider::after {
156
+ content: '';
157
+ flex: 1;
158
+ height: 1px;
159
+ background: color-mix(in srgb, var(--sm-primary) 28%, white);
160
+ }
161
+ .attachment {
162
+ display: block;
163
+ width: 100%;
164
+ margin-top: 8px;
165
+ border-radius: 12px;
166
+ }
167
+ .attachment-image,
168
+ .attachment-video { max-width: 320px; }
169
+ .attachment-link { color: inherit; }
170
+ .reactions {
171
+ display: flex;
172
+ gap: 6px;
173
+ flex-wrap: wrap;
174
+ }
175
+ .reaction-badge,
176
+ .reaction-picker-btn {
177
+ border: 1px solid #dbe3ef;
178
+ border-radius: 999px;
179
+ background: #fff;
180
+ padding: 4px 8px;
181
+ cursor: pointer;
182
+ font-size: 12px;
183
+ }
184
+ .reaction-badge-active {
185
+ border-color: color-mix(in srgb, var(--sm-primary) 40%, white);
186
+ background: color-mix(in srgb, var(--sm-primary) 10%, white);
187
+ }
188
+ .reaction-picker {
189
+ display: flex;
190
+ flex-wrap: wrap;
191
+ gap: 6px;
192
+ }
193
+ .typing {
194
+ min-height: 20px;
195
+ padding: 0 16px 10px;
196
+ font-size: 12px;
197
+ color: #6b7280;
198
+ }
199
+ .attachments {
200
+ display: flex;
201
+ flex-wrap: wrap;
202
+ gap: 8px;
203
+ padding: 0 12px 12px;
204
+ }
205
+ .attachment-chip {
206
+ display: inline-flex;
207
+ align-items: center;
208
+ gap: 8px;
209
+ padding: 6px 10px;
210
+ border-radius: 999px;
211
+ border: 1px solid #dbe3ef;
212
+ background: #f8fafc;
213
+ font-size: 12px;
214
+ }
215
+ .attachment-chip-error {
216
+ border-color: #fecaca;
217
+ background: #fef2f2;
218
+ color: #b91c1c;
219
+ }
220
+ .attachment-remove {
221
+ border: none;
222
+ background: transparent;
223
+ color: inherit;
224
+ cursor: pointer;
225
+ font: inherit;
226
+ }
227
+ .input-area {
228
+ display: flex;
229
+ gap: 10px;
230
+ padding: 12px;
231
+ border-top: 1px solid #e5e7eb;
232
+ background: #fff;
233
+ }
234
+ .input-area textarea {
235
+ flex: 1;
236
+ min-height: 44px;
237
+ resize: vertical;
238
+ border: 1px solid #d1d5db;
239
+ border-radius: 12px;
240
+ padding: 10px 12px;
241
+ font: inherit;
242
+ outline: none;
243
+ }
244
+ .input-area button {
245
+ border: none;
246
+ border-radius: 12px;
247
+ padding: 0 16px;
248
+ background: var(--sm-primary);
249
+ color: white;
250
+ cursor: pointer;
251
+ }
252
+ .input-area .attach-btn {
253
+ background: var(--sm-primary-muted);
254
+ color: #0f172a;
255
+ }
256
+ .empty {
257
+ color: #6b7280;
258
+ font-size: 14px;
259
+ padding: 32px 0;
260
+ text-align: center;
261
+ }
10
262
  </style>
11
263
  <div class="chat-container">
12
- <div class="messages" id="messages"></div>
264
+ <div class="header">
265
+ <div>
266
+ <div class="header-title">Chat</div>
267
+ <div class="header-status-copy" id="status-copy">Loading conversation\u2026</div>
268
+ </div>
269
+ <div class="header-presence">
270
+ <span class="header-presence-dot" id="presence-dot"></span>
271
+ <span id="presence-label">Away</span>
272
+ </div>
273
+ </div>
274
+ <div class="messages" id="messages" role="log" aria-live="polite"></div>
275
+ <div class="typing" id="typing" aria-live="polite"></div>
276
+ <div class="attachments" id="attachments" aria-live="polite"></div>
13
277
  <div class="input-area">
14
- <input type="text" placeholder="Type a message..." id="input" />
15
- <button id="send">Send</button>
278
+ <input type="file" id="file-input" hidden multiple accept="image/*,video/*,audio/*" aria-label="Attach files" />
279
+ <button class="attach-btn" id="attach" type="button" aria-label="Attach files">Attach</button>
280
+ <textarea placeholder="Type a message..." id="input" aria-label="Message"></textarea>
281
+ <button id="send" aria-label="Send message">Send</button>
16
282
  </div>
17
283
  </div>
18
- `;let r=this.shadow.getElementById("messages"),a=this.shadow.getElementById("input"),h=this.shadow.getElementById("send");this.client.on("message",({message:p})=>{this.appendMessage(r,p),this.dispatchEvent(new CustomEvent("chat-message",{detail:p,composed:!0,bubbles:!0}))});let y=()=>{let p=a.value.trim();!p||!t||!this.client||(this.client.sendMessage(t,{content:p}),a.value="")};h.addEventListener("click",y),a.addEventListener("keydown",p=>{p.key==="Enter"&&y()});let k=this.client;t&&(this.client.getConversation(t).then(()=>{this.client===k&&(this.unsub=this.client.subscribeToConversation(t),this.client.connect())}),this.client.getMessages(t).then(p=>{if(p.data?.messages)for(let E of p.data.messages)this.appendMessage(r,E)}))}appendMessage(e,t){let s=document.createElement("div");s.className="message",s.textContent=t.content,e.appendChild(s),e.scrollTop=e.scrollHeight}cleanup(){this.unsub?.(),this.unsub=null,this.client?.destroy(),this.client=null,this.shadow.innerHTML=""}};typeof customElements<"u"&&!customElements.get("scalemule-chat")&&customElements.define("scalemule-chat",C);var K=f.create.bind(f),W=f.version;return P(H);})();
284
+ `;let d=this.shadow.getElementById("messages"),v=this.shadow.getElementById("typing"),C=this.shadow.getElementById("attachments"),b=this.shadow.getElementById("status-copy"),K=this.shadow.getElementById("presence-dot"),z=this.shadow.getElementById("presence-label"),I=this.shadow.getElementById("input"),T=this.shadow.getElementById("file-input"),q=this.shadow.getElementById("attach"),Q=this.shadow.getElementById("send"),S=()=>{C.innerHTML=this.pendingAttachments.length?this.pendingAttachments.map(l=>`
285
+ <div class="attachment-chip ${l.error?"attachment-chip-error":""}">
286
+ <span>${p(l.fileName)}</span>
287
+ <span>${p(l.error??`${l.progress}%`)}</span>
288
+ <button class="attachment-remove" type="button" data-pending-id="${p(l.id)}" aria-label="Remove attachment">x</button>
289
+ </div>
290
+ `).join(""):"",C.querySelectorAll("[data-pending-id]").forEach(l=>{l.addEventListener("click",()=>{let h=l.dataset.pendingId;h&&(this.pendingAttachments=this.pendingAttachments.filter(u=>u.id!==h),S())})})},$=l=>{this.currentState=l;let h=this.client?.userId,u=l.members.filter(g=>g.userId!==h),f=u.some(g=>g.status==="online"),m=u.filter(g=>g.status==="online").length,w=h?l.readStatuses.find(g=>g.user_id===h)?.last_read_at:void 0,X=we(l.messages,w);K.className=`header-presence-dot ${f?"online":""}`,z.textContent=f?"Online":"Away",b.textContent=l.error?l.error:l.typingUsers.some(g=>g!==h)?"Someone is typing\u2026":m?`${m} online`:"No one online",l.messages.length?d.innerHTML=l.messages.map((g,O)=>{let N=l.messages[O-1],V=!N||!_e(N.created_at,g.created_at),G=X===O;return`
291
+ ${V?`<div class="date-divider">${p(ye(g.created_at))}</div>`:""}
292
+ ${G?'<div class="unread-divider" id="unread-divider">New messages</div>':""}
293
+ ${Te(g,h,this.openReactionMessageId===g.id)}
294
+ `}).join(""):d.innerHTML=`<div class="empty">${p(l.error??(l.isLoading?"Loading messages...":"Start the conversation"))}</div>`,v.textContent=l.typingUsers.some(g=>g!==h)?"Someone is typing...":"";let D=d.querySelector("#unread-divider");D&&!this.didScrollToUnread?(D.scrollIntoView({block:"center"}),this.didScrollToUnread=!0):d.scrollTop=d.scrollHeight};this.cleanupFns.push(this.controller.on("state",l=>{$(l)})),this.cleanupFns.push(this.client.on("message",()=>{this.dispatchEvent(new CustomEvent("chat-message",{composed:!0,bubbles:!0}))})),d.addEventListener("click",l=>{let h=l.target.closest("[data-action]");if(!h||!this.controller||!this.currentState)return;let u=h.dataset.action,f=h.dataset.messageId;if(!f)return;if(u==="toggle-picker"){this.openReactionMessageId=this.openReactionMessageId===f?null:f,$(this.currentState);return}let m=h.dataset.emoji;if(m){if(u==="add-reaction"){this.openReactionMessageId=null,this.controller.addReaction(f,m);return}u==="toggle-reaction"&&(h.dataset.reacted==="true"?this.controller.removeReaction(f,m):this.controller.addReaction(f,m))}});let Y=async l=>{if(this.controller)for(let h of Array.from(l)){let u=`${h.name}:${h.size}:${Date.now()}:${Math.random().toString(36).slice(2)}`;this.pendingAttachments=[...this.pendingAttachments,{id:u,fileName:h.name,progress:0}],S();let f=await this.controller.uploadAttachment(h,m=>{this.pendingAttachments=this.pendingAttachments.map(w=>w.id===u?{...w,progress:m}:w),S()});this.pendingAttachments=this.pendingAttachments.map(m=>m.id!==u?m:f?.data?{...m,progress:100,attachment:f.data}:{...m,error:f?.error?.message??"Upload failed"}),S()}},P=async()=>{let l=I.value.trim(),h=this.pendingAttachments.filter(u=>u.attachment).map(u=>u.attachment);!l&&!h.length||!this.controller||(I.value="",await this.controller.sendMessage(l,h),this.pendingAttachments=[],S(),await this.controller.markRead())};q.addEventListener("click",()=>{T.click()}),T.addEventListener("change",()=>{T.files&&(Y(T.files),T.value="")}),Q.addEventListener("click",()=>{P()}),I.addEventListener("keydown",l=>{l.key==="Enter"&&!l.shiftKey?(l.preventDefault(),P()):this.controller?.sendTyping(!0)}),this.controller.init().then(()=>this.controller?.markRead())}cleanup(){for(let e of this.cleanupFns)e();this.cleanupFns=[],this.controller?.destroy(),this.controller=null,this.client?.destroy(),this.client=null,this.currentState=null,this.openReactionMessageId=null,this.didScrollToUnread=!1,this.shadow.innerHTML=""}};typeof customElements<"u"&&!customElements.get("scalemule-chat")&&customElements.define("scalemule-chat",L);var Se=A.create.bind(A),Me=A.version;return ne(xe);})();