@scalemule/chat 0.0.5 → 0.0.8
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/chat.embed.global.js +1 -1
- package/dist/chat.umd.global.js +288 -12
- package/dist/{chunk-ZLMMNFZL.js → chunk-5O5YLRJL.js} +386 -16
- package/dist/chunk-GTMAK3IA.js +285 -0
- package/dist/chunk-TRCELAZQ.cjs +287 -0
- package/dist/{chunk-YDLRISR7.cjs → chunk-W2PWFS3E.cjs} +386 -15
- package/dist/element.cjs +542 -51
- package/dist/element.js +541 -50
- package/dist/index.cjs +34 -5
- package/dist/index.js +29 -4
- package/dist/react.cjs +1260 -50
- package/dist/react.js +1212 -13
- package/dist/support-widget.global.js +485 -157
- package/package.json +5 -2
- package/dist/ChatClient-BoZaTtyM.d.cts +0 -88
- package/dist/ChatClient-COmdEJ11.d.ts +0 -88
- package/dist/element.d.cts +0 -2
- package/dist/element.d.ts +0 -2
- package/dist/iframe.d.cts +0 -17
- package/dist/iframe.d.ts +0 -17
- package/dist/index.d.cts +0 -77
- package/dist/index.d.ts +0 -77
- package/dist/react.d.cts +0 -49
- package/dist/react.d.ts +0 -49
- package/dist/types-BmD7f1gV.d.cts +0 -232
- package/dist/types-BmD7f1gV.d.ts +0 -232
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ChatEmbed=(()=>{var p=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 v="https://api.scalemule.com";var d=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 f="scalemule_chat_offline_queue",u=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(f,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let n=localStorage.getItem(f);n&&(this.queue=JSON.parse(n))}}catch{this.queue=[]}}};var l=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 g=class extends p{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 m=class extends p{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??v;this.http=new l({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0)}),this.ws=new g({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 d(e.messageCache?.maxMessages,e.messageCache?.maxConversations),this.offlineQueue=new u(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,c=i.content??"";o&&this.emit("inbox:update",{conversationId:o,messageId:r,senderId:a,preview:c});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 c of a)this.emit("typing",{userId:c,conversationId:s});break}case"messages_read":{let a=r.user_id,c=r.last_read_at;a&&c&&this.emit("read",{userId:a,conversationId:s,lastReadAt:c});break}case"room_upgraded":{if(r.new_type==="large_room"){this.conversationTypes.set(s,"large_room");let c=this.conversationSubs.get(s);c&&(c(),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})}};function y(){let h=new URLSearchParams(window.location.search),n=window.location.pathname.split("/"),e=n[n.length-1]||void 0,t=h.get("token")??void 0,s;try{s={...window.location.hash?JSON.parse(decodeURIComponent(window.location.hash.slice(1))):{},embedToken:t}}catch{s={embedToken:t}}let i=new m(s);i.on("message",({message:r,conversationId:a})=>{window.parent.postMessage({type:"message",payload:{message:r,conversationId:a}},"*")}),i.on("connected",()=>{window.parent.postMessage({type:"connected"},"*")});let o=!1;window.addEventListener("message",r=>{let{method:a,args:c}=r.data??{};switch(a){case"sendMessage":e&&c?.content&&i.sendMessage(e,{content:c.content});break;case"disconnect":o=!0,i.disconnect();break}}),e&&i.getConversation(e).then(()=>{o||(i.subscribeToConversation(e),i.connect())})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",y):y());})();
|
|
1
|
+
"use strict";var ChatEmbed=(()=>{var h=class{constructor(){this.listeners=new Map}on(i,e){return this.listeners.has(i)||this.listeners.set(i,new Set),this.listeners.get(i).add(e),()=>this.off(i,e)}off(i,e){this.listeners.get(i)?.delete(e)}once(i,e){let t=(s=>{this.off(i,t),e(s)});return this.on(i,t)}emit(i,...[e]){let t=this.listeners.get(i);if(t)for(let s of t)try{s(e)}catch(n){console.error(`[ScaleMuleChat] Error in ${String(i)} listener:`,n)}}removeAllListeners(i){i?this.listeners.delete(i):this.listeners.clear()}};var b="https://api.scalemule.com";var l=class{constructor(i,e){this.cache=new Map;this.maxMessages=i??200,this.maxConversations=e??50}getMessages(i){return this.cache.get(i)??[]}getMessage(i,e){return this.getMessages(i).find(t=>t.id===e)}setMessages(i,e){this.cache.set(i,e.slice(0,this.maxMessages)),this.evictOldConversations()}addMessage(i,e){let t=this.cache.get(i)??[];t.some(s=>s.id===e.id)||(t.push(e),t.sort((s,n)=>s.created_at.localeCompare(n.created_at)),t.length>this.maxMessages&&t.splice(0,t.length-this.maxMessages),this.cache.set(i,t))}upsertMessage(i,e){if(this.getMessage(i,e.id)){this.updateMessage(i,e);return}this.addMessage(i,e)}updateMessage(i,e){let t=this.cache.get(i);if(!t)return;let s=t.findIndex(n=>n.id===e.id);s>=0&&(t[s]=e)}reconcileOptimisticMessage(i,e){let t=this.cache.get(i);if(!t)return e;let s=(e.attachments??[]).map(o=>o.file_id).sort(),n=t.findIndex(o=>{if(!o.id.startsWith("pending-")||o.sender_id!==e.sender_id||o.content!==e.content)return!1;let a=(o.attachments??[]).map(r=>r.file_id).sort();return a.length!==s.length?!1:a.every((r,c)=>r===s[c])});return n>=0&&(t[n]=e),e}removeMessage(i,e){let t=this.cache.get(i);if(!t)return;let s=t.findIndex(n=>n.id===e);s>=0&&t.splice(s,1)}clear(i){i?this.cache.delete(i):this.cache.clear()}evictOldConversations(){for(;this.cache.size>this.maxConversations;){let i=this.cache.keys().next().value;i&&this.cache.delete(i)}}};var y="scalemule_chat_offline_queue",g=class{constructor(i=!0){this.queue=[];this.enabled=i,this.enabled&&this.load()}enqueue(i,e,t="text",s){this.enabled&&(this.queue.push({conversationId:i,content:e,message_type:t,attachments:s,timestamp:Date.now()}),this.save())}drain(){let i=[...this.queue];return this.queue=[],this.save(),i}get size(){return this.queue.length}save(){try{typeof localStorage<"u"&&localStorage.setItem(y,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let i=localStorage.getItem(y);i&&(this.queue=JSON.parse(i))}}catch{this.queue=[]}}};var m=class{constructor(i){this.baseUrl=i.baseUrl.replace(/\/$/,""),this.apiKey=i.apiKey,this.getToken=i.getToken,this.timeout=i.timeout??1e4}async get(i){return this.request("GET",i)}async post(i,e){return this.request("POST",i,e)}async patch(i,e){return this.request("PATCH",i,e)}async del(i){return this.request("DELETE",i)}async request(i,e,t){let s={"Content-Type":"application/json"};if(this.apiKey&&(s["x-api-key"]=this.apiKey),this.getToken){let a=await this.getToken();a&&(s.Authorization=`Bearer ${a}`)}let n=new AbortController,o=setTimeout(()=>n.abort(),this.timeout);try{let a=await fetch(`${this.baseUrl}${e}`,{method:i,headers:s,body:t?JSON.stringify(t):void 0,signal:n.signal});if(clearTimeout(o),a.status===204)return{data:null,error:null};let r=await a.json().catch(()=>null);return a.ok?{data:r?.data!==void 0?r.data:r,error:null}:{data:null,error:{code:r?.error?.code??r?.code??"unknown",message:r?.error?.message??r?.message??a.statusText,status:a.status,details:r?.error?.details??r?.details}}}catch(a){return clearTimeout(o),{data:null,error:{code:"network_error",message:a instanceof Error?a.message:"Network error",status:0}}}}};var v=class extends h{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 P=[0,1e3,3e3],w=45e3,x=new Set([0,408,429,500,502,503,504]);function L(d){return new Promise(i=>setTimeout(i,d))}function I(d,i){return i==="aborted"?!1:x.has(d)||i==="upload_stalled"}function O(d,i,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 n=new XMLHttpRequest,o=!1,a=null,r=0,c=i.size,p=u=>{o||(o=!0,a&&(clearTimeout(a),a=null),s(u))},_=()=>{a&&clearTimeout(a),a=setTimeout(()=>{n.abort(),p({data:null,error:{code:"upload_stalled",message:`Upload stalled (no progress for ${w/1e3}s)`,status:0,details:{bytes_sent:r,total_bytes:c}}})},w)};if(t){if(t.aborted){p({data:null,error:{code:"aborted",message:"Upload aborted",status:0}});return}t.addEventListener("abort",()=>{n.abort(),p({data:null,error:{code:"aborted",message:"Upload aborted",status:0}})},{once:!0})}n.upload.addEventListener("progress",u=>{_(),r=u.loaded,c=u.total||c,u.lengthComputable&&e?.(Math.round(u.loaded/u.total*100))}),n.addEventListener("load",()=>{if(n.status>=200&&n.status<300){e?.(100),p({data:null,error:null});return}p({data:null,error:{code:"upload_error",message:`S3 upload failed: ${n.status}`,status:n.status,details:{bytes_sent:r,total_bytes:c}}})}),n.addEventListener("error",()=>{p({data:null,error:{code:"upload_error",message:"S3 upload failed",status:n.status||0,details:{bytes_sent:r,total_bytes:c}}})}),n.addEventListener("abort",()=>{o||p({data:null,error:{code:"aborted",message:"Upload aborted",status:0}})}),n.open("PUT",d,!0),i.type&&n.setRequestHeader("Content-Type",i.type),_(),n.send(i)})}async function T(d,i,e,t){let s=null;for(let[n,o]of P.entries()){o>0&&await L(o);let a=await O(d,i,e,t);if(!a.error)return a;if(s={...a.error,details:{...a.error.details,attempt:n+1}},!I(a.error.status,a.error.code))break}return{data:null,error:s??{code:"upload_error",message:"Upload failed",status:0}}}var f=class extends h{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??b;this.currentUserId=e.userId,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:n})=>{this.handleRealtimeMessage(s,n)}),this.ws.on("presence:state",({channel:s,members:n})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:state",{conversationId:o,members:n})}),this.ws.on("presence:join",({channel:s,user:n})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:join",{userId:n.user_id,conversationId:o,userData:n.user_data})}),this.ws.on("presence:leave",({channel:s,userId:n})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:leave",{userId:n,conversationId:o})}),this.ws.on("presence:update",({channel:s,userId:n,status:o,userData:a})=>{let r=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:update",{userId:n,conversationId:r,status:o,userData:a})}),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(),n=await this.http.get(`/v1/chat/conversations${s?"?"+s:""}`);return n.data&&n.data.forEach(o=>this.trackConversationType(o)),n}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 n=this.cache.reconcileOptimisticMessage(e,s.data);this.cache.upsertMessage(e,n)}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 n=s.toString(),o=await this.http.get(`/v1/chat/conversations/${e}/messages${n?"?"+n:""}`);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}`)}async uploadAttachment(e,t,s){let n=typeof File<"u"&&e instanceof File?e.name:"attachment",o=e.type||"application/octet-stream",a=await this.http.post("/v1/storage/signed-url/upload",{filename:n,content_type:o,size_bytes:e.size,is_public:!1,metadata:{source:"chat_sdk"}});if(a.error||!a.data)return{data:null,error:a.error};let r=await T(a.data.upload_url,e,t,s);if(r.error)return{data:null,error:r.error};let c=await this.http.post("/v1/storage/signed-url/complete",{file_id:a.data.file_id,completion_token:a.data.completion_token});return c.error||!c.data?{data:null,error:c.error}:{data:{file_id:c.data.file_id,file_name:c.data.filename,file_size:c.data.size_bytes,mime_type:c.data.content_type,presigned_url:c.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 n=this.channelName(e);this.ws.send({type:"presence_update",channel:n,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,n=t.data??t;switch(s){case"new_message":{let o=n.conversation_id,a=n.id??n.message_id,r=n.sender_id,c=n.content??"";o&&this.emit("inbox:update",{conversationId:o,messageId:a,senderId:r,preview:c});break}case"support:new_conversation":{let o=n.conversation_id,a=n.visitor_name;o&&this.emit("support:new",{conversationId:o,visitorName:a});break}case"support:assigned":{let o=n.conversation_id,a=n.visitor_name,r=n.visitor_email;o&&this.emit("support:assigned",{conversationId:o,visitorName:a,visitorEmail:r});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 n=this.cache.getMessage(e,s);return{id:n?.id??s,content:t.content??t.new_content??n?.content??"",message_type:n?.message_type??"text",sender_id:n?.sender_id??"",sender_type:n?.sender_type,sender_agent_model:n?.sender_agent_model,attachments:n?.attachments,reactions:n?.reactions,is_edited:!0,created_at:n?.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},n=this.cache.getMessage(e,t.message_id);if(!n)return s;let o=[...n.reactions??[]],a=o.findIndex(r=>r.emoji===t.emoji);if(t.action==="added")if(a>=0){let r=o[a];if(!r.user_ids.includes(t.user_id)){let c=[...r.user_ids,t.user_id];o[a]={...r,user_ids:c,count:c.length}}}else o.push({emoji:t.emoji,count:1,user_ids:[t.user_id]});else if(a>=0){let r=o[a],c=r.user_ids.filter(p=>p!==t.user_id);c.length===0?o.splice(a,1):o[a]={...r,user_ids:c,count:c.length}}return this.cache.updateMessage(e,{...n,reactions:o}),s}handleConversationMessage(e,t){let s=e.replace(/^conversation:(?:lr:|bc:|support:)?/,""),n=t;if(!n)return;let o=n.event??n.type,a=n.data??n;switch(o){case"new_message":{let r=this.normalizeMessage(a),c=this.cache.reconcileOptimisticMessage(s,r);this.cache.upsertMessage(s,c),this.emit("message",{message:c,conversationId:s});break}case"message_edited":{let r=a,c=this.buildEditedMessage(s,r);c&&(this.cache.upsertMessage(s,c),this.emit("message:updated",{message:c,conversationId:s,update:r}));break}case"reaction":{let r=a;if(!r.message_id||!r.user_id||!r.emoji)break;let c=this.applyReactionEvent(s,r);this.emit("reaction",{reaction:c,conversationId:s,action:r.action});break}case"message_deleted":{let r=a.message_id??a.id;r&&(this.cache.removeMessage(s,r),this.emit("message:deleted",{messageId:r,conversationId:s}));break}case"user_typing":{let r=a.user_id;r&&this.emit("typing",{userId:r,conversationId:s});break}case"user_stopped_typing":{let r=a.user_id;r&&this.emit("typing:stop",{userId:r,conversationId:s});break}case"typing_batch":{let r=a.users??[];for(let c of r)this.emit("typing",{userId:c,conversationId:s});break}case"messages_read":{let r=a.user_id,c=a.last_read_at;r&&c&&this.emit("read",{userId:r,conversationId:s,lastReadAt:c});break}case"room_upgraded":{if(a.new_type==="large_room"){this.conversationTypes.set(s,"large_room");let c=this.conversationSubs.get(s);c&&(c(),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})}};function C(){let d=new URLSearchParams(window.location.search),i=window.location.pathname.split("/"),e=i[i.length-1]||void 0,t=d.get("token")??void 0,s;try{s={...window.location.hash?JSON.parse(decodeURIComponent(window.location.hash.slice(1))):{},embedToken:t}}catch{s={embedToken:t}}let n=new f(s);n.on("message",({message:a,conversationId:r})=>{window.parent.postMessage({type:"message",payload:{message:a,conversationId:r}},"*")}),n.on("connected",()=>{window.parent.postMessage({type:"connected"},"*")});let o=!1;window.addEventListener("message",a=>{let{method:r,args:c}=a.data??{};switch(r){case"sendMessage":e&&c?.content&&n.sendMessage(e,{content:c.content});break;case"disconnect":o=!0,n.disconnect();break}}),e&&n.getConversation(e).then(()=>{o||(n.subscribeToConversation(e),n.connect())})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",C):C());})();
|
package/dist/chat.umd.global.js
CHANGED
|
@@ -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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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 {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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="
|
|
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="
|
|
15
|
-
<button id="
|
|
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
|
|
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);})();
|