@scalemule/chat 0.0.4 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ChatClient-COmdEJ11.d.ts → ChatClient-DQPHdUHX.d.cts} +21 -2
- package/dist/{ChatClient-BoZaTtyM.d.cts → ChatClient-DtUKF-4c.d.ts} +21 -2
- 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/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core/ChatClient.d.ts +96 -0
- package/dist/core/ChatClient.d.ts.map +1 -0
- package/dist/core/EventEmitter.d.ts +11 -0
- package/dist/core/EventEmitter.d.ts.map +1 -0
- package/dist/core/MessageCache.d.ts +18 -0
- package/dist/core/MessageCache.d.ts.map +1 -0
- package/dist/core/OfflineQueue.d.ts +19 -0
- package/dist/core/OfflineQueue.d.ts.map +1 -0
- package/dist/element.cjs +542 -51
- package/dist/element.d.ts +2 -2
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +541 -50
- package/dist/embed/index.d.ts +2 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/iframe.d.cts +1 -1
- package/dist/iframe.d.ts +3 -5
- package/dist/iframe.d.ts.map +1 -0
- package/dist/index.cjs +34 -5
- package/dist/index.d.cts +93 -4
- package/dist/index.d.ts +8 -77
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -4
- package/dist/react-components/ChatInput.d.ts +16 -0
- package/dist/react-components/ChatInput.d.ts.map +1 -0
- package/dist/react-components/ChatMessageItem.d.ts +13 -0
- package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
- package/dist/react-components/ChatMessageList.d.ts +15 -0
- package/dist/react-components/ChatMessageList.d.ts.map +1 -0
- package/dist/react-components/ChatThread.d.ts +12 -0
- package/dist/react-components/ChatThread.d.ts.map +1 -0
- package/dist/react-components/ConversationList.d.ts +13 -0
- package/dist/react-components/ConversationList.d.ts.map +1 -0
- package/dist/react-components/EmojiPicker.d.ts +8 -0
- package/dist/react-components/EmojiPicker.d.ts.map +1 -0
- package/dist/react-components/index.d.ts +8 -0
- package/dist/react-components/index.d.ts.map +1 -0
- package/dist/react-components/theme.d.ts +19 -0
- package/dist/react-components/theme.d.ts.map +1 -0
- package/dist/react-components/utils.d.ts +4 -0
- package/dist/react-components/utils.d.ts.map +1 -0
- package/dist/react.cjs +1213 -53
- package/dist/react.d.cts +100 -4
- package/dist/react.d.ts +38 -15
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1167 -18
- package/dist/shared/ChatController.d.ts +65 -0
- package/dist/shared/ChatController.d.ts.map +1 -0
- package/dist/shared/upload.d.ts +3 -0
- package/dist/shared/upload.d.ts.map +1 -0
- package/dist/support-widget.global.js +485 -157
- package/dist/support.d.ts +99 -0
- package/dist/support.d.ts.map +1 -0
- package/dist/transport/HttpTransport.d.ts +20 -0
- package/dist/transport/HttpTransport.d.ts.map +1 -0
- package/dist/transport/WebSocketTransport.d.ts +78 -0
- package/dist/transport/WebSocketTransport.d.ts.map +1 -0
- package/dist/{types-BmD7f1gV.d.cts → types-COPVrm3K.d.cts} +25 -1
- package/dist/{types-BmD7f1gV.d.ts → types-COPVrm3K.d.ts} +25 -1
- package/dist/types.d.ts +271 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/umd.d.ts +6 -0
- package/dist/umd.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/widget/icons.d.ts +8 -0
- package/dist/widget/icons.d.ts.map +1 -0
- package/dist/widget/index.d.ts +2 -0
- package/dist/widget/index.d.ts.map +1 -0
- package/dist/widget/storage.d.ts +5 -0
- package/dist/widget/storage.d.ts.map +1 -0
- package/dist/widget/styles.d.ts +3 -0
- package/dist/widget/styles.d.ts.map +1 -0
- package/package.json +5 -2
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
"use strict";var ScaleMuleSupportWidget=(()=>{var p=class{constructor(){this.listeners=new Map}on(s,e){return this.listeners.has(s)||this.listeners.set(s,new Set),this.listeners.get(s).add(e),()=>this.off(s,e)}off(s,e){this.listeners.get(s)?.delete(e)}once(s,e){let t=(n=>{this.off(s,t),e(n)});return this.on(s,t)}emit(s,...[e]){let t=this.listeners.get(s);if(t)for(let n of t)try{n(e)}catch(i){console.error(`[ScaleMuleChat] Error in ${String(s)} listener:`,i)}}removeAllListeners(s){s?this.listeners.delete(s):this.listeners.clear()}};var b="https://api.scalemule.com";var d=class{constructor(s,e){this.cache=new Map;this.maxMessages=s??200,this.maxConversations=e??50}getMessages(s){return this.cache.get(s)??[]}setMessages(s,e){this.cache.set(s,e.slice(0,this.maxMessages)),this.evictOldConversations()}addMessage(s,e){let t=this.cache.get(s)??[];t.some(n=>n.id===e.id)||(t.push(e),t.sort((n,i)=>n.created_at.localeCompare(i.created_at)),t.length>this.maxMessages&&t.splice(0,t.length-this.maxMessages),this.cache.set(s,t))}updateMessage(s,e){let t=this.cache.get(s);if(!t)return;let n=t.findIndex(i=>i.id===e.id);n>=0&&(t[n]=e)}removeMessage(s,e){let t=this.cache.get(s);if(!t)return;let n=t.findIndex(i=>i.id===e);n>=0&&t.splice(n,1)}clear(s){s?this.cache.delete(s):this.cache.clear()}evictOldConversations(){for(;this.cache.size>this.maxConversations;){let s=this.cache.keys().next().value;s&&this.cache.delete(s)}}};var y="scalemule_chat_offline_queue",h=class{constructor(s=!0){this.queue=[];this.enabled=s,this.enabled&&this.load()}enqueue(s,e,t="text",n){this.enabled&&(this.queue.push({conversationId:s,content:e,message_type:t,attachments:n,timestamp:Date.now()}),this.save())}drain(){let s=[...this.queue];return this.queue=[],this.save(),s}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 s=localStorage.getItem(y);s&&(this.queue=JSON.parse(s))}}catch{this.queue=[]}}};var u=class{constructor(s){this.baseUrl=s.baseUrl.replace(/\/$/,""),this.apiKey=s.apiKey,this.getToken=s.getToken,this.timeout=s.timeout??1e4}async get(s){return this.request("GET",s)}async post(s,e){return this.request("POST",s,e)}async patch(s,e){return this.request("PATCH",s,e)}async del(s){return this.request("DELETE",s)}async request(s,e,t){let n={"Content-Type":"application/json"};if(this.apiKey&&(n["x-api-key"]=this.apiKey),this.getToken){let o=await this.getToken();o&&(n.Authorization=`Bearer ${o}`)}let i=new AbortController,a=setTimeout(()=>i.abort(),this.timeout);try{let o=await fetch(`${this.baseUrl}${e}`,{method:s,headers:n,body:t?JSON.stringify(t):void 0,signal:i.signal,credentials:"include"});if(clearTimeout(a),o.status===204)return{data:null,error:null};let r=await o.json().catch(()=>null);return o.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??o.statusText,status:o.status,details:r?.error?.details??r?.details}}}catch(o){return clearTimeout(a),{data:null,error:{code:"network_error",message:o instanceof Error?o.message:"Network error",status:0}}}}};var m=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 n=e.wsUrl?e.wsUrl.replace(/\/$/,""):t;this.wsBaseUrl=n.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=n=>{this.handleMessage(n.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 n=await t.json();return n.ticket??n.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 g=class extends p{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??b;this.http=new u({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0)}),this.ws=new m({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 h(e.offlineQueue??!0),this.ws.on("status",n=>{switch(n){case"connected":this.emit("connected"),this.flushOfflineQueue();break;case"disconnected":this.emit("disconnected");break}}),this.ws.on("reconnecting",n=>{this.emit("reconnecting",n)}),this.ws.on("message",({channel:n,data:i})=>{this.handleRealtimeMessage(n,i)}),this.ws.on("presence:state",({channel:n,members:i})=>{let a=n.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:state",{conversationId:a,members:i})}),this.ws.on("presence:join",({channel:n,user:i})=>{let a=n.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:join",{userId:i.user_id,conversationId:a,userData:i.user_data})}),this.ws.on("presence:leave",({channel:n,userId:i})=>{let a=n.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:leave",{userId:i,conversationId:a})}),this.ws.on("presence:update",({channel:n,userId:i,status:a,userData:o})=>{let r=n.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:update",{userId:i,conversationId:r,status:a,userData:o})}),this.ws.on("error",({message:n})=>{this.emit("error",{code:"ws_error",message:n})})}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"},n=await this.http.post("/v1/chat/conversations",t);return n.data&&this.trackConversationType(n.data),n}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 n=t.toString(),i=await this.http.get(`/v1/chat/conversations${n?"?"+n:""}`);return i.data&&i.data.forEach(a=>this.trackConversationType(a)),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 n=await this.http.post(`/v1/chat/conversations/${e}/messages`,{content:t.content,message_type:t.message_type??"text",attachments:t.attachments});return n.data?this.cache.addMessage(e,n.data):n.error?.status===0&&this.offlineQueue.enqueue(e,t.content,t.message_type??"text"),n}async getMessages(e,t){let n=new URLSearchParams;t?.limit&&n.set("limit",String(t.limit)),t?.before&&n.set("before",t.before),t?.after&&n.set("after",t.after);let i=n.toString(),a=await this.http.get(`/v1/chat/conversations/${e}/messages${i?"?"+i:""}`);return a.data?.messages&&(t?.after||(a.data.messages=a.data.messages.slice().reverse()),!t?.before&&!t?.after&&this.cache.setMessages(e,a.data.messages)),a}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 n=this.channelName(e);this.ws.joinPresence(n,t)}leavePresence(e){let t=this.channelName(e);this.ws.leavePresence(t)}updatePresence(e,t,n){let i=this.channelName(e);this.ws.send({type:"presence_update",channel:i,status:t,user_data:n})}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),n=this.ws.subscribe(t);return this.conversationSubs.set(e,n),()=>{this.conversationSubs.delete(e),n()}}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 n=t.event??t.type,i=t.data??t;switch(n){case"new_message":{let a=i.conversation_id,o=i.id??i.message_id,r=i.sender_id,l=i.content??"";a&&this.emit("inbox:update",{conversationId:a,messageId:o,senderId:r,preview:l});break}case"support:new_conversation":{let a=i.conversation_id,o=i.visitor_name;a&&this.emit("support:new",{conversationId:a,visitorName:o});break}case"support:assigned":{let a=i.conversation_id,o=i.visitor_name,r=i.visitor_email;a&&this.emit("support:assigned",{conversationId:a,visitorName:o,visitorEmail:r});break}}}handleConversationMessage(e,t){let n=e.replace(/^conversation:(?:lr:|bc:|support:)?/,""),i=t;if(!i)return;let a=i.event??i.type,o=i.data??i;switch(a){case"new_message":{let r=o;this.cache.addMessage(n,r),this.emit("message",{message:r,conversationId:n});break}case"message_edited":{let r=o;this.cache.updateMessage(n,r),this.emit("message:updated",{message:r,conversationId:n});break}case"message_deleted":{let r=o.message_id??o.id;r&&(this.cache.removeMessage(n,r),this.emit("message:deleted",{messageId:r,conversationId:n}));break}case"user_typing":{let r=o.user_id;r&&this.emit("typing",{userId:r,conversationId:n});break}case"user_stopped_typing":{let r=o.user_id;r&&this.emit("typing:stop",{userId:r,conversationId:n});break}case"typing_batch":{let r=o.users??[];for(let l of r)this.emit("typing",{userId:l,conversationId:n});break}case"messages_read":{let r=o.user_id,l=o.last_read_at;r&&l&&this.emit("read",{userId:r,conversationId:n,lastReadAt:l});break}case"room_upgraded":{if(o.new_type==="large_room"){this.conversationTypes.set(n,"large_room");let l=this.conversationSubs.get(n);l&&(l(),this.conversationSubs.delete(n),this.subscribeToConversation(n)),this.emit("room_upgraded",{conversationId:n,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 P="sm_support_",v=class{constructor(s){this.chatClient=null;this.refreshToken=null;this.accessToken=null;this.tokenExpiresAt=0;this.userId=null;this.apiKey=s.apiKey,this.apiBaseUrl=s.apiBaseUrl??"https://api.scalemule.com",this.storageKey=P+s.apiKey.substring(0,8);let e=this.loadState();e?(this.anonymousId=e.anonymous_id,this.refreshToken=e.refresh_token,this.userId=e.user_id):this.anonymousId=crypto.randomUUID()}async initVisitorSession(s){if(this.visitorName=s?.name,this.visitorEmail=s?.email,this.refreshToken)try{await this.refreshAccessToken(),this.initChatClient();return}catch{this.refreshToken=null}let e=await fetch(`${this.apiBaseUrl}/v1/auth/visitor-session`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.apiKey},body:JSON.stringify({anonymous_id:this.anonymousId,name:s?.name,email:s?.email,page_url:typeof location<"u"?location.href:void 0})});if(!e.ok){let i=await e.text();throw new Error(`Visitor session failed: ${e.status} ${i}`)}let n=(await e.json()).data;this.accessToken=n.access_token,this.refreshToken=n.refresh_token,this.userId=n.user_id,this.tokenExpiresAt=Date.now()+n.expires_in*1e3,this.saveState(),this.initChatClient()}async startConversation(s,e){if(!this.accessToken)throw new Error("Call initVisitorSession() first");let t=await fetch(`${this.apiBaseUrl}/v1/chat/support/conversations`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.apiKey,Authorization:`Bearer ${this.accessToken}`},body:JSON.stringify({message:s,name:this.visitorName,email:this.visitorEmail,page_url:e?.page_url??(typeof location<"u"?location.href:void 0),user_agent:typeof navigator<"u"?navigator.userAgent:void 0})});if(!t.ok){let a=await t.text();throw new Error(`Create support conversation failed: ${t.status} ${a}`)}let i=(await t.json()).data;return this.chatClient&&this.chatClient.conversationTypes?.set(i.conversation_id,"support"),i}async getActiveConversation(){if(!this.accessToken)return null;let s=await fetch(`${this.apiBaseUrl}/v1/chat/support/conversations/mine`,{headers:{"x-api-key":this.apiKey,Authorization:`Bearer ${this.accessToken}`}});if(!s.ok)return null;let n=(await s.json()).data.find(i=>i.status==="active"||i.status==="waiting");return n&&this.chatClient&&this.chatClient.conversationTypes?.set(n.conversation_id,"support"),n??null}get chat(){if(!this.chatClient)throw new Error("Call initVisitorSession() first");return this.chatClient}get isInitialized(){return this.chatClient!==null}get visitorUserId(){return this.userId}destroy(){this.chatClient?.destroy(),this.chatClient=null}initChatClient(){this.chatClient&&this.chatClient.destroy();let s={apiKey:this.apiKey,apiBaseUrl:this.apiBaseUrl,getToken:async()=>{if(!this.accessToken)return null;if(Date.now()>this.tokenExpiresAt-6e4)try{await this.refreshAccessToken()}catch{}return this.accessToken}};this.chatClient=new g(s)}async refreshAccessToken(){if(!this.refreshToken)throw new Error("No refresh token");let s=await fetch(`${this.apiBaseUrl}/v1/auth/token/refresh`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.apiKey},body:JSON.stringify({refresh_token:this.refreshToken})});if(!s.ok)throw new Error(`Token refresh failed: ${s.status}`);let t=(await s.json()).data;this.accessToken=t.access_token,this.tokenExpiresAt=Date.now()+t.expires_in*1e3}loadState(){try{let s=localStorage.getItem(this.storageKey);return s?JSON.parse(s):null}catch{return null}}saveState(){try{localStorage.setItem(this.storageKey,JSON.stringify({anonymous_id:this.anonymousId,refresh_token:this.refreshToken,user_id:this.userId}))}catch{}}};var w=`
|
|
1
|
+
"use strict";var ScaleMuleSupportWidget=(()=>{var g=class{constructor(){this.listeners=new Map}on(s,e){return this.listeners.has(s)||this.listeners.set(s,new Set),this.listeners.get(s).add(e),()=>this.off(s,e)}off(s,e){this.listeners.get(s)?.delete(e)}once(s,e){let t=(i=>{this.off(s,t),e(i)});return this.on(s,t)}emit(s,...[e]){let t=this.listeners.get(s);if(t)for(let i of t)try{i(e)}catch(n){console.error(`[ScaleMuleChat] Error in ${String(s)} listener:`,n)}}removeAllListeners(s){s?this.listeners.delete(s):this.listeners.clear()}};var b=class extends g{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,i=e.presence??t;this.bindEvents(),t&&this.client.connect();try{await this.client.getConversation(this.conversationId);let[n,r]=await Promise.all([this.client.getMessages(this.conversationId),this.client.getReadStatus(this.conversationId)]);return this.state={...this.state,messages:n.data?.messages??[],readStatuses:r.data?.statuses??[],hasMore:n.data?.has_more??!1,isLoading:!1,error:n.error?.message??r.error?.message??null},t&&this.unsubscribers.push(this.client.subscribeToConversation(this.conversationId)),i&&this.client.joinPresence(this.conversationId),this.emit("state",this.state),this.emit("ready",this.state),this.state}catch(n){let r=n instanceof Error?n.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}),n}}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 i=t.length>0?t.every(r=>r.mime_type.startsWith("image/"))&&!e?"image":"file":"text",n=await this.client.sendMessage(this.conversationId,{content:e,attachments:t,message_type:i});if(n.error)throw new Error(n.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,i){return this.client.uploadAttachment(e,t,i)}async refreshAttachmentUrl(e,t){return this.client.refreshAttachmentUrl(e,t)}async addReaction(e,t){let i=await this.client.addReaction(e,t);if(i.error)throw new Error(i.error.message)}async removeReaction(e,t){let i=await this.client.removeReaction(e,t);if(i.error)throw new Error(i.error.message)}async reportMessage(e,t,i){return this.client.reportMessage(e,t,i)}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 i=this.typingTimers.get(t);i&&clearTimeout(i),this.typingTimers.set(t,setTimeout(()=>{this.patchState({typingUsers:this.state.typingUsers.filter(n=>n!==t)}),this.typingTimers.delete(t)},3e3))})),this.unsubscribers.push(this.client.on("typing:stop",({conversationId:e,userId:t})=>{if(e!==this.conversationId)return;let i=this.typingTimers.get(t);i&&(clearTimeout(i),this.typingTimers.delete(t)),this.patchState({typingUsers:this.state.typingUsers.filter(n=>n!==t)})})),this.unsubscribers.push(this.client.on("read",({conversationId:e,userId:t,lastReadAt:i})=>{if(e!==this.conversationId)return;let n=[...this.state.readStatuses],r=n.findIndex(a=>a.user_id===t);r>=0?n[r]={...n[r],last_read_at:i}:n.push({user_id:t,last_read_at:i}),this.patchState({readStatuses:n})})),this.unsubscribers.push(this.client.on("presence:state",({conversationId:e,members:t})=>{e===this.conversationId&&this.patchState({members:t.map(i=>({userId:i.user_id,status:i.status??"online",userData:i.user_data}))})})),this.unsubscribers.push(this.client.on("presence:join",({conversationId:e,userId:t,userData:i})=>{e===this.conversationId&&(this.state.members.some(n=>n.userId===t)||this.patchState({members:[...this.state.members,{userId:t,status:"online",userData:i}]}))})),this.unsubscribers.push(this.client.on("presence:leave",({conversationId:e,userId:t})=>{e===this.conversationId&&this.patchState({members:this.state.members.filter(i=>i.userId!==t)})})),this.unsubscribers.push(this.client.on("presence:update",({conversationId:e,userId:t,status:i,userData:n})=>{e===this.conversationId&&this.patchState({members:this.state.members.map(r=>r.userId===t?{...r,status:i,userData:n}:r)})})))}patchState(e){this.state={...this.state,...e,error:e.error!==void 0?e.error:this.state.error},this.emit("state",this.state)}};var M="https://api.scalemule.com";var y=class{constructor(s,e){this.cache=new Map;this.maxMessages=s??200,this.maxConversations=e??50}getMessages(s){return this.cache.get(s)??[]}getMessage(s,e){return this.getMessages(s).find(t=>t.id===e)}setMessages(s,e){this.cache.set(s,e.slice(0,this.maxMessages)),this.evictOldConversations()}addMessage(s,e){let t=this.cache.get(s)??[];t.some(i=>i.id===e.id)||(t.push(e),t.sort((i,n)=>i.created_at.localeCompare(n.created_at)),t.length>this.maxMessages&&t.splice(0,t.length-this.maxMessages),this.cache.set(s,t))}upsertMessage(s,e){if(this.getMessage(s,e.id)){this.updateMessage(s,e);return}this.addMessage(s,e)}updateMessage(s,e){let t=this.cache.get(s);if(!t)return;let i=t.findIndex(n=>n.id===e.id);i>=0&&(t[i]=e)}reconcileOptimisticMessage(s,e){let t=this.cache.get(s);if(!t)return e;let i=(e.attachments??[]).map(r=>r.file_id).sort(),n=t.findIndex(r=>{if(!r.id.startsWith("pending-")||r.sender_id!==e.sender_id||r.content!==e.content)return!1;let a=(r.attachments??[]).map(o=>o.file_id).sort();return a.length!==i.length?!1:a.every((o,l)=>o===i[l])});return n>=0&&(t[n]=e),e}removeMessage(s,e){let t=this.cache.get(s);if(!t)return;let i=t.findIndex(n=>n.id===e);i>=0&&t.splice(i,1)}clear(s){s?this.cache.delete(s):this.cache.clear()}evictOldConversations(){for(;this.cache.size>this.maxConversations;){let s=this.cache.keys().next().value;s&&this.cache.delete(s)}}};var A="scalemule_chat_offline_queue",_=class{constructor(s=!0){this.queue=[];this.enabled=s,this.enabled&&this.load()}enqueue(s,e,t="text",i){this.enabled&&(this.queue.push({conversationId:s,content:e,message_type:t,attachments:i,timestamp:Date.now()}),this.save())}drain(){let s=[...this.queue];return this.queue=[],this.save(),s}get size(){return this.queue.length}save(){try{typeof localStorage<"u"&&localStorage.setItem(A,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let s=localStorage.getItem(A);s&&(this.queue=JSON.parse(s))}}catch{this.queue=[]}}};var w=class{constructor(s){this.baseUrl=s.baseUrl.replace(/\/$/,""),this.apiKey=s.apiKey,this.getToken=s.getToken,this.timeout=s.timeout??1e4}async get(s){return this.request("GET",s)}async post(s,e){return this.request("POST",s,e)}async patch(s,e){return this.request("PATCH",s,e)}async del(s){return this.request("DELETE",s)}async request(s,e,t){let i={"Content-Type":"application/json"};if(this.apiKey&&(i["x-api-key"]=this.apiKey),this.getToken){let a=await this.getToken();a&&(i.Authorization=`Bearer ${a}`)}let n=new AbortController,r=setTimeout(()=>n.abort(),this.timeout);try{let a=await fetch(`${this.baseUrl}${e}`,{method:s,headers:i,body:t?JSON.stringify(t):void 0,signal:n.signal});if(clearTimeout(r),a.status===204)return{data:null,error:null};let o=await a.json().catch(()=>null);return a.ok?{data:o?.data!==void 0?o.data:o,error:null}:{data:null,error:{code:o?.error?.code??o?.code??"unknown",message:o?.error?.message??o?.message??a.statusText,status:a.status,details:o?.error?.details??o?.details}}}catch(a){return clearTimeout(r),{data:null,error:{code:"network_error",message:a instanceof Error?a.message:"Network error",status:0}}}}};var x=class extends g{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 i=e.wsUrl?e.wsUrl.replace(/\/$/,""):t;this.wsBaseUrl=i.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=i=>{this.handleMessage(i.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 i=await t.json();return i.ticket??i.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 Z=[0,1e3,3e3],R=45e3,ee=new Set([0,408,429,500,502,503,504]);function te(c){return new Promise(s=>setTimeout(s,c))}function se(c,s){return s==="aborted"?!1:ee.has(c)||s==="upload_stalled"}function ie(c,s,e,t){return new Promise(i=>{if(typeof XMLHttpRequest>"u"){i({data:null,error:{code:"unsupported_environment",message:"XMLHttpRequest is not available in this environment",status:0}});return}let n=new XMLHttpRequest,r=!1,a=null,o=0,l=s.size,h=p=>{r||(r=!0,a&&(clearTimeout(a),a=null),i(p))},f=()=>{a&&clearTimeout(a),a=setTimeout(()=>{n.abort(),h({data:null,error:{code:"upload_stalled",message:`Upload stalled (no progress for ${R/1e3}s)`,status:0,details:{bytes_sent:o,total_bytes:l}}})},R)};if(t){if(t.aborted){h({data:null,error:{code:"aborted",message:"Upload aborted",status:0}});return}t.addEventListener("abort",()=>{n.abort(),h({data:null,error:{code:"aborted",message:"Upload aborted",status:0}})},{once:!0})}n.upload.addEventListener("progress",p=>{f(),o=p.loaded,l=p.total||l,p.lengthComputable&&e?.(Math.round(p.loaded/p.total*100))}),n.addEventListener("load",()=>{if(n.status>=200&&n.status<300){e?.(100),h({data:null,error:null});return}h({data:null,error:{code:"upload_error",message:`S3 upload failed: ${n.status}`,status:n.status,details:{bytes_sent:o,total_bytes:l}}})}),n.addEventListener("error",()=>{h({data:null,error:{code:"upload_error",message:"S3 upload failed",status:n.status||0,details:{bytes_sent:o,total_bytes:l}}})}),n.addEventListener("abort",()=>{r||h({data:null,error:{code:"aborted",message:"Upload aborted",status:0}})}),n.open("PUT",c,!0),s.type&&n.setRequestHeader("Content-Type",s.type),f(),n.send(s)})}async function I(c,s,e,t){let i=null;for(let[n,r]of Z.entries()){r>0&&await te(r);let a=await ie(c,s,e,t);if(!a.error)return a;if(i={...a.error,details:{...a.error.details,attempt:n+1}},!se(a.error.status,a.error.code))break}return{data:null,error:i??{code:"upload_error",message:"Upload failed",status:0}}}var S=class extends g{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??M;this.currentUserId=e.userId,this.http=new w({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0)}),this.ws=new x({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 y(e.messageCache?.maxMessages,e.messageCache?.maxConversations),this.offlineQueue=new _(e.offlineQueue??!0),this.ws.on("status",i=>{switch(i){case"connected":this.emit("connected"),this.flushOfflineQueue();break;case"disconnected":this.emit("disconnected");break}}),this.ws.on("reconnecting",i=>{this.emit("reconnecting",i)}),this.ws.on("message",({channel:i,data:n})=>{this.handleRealtimeMessage(i,n)}),this.ws.on("presence:state",({channel:i,members:n})=>{let r=i.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:state",{conversationId:r,members:n})}),this.ws.on("presence:join",({channel:i,user:n})=>{let r=i.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:join",{userId:n.user_id,conversationId:r,userData:n.user_data})}),this.ws.on("presence:leave",({channel:i,userId:n})=>{let r=i.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:leave",{userId:n,conversationId:r})}),this.ws.on("presence:update",({channel:i,userId:n,status:r,userData:a})=>{let o=i.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:update",{userId:n,conversationId:o,status:r,userData:a})}),this.ws.on("error",({message:i})=>{this.emit("error",{code:"ws_error",message:i})})}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"},i=await this.http.post("/v1/chat/conversations",t);return i.data&&this.trackConversationType(i.data),i}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 i=t.toString(),n=await this.http.get(`/v1/chat/conversations${i?"?"+i:""}`);return n.data&&n.data.forEach(r=>this.trackConversationType(r)),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 i=await this.http.post(`/v1/chat/conversations/${e}/messages`,{content:t.content,message_type:t.message_type??"text",attachments:t.attachments});if(i.data){let n=this.cache.reconcileOptimisticMessage(e,i.data);this.cache.upsertMessage(e,n)}else i.error?.status===0&&this.offlineQueue.enqueue(e,t.content,t.message_type??"text",t.attachments);return i}async getMessages(e,t){let i=new URLSearchParams;t?.limit&&i.set("limit",String(t.limit)),t?.before&&i.set("before",t.before),t?.after&&i.set("after",t.after);let n=i.toString(),r=await this.http.get(`/v1/chat/conversations/${e}/messages${n?"?"+n:""}`);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,i){let n=typeof File<"u"&&e instanceof File?e.name:"attachment",r=e.type||"application/octet-stream",a=await this.http.post("/v1/storage/signed-url/upload",{filename:n,content_type:r,size_bytes:e.size,is_public:!1,metadata:{source:"chat_sdk"}});if(a.error||!a.data)return{data:null,error:a.error};let o=await I(a.data.upload_url,e,t,i);if(o.error)return{data:null,error:o.error};let l=await this.http.post("/v1/storage/signed-url/complete",{file_id:a.data.file_id,completion_token:a.data.completion_token});return l.error||!l.data?{data:null,error:l.error}:{data:{file_id:l.data.file_id,file_name:l.data.filename,file_size:l.data.size_bytes,mime_type:l.data.content_type,presigned_url:l.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,i){return this.http.post(`/v1/chat/messages/${e}/report`,{reason:t,description:i})}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 i=this.channelName(e);this.ws.joinPresence(i,t)}leavePresence(e){let t=this.channelName(e);this.ws.leavePresence(t)}updatePresence(e,t,i){let n=this.channelName(e);this.ws.send({type:"presence_update",channel:n,status:t,user_data:i})}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),i=this.ws.subscribe(t);return this.conversationSubs.set(e,i),()=>{this.conversationSubs.delete(e),i()}}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 i=t.event??t.type,n=t.data??t;switch(i){case"new_message":{let r=n.conversation_id,a=n.id??n.message_id,o=n.sender_id,l=n.content??"";r&&this.emit("inbox:update",{conversationId:r,messageId:a,senderId:o,preview:l});break}case"support:new_conversation":{let r=n.conversation_id,a=n.visitor_name;r&&this.emit("support:new",{conversationId:r,visitorName:a});break}case"support:assigned":{let r=n.conversation_id,a=n.visitor_name,o=n.visitor_email;r&&this.emit("support:assigned",{conversationId:r,visitorName:a,visitorEmail:o});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 i=t.message_id??t.id;if(!i)return null;let n=this.cache.getMessage(e,i);return{id:n?.id??i,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 i={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 i;let r=[...n.reactions??[]],a=r.findIndex(o=>o.emoji===t.emoji);if(t.action==="added")if(a>=0){let o=r[a];if(!o.user_ids.includes(t.user_id)){let l=[...o.user_ids,t.user_id];r[a]={...o,user_ids:l,count:l.length}}}else r.push({emoji:t.emoji,count:1,user_ids:[t.user_id]});else if(a>=0){let o=r[a],l=o.user_ids.filter(h=>h!==t.user_id);l.length===0?r.splice(a,1):r[a]={...o,user_ids:l,count:l.length}}return this.cache.updateMessage(e,{...n,reactions:r}),i}handleConversationMessage(e,t){let i=e.replace(/^conversation:(?:lr:|bc:|support:)?/,""),n=t;if(!n)return;let r=n.event??n.type,a=n.data??n;switch(r){case"new_message":{let o=this.normalizeMessage(a),l=this.cache.reconcileOptimisticMessage(i,o);this.cache.upsertMessage(i,l),this.emit("message",{message:l,conversationId:i});break}case"message_edited":{let o=a,l=this.buildEditedMessage(i,o);l&&(this.cache.upsertMessage(i,l),this.emit("message:updated",{message:l,conversationId:i,update:o}));break}case"reaction":{let o=a;if(!o.message_id||!o.user_id||!o.emoji)break;let l=this.applyReactionEvent(i,o);this.emit("reaction",{reaction:l,conversationId:i,action:o.action});break}case"message_deleted":{let o=a.message_id??a.id;o&&(this.cache.removeMessage(i,o),this.emit("message:deleted",{messageId:o,conversationId:i}));break}case"user_typing":{let o=a.user_id;o&&this.emit("typing",{userId:o,conversationId:i});break}case"user_stopped_typing":{let o=a.user_id;o&&this.emit("typing:stop",{userId:o,conversationId:i});break}case"typing_batch":{let o=a.users??[];for(let l of o)this.emit("typing",{userId:l,conversationId:i});break}case"messages_read":{let o=a.user_id,l=a.last_read_at;o&&l&&this.emit("read",{userId:o,conversationId:i,lastReadAt:l});break}case"room_upgraded":{if(a.new_type==="large_room"){this.conversationTypes.set(i,"large_room");let l=this.conversationSubs.get(i);l&&(l(),this.conversationSubs.delete(i),this.subscribeToConversation(i)),this.emit("room_upgraded",{conversationId:i,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 ne="sm_support_",C=class{constructor(s){this.chatClient=null;this.refreshToken=null;this.accessToken=null;this.tokenExpiresAt=0;this.userId=null;this.apiKey=s.apiKey,this.apiBaseUrl=s.apiBaseUrl??"https://api.scalemule.com",this.wsUrl=s.wsUrl,this.storageKey=ne+s.apiKey.substring(0,8);let e=this.loadState();e?(this.anonymousId=e.anonymous_id,this.refreshToken=e.refresh_token,this.userId=e.user_id):this.anonymousId=crypto.randomUUID()}async initVisitorSession(s){if(this.visitorName=s?.name,this.visitorEmail=s?.email,this.refreshToken)try{await this.refreshAccessToken(),this.initChatClient();return}catch{this.refreshToken=null}let e=await fetch(`${this.apiBaseUrl}/v1/auth/visitor-session`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.apiKey},body:JSON.stringify({anonymous_id:this.anonymousId,name:s?.name,email:s?.email,page_url:typeof location<"u"?location.href:void 0})});if(!e.ok){let n=await e.text();throw new Error(`Visitor session failed: ${e.status} ${n}`)}let i=(await e.json()).data;this.accessToken=i.access_token,this.refreshToken=i.refresh_token,this.userId=i.user_id,this.tokenExpiresAt=Date.now()+i.expires_in*1e3,this.saveState(),this.initChatClient()}async startConversation(s,e){if(!this.accessToken)throw new Error("Call initVisitorSession() first");let t=await fetch(`${this.apiBaseUrl}/v1/chat/support/conversations`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.apiKey,Authorization:`Bearer ${this.accessToken}`},body:JSON.stringify({message:s,name:this.visitorName,email:this.visitorEmail,page_url:e?.page_url??(typeof location<"u"?location.href:void 0),user_agent:typeof navigator<"u"?navigator.userAgent:void 0,attachments:e?.attachments,metadata:e?.metadata})});if(!t.ok){let r=await t.text();throw new Error(`Create support conversation failed: ${t.status} ${r}`)}let n=(await t.json()).data;return this.chatClient&&this.chatClient.conversationTypes?.set(n.conversation_id,"support"),n}async getActiveConversation(){if(!this.accessToken)return null;let s=await fetch(`${this.apiBaseUrl}/v1/chat/support/conversations/mine`,{headers:{"x-api-key":this.apiKey,Authorization:`Bearer ${this.accessToken}`}});if(!s.ok)return null;let i=(await s.json()).data.find(n=>n.status==="active"||n.status==="waiting");return i&&this.chatClient&&this.chatClient.conversationTypes?.set(i.conversation_id,"support"),i??null}async getWidgetConfig(){let s=await fetch(`${this.apiBaseUrl}/v1/chat/support/widget/config`,{headers:{"x-api-key":this.apiKey}});if(!s.ok){let t=await s.text();throw new Error(`Get widget config failed: ${s.status} ${t}`)}return(await s.json()).data}get chat(){if(!this.chatClient)throw new Error("Call initVisitorSession() first");return this.chatClient}get isInitialized(){return this.chatClient!==null}connect(){this.chat.connect()}disconnect(){this.chat.disconnect()}get visitorUserId(){return this.userId}destroy(){this.chatClient?.destroy(),this.chatClient=null}initChatClient(){this.chatClient&&this.chatClient.destroy();let s={apiKey:this.apiKey,apiBaseUrl:this.apiBaseUrl,wsUrl:this.wsUrl,getToken:async()=>{if(!this.accessToken)return null;if(Date.now()>this.tokenExpiresAt-6e4)try{await this.refreshAccessToken()}catch{}return this.accessToken}};this.chatClient=new S(s)}async refreshAccessToken(){if(!this.refreshToken)throw new Error("No refresh token");let s=await fetch(`${this.apiBaseUrl}/v1/auth/token/refresh`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.apiKey},body:JSON.stringify({refresh_token:this.refreshToken})});if(!s.ok)throw new Error(`Token refresh failed: ${s.status}`);let t=(await s.json()).data;this.accessToken=t.access_token,this.tokenExpiresAt=Date.now()+t.expires_in*1e3}loadState(){try{let s=localStorage.getItem(this.storageKey);return s?JSON.parse(s):null}catch{return null}}saveState(){try{localStorage.setItem(this.storageKey,JSON.stringify({anonymous_id:this.anonymousId,refresh_token:this.refreshToken,user_id:this.userId}))}catch{}}};var P='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="28" height="28"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12z"/><path d="M7 9h2v2H7zm4 0h2v2h-2zm4 0h2v2h-2z"/></svg>',L='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>',U='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>',$='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>',E='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M16.5 6.5v9.25a4.75 4.75 0 1 1-9.5 0V5.75a3.25 3.25 0 1 1 6.5 0V14a1.75 1.75 0 1 1-3.5 0V6.5H8.5V14a3.25 3.25 0 1 0 6.5 0V5.75a4.75 4.75 0 1 0-9.5 0v10a6.25 6.25 0 1 0 12.5 0V6.5h-1.5Z"/></svg>',O='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M12 22a10 10 0 1 1 10-10 10.01 10.01 0 0 1-10 10Zm0-18.5a8.5 8.5 0 1 0 8.5 8.5A8.51 8.51 0 0 0 12 3.5Zm-3 7a1.25 1.25 0 1 1 1.25-1.25A1.25 1.25 0 0 1 9 10.5Zm6 0a1.25 1.25 0 1 1 1.25-1.25A1.25 1.25 0 0 1 15 10.5Zm-3 6.25A5.22 5.22 0 0 1 7.58 14h1.71a3.5 3.5 0 0 0 5.42 0h1.71A5.22 5.22 0 0 1 12 16.75Z"/></svg>';var D=`
|
|
2
2
|
:host {
|
|
3
3
|
all: initial;
|
|
4
|
-
|
|
4
|
+
--sm-primary: #2563eb;
|
|
5
|
+
--sm-primary-hover: #1d4ed8;
|
|
6
|
+
--sm-primary-disabled: #93c5fd;
|
|
7
|
+
--sm-primary-text: #ffffff;
|
|
8
|
+
--sm-badge-bg: #ef4444;
|
|
9
|
+
--sm-bubble-left: auto;
|
|
10
|
+
--sm-bubble-right: 20px;
|
|
11
|
+
--sm-panel-left: auto;
|
|
12
|
+
--sm-panel-right: 20px;
|
|
13
|
+
--sm-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
14
|
+
font-family: var(--sm-font-family);
|
|
5
15
|
font-size: 14px;
|
|
6
16
|
line-height: 1.5;
|
|
7
|
-
color: #
|
|
17
|
+
color: #111827;
|
|
8
18
|
}
|
|
9
19
|
|
|
10
20
|
* {
|
|
@@ -13,69 +23,67 @@
|
|
|
13
23
|
padding: 0;
|
|
14
24
|
}
|
|
15
25
|
|
|
16
|
-
/* ============ Bubble ============ */
|
|
17
|
-
|
|
18
26
|
.sm-bubble {
|
|
19
27
|
position: fixed;
|
|
20
28
|
bottom: 20px;
|
|
21
|
-
|
|
29
|
+
left: var(--sm-bubble-left);
|
|
30
|
+
right: var(--sm-bubble-right);
|
|
22
31
|
width: 60px;
|
|
23
32
|
height: 60px;
|
|
24
|
-
border-radius:
|
|
25
|
-
background:
|
|
26
|
-
color:
|
|
33
|
+
border-radius: 999px;
|
|
34
|
+
background: var(--sm-primary);
|
|
35
|
+
color: var(--sm-primary-text);
|
|
27
36
|
border: none;
|
|
28
37
|
cursor: pointer;
|
|
29
38
|
display: flex;
|
|
30
39
|
align-items: center;
|
|
31
40
|
justify-content: center;
|
|
32
|
-
box-shadow: 0
|
|
41
|
+
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.22);
|
|
33
42
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
34
43
|
z-index: 999999;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
.sm-bubble:hover {
|
|
38
|
-
transform: scale(1.
|
|
39
|
-
box-shadow: 0
|
|
47
|
+
transform: translateY(-1px) scale(1.03);
|
|
48
|
+
box-shadow: 0 18px 36px rgba(15, 23, 42, 0.28);
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
.sm-bubble .sm-badge {
|
|
43
52
|
position: absolute;
|
|
44
53
|
top: -4px;
|
|
45
54
|
right: -4px;
|
|
46
|
-
background: #ef4444;
|
|
47
|
-
color: #fff;
|
|
48
|
-
border-radius: 10px;
|
|
49
|
-
font-size: 11px;
|
|
50
|
-
font-weight: 600;
|
|
51
55
|
min-width: 20px;
|
|
52
56
|
height: 20px;
|
|
57
|
+
border-radius: 999px;
|
|
58
|
+
background: var(--sm-badge-bg);
|
|
59
|
+
color: #fff;
|
|
53
60
|
display: flex;
|
|
54
61
|
align-items: center;
|
|
55
62
|
justify-content: center;
|
|
56
63
|
padding: 0 6px;
|
|
64
|
+
font-size: 11px;
|
|
65
|
+
font-weight: 700;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
/* ============ Panel ============ */
|
|
60
|
-
|
|
61
68
|
.sm-panel {
|
|
62
69
|
position: fixed;
|
|
63
70
|
bottom: 90px;
|
|
64
|
-
|
|
71
|
+
left: var(--sm-panel-left);
|
|
72
|
+
right: var(--sm-panel-right);
|
|
65
73
|
width: 380px;
|
|
66
|
-
max-height: 560px;
|
|
67
|
-
background: #
|
|
68
|
-
border-radius:
|
|
69
|
-
box-shadow: 0
|
|
74
|
+
max-height: min(560px, calc(100vh - 110px));
|
|
75
|
+
background: #ffffff;
|
|
76
|
+
border-radius: 18px;
|
|
77
|
+
box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);
|
|
70
78
|
display: flex;
|
|
71
79
|
flex-direction: column;
|
|
72
80
|
overflow: hidden;
|
|
73
81
|
z-index: 999998;
|
|
74
|
-
animation: sm-slide-up 0.
|
|
82
|
+
animation: sm-slide-up 0.22s ease;
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
@keyframes sm-slide-up {
|
|
78
|
-
from { opacity: 0; transform: translateY(
|
|
86
|
+
from { opacity: 0; transform: translateY(14px); }
|
|
79
87
|
to { opacity: 1; transform: translateY(0); }
|
|
80
88
|
}
|
|
81
89
|
|
|
@@ -83,64 +91,106 @@
|
|
|
83
91
|
display: none;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
/* ============ Header ============ */
|
|
87
|
-
|
|
88
94
|
.sm-header {
|
|
89
|
-
background:
|
|
90
|
-
color:
|
|
91
|
-
padding: 16px
|
|
95
|
+
background: var(--sm-primary);
|
|
96
|
+
color: var(--sm-primary-text);
|
|
97
|
+
padding: 16px 18px;
|
|
92
98
|
display: flex;
|
|
93
|
-
align-items:
|
|
99
|
+
align-items: flex-start;
|
|
94
100
|
justify-content: space-between;
|
|
101
|
+
gap: 16px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.sm-header-copy {
|
|
105
|
+
min-width: 0;
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
.sm-header-title {
|
|
98
109
|
font-size: 16px;
|
|
99
|
-
font-weight:
|
|
110
|
+
font-weight: 700;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.sm-header-status {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 6px;
|
|
117
|
+
margin-top: 4px;
|
|
118
|
+
font-size: 12px;
|
|
119
|
+
opacity: 0.92;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.sm-status-dot {
|
|
123
|
+
width: 10px;
|
|
124
|
+
height: 10px;
|
|
125
|
+
border-radius: 999px;
|
|
126
|
+
background: rgba(255, 255, 255, 0.65);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.sm-status-dot-online {
|
|
130
|
+
background: #22c55e;
|
|
131
|
+
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.sm-status-dot-away {
|
|
135
|
+
background: #cbd5e1;
|
|
100
136
|
}
|
|
101
137
|
|
|
102
138
|
.sm-header-subtitle {
|
|
103
139
|
font-size: 12px;
|
|
104
|
-
opacity: 0.
|
|
105
|
-
margin-top:
|
|
140
|
+
opacity: 0.82;
|
|
141
|
+
margin-top: 6px;
|
|
142
|
+
max-width: 250px;
|
|
106
143
|
}
|
|
107
144
|
|
|
108
145
|
.sm-header-actions {
|
|
109
146
|
display: flex;
|
|
110
|
-
gap:
|
|
147
|
+
gap: 6px;
|
|
111
148
|
}
|
|
112
149
|
|
|
113
150
|
.sm-header-btn {
|
|
114
151
|
background: none;
|
|
115
152
|
border: none;
|
|
116
|
-
color:
|
|
153
|
+
color: inherit;
|
|
117
154
|
cursor: pointer;
|
|
118
|
-
|
|
119
|
-
padding:
|
|
120
|
-
border-radius: 4px;
|
|
155
|
+
border-radius: 10px;
|
|
156
|
+
padding: 6px;
|
|
121
157
|
display: flex;
|
|
122
158
|
align-items: center;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
opacity: 0.86;
|
|
123
161
|
}
|
|
124
162
|
|
|
125
163
|
.sm-header-btn:hover {
|
|
126
164
|
opacity: 1;
|
|
127
|
-
background: rgba(255, 255, 255, 0.
|
|
165
|
+
background: rgba(255, 255, 255, 0.12);
|
|
128
166
|
}
|
|
129
167
|
|
|
130
|
-
|
|
168
|
+
.sm-error {
|
|
169
|
+
padding: 10px 14px;
|
|
170
|
+
background: #fef2f2;
|
|
171
|
+
border-bottom: 1px solid #fecaca;
|
|
172
|
+
color: #b91c1c;
|
|
173
|
+
font-size: 12px;
|
|
174
|
+
}
|
|
131
175
|
|
|
132
|
-
.sm-
|
|
133
|
-
padding: 24px 20px;
|
|
176
|
+
.sm-body {
|
|
134
177
|
flex: 1;
|
|
178
|
+
min-height: 0;
|
|
135
179
|
display: flex;
|
|
136
180
|
flex-direction: column;
|
|
137
|
-
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.sm-prechat {
|
|
184
|
+
padding: 20px 18px 18px;
|
|
185
|
+
display: flex;
|
|
186
|
+
flex-direction: column;
|
|
187
|
+
gap: 14px;
|
|
138
188
|
}
|
|
139
189
|
|
|
140
190
|
.sm-prechat-title {
|
|
141
191
|
font-size: 18px;
|
|
142
|
-
font-weight:
|
|
143
|
-
color: #
|
|
192
|
+
font-weight: 700;
|
|
193
|
+
color: #111827;
|
|
144
194
|
}
|
|
145
195
|
|
|
146
196
|
.sm-prechat-desc {
|
|
@@ -151,194 +201,414 @@
|
|
|
151
201
|
.sm-field {
|
|
152
202
|
display: flex;
|
|
153
203
|
flex-direction: column;
|
|
154
|
-
gap:
|
|
204
|
+
gap: 6px;
|
|
155
205
|
}
|
|
156
206
|
|
|
157
207
|
.sm-field label {
|
|
158
208
|
font-size: 13px;
|
|
159
|
-
font-weight:
|
|
209
|
+
font-weight: 600;
|
|
160
210
|
color: #374151;
|
|
161
211
|
}
|
|
162
212
|
|
|
163
213
|
.sm-field input,
|
|
164
|
-
.sm-field textarea
|
|
214
|
+
.sm-field textarea,
|
|
215
|
+
.sm-input {
|
|
165
216
|
border: 1px solid #d1d5db;
|
|
166
|
-
border-radius:
|
|
217
|
+
border-radius: 14px;
|
|
167
218
|
padding: 10px 12px;
|
|
168
219
|
font-size: 14px;
|
|
169
220
|
font-family: inherit;
|
|
221
|
+
color: #111827;
|
|
170
222
|
outline: none;
|
|
171
|
-
transition: border-color 0.15s;
|
|
223
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
224
|
+
background: #fff;
|
|
172
225
|
}
|
|
173
226
|
|
|
174
227
|
.sm-field input:focus,
|
|
175
|
-
.sm-field textarea:focus
|
|
176
|
-
|
|
177
|
-
|
|
228
|
+
.sm-field textarea:focus,
|
|
229
|
+
.sm-input:focus {
|
|
230
|
+
border-color: var(--sm-primary);
|
|
231
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12);
|
|
178
232
|
}
|
|
179
233
|
|
|
180
234
|
.sm-field textarea {
|
|
181
235
|
resize: vertical;
|
|
182
|
-
min-height:
|
|
236
|
+
min-height: 92px;
|
|
183
237
|
}
|
|
184
238
|
|
|
185
|
-
.sm-
|
|
186
|
-
|
|
187
|
-
|
|
239
|
+
.sm-prechat-actions {
|
|
240
|
+
display: flex;
|
|
241
|
+
gap: 10px;
|
|
242
|
+
align-items: center;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.sm-submit-btn,
|
|
246
|
+
.sm-send-btn {
|
|
188
247
|
border: none;
|
|
189
|
-
border-radius: 8px;
|
|
190
|
-
padding: 12px;
|
|
191
|
-
font-size: 14px;
|
|
192
|
-
font-weight: 600;
|
|
193
248
|
cursor: pointer;
|
|
194
|
-
|
|
249
|
+
background: var(--sm-primary);
|
|
250
|
+
color: var(--sm-primary-text);
|
|
251
|
+
transition: background 0.15s ease, opacity 0.15s ease;
|
|
195
252
|
}
|
|
196
253
|
|
|
197
|
-
.sm-submit-btn:hover
|
|
198
|
-
|
|
254
|
+
.sm-submit-btn:hover,
|
|
255
|
+
.sm-send-btn:hover {
|
|
256
|
+
background: var(--sm-primary-hover);
|
|
199
257
|
}
|
|
200
258
|
|
|
201
|
-
.sm-submit-btn:disabled
|
|
202
|
-
|
|
259
|
+
.sm-submit-btn:disabled,
|
|
260
|
+
.sm-send-btn:disabled {
|
|
261
|
+
background: var(--sm-primary-disabled);
|
|
203
262
|
cursor: not-allowed;
|
|
204
263
|
}
|
|
205
264
|
|
|
206
|
-
|
|
265
|
+
.sm-submit-btn {
|
|
266
|
+
flex: 1;
|
|
267
|
+
border-radius: 14px;
|
|
268
|
+
padding: 12px;
|
|
269
|
+
font-size: 14px;
|
|
270
|
+
font-weight: 700;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.sm-chat-shell {
|
|
274
|
+
flex: 1;
|
|
275
|
+
min-height: 0;
|
|
276
|
+
display: flex;
|
|
277
|
+
flex-direction: column;
|
|
278
|
+
background: #f8fafc;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.sm-chat-shell.sm-dragging {
|
|
282
|
+
background: rgba(37, 99, 235, 0.05);
|
|
283
|
+
}
|
|
207
284
|
|
|
208
285
|
.sm-messages {
|
|
209
286
|
flex: 1;
|
|
287
|
+
min-height: 0;
|
|
210
288
|
overflow-y: auto;
|
|
211
|
-
padding: 16px
|
|
289
|
+
padding: 16px;
|
|
212
290
|
display: flex;
|
|
213
291
|
flex-direction: column;
|
|
214
292
|
gap: 12px;
|
|
215
|
-
|
|
216
|
-
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.sm-date-divider {
|
|
296
|
+
align-self: center;
|
|
297
|
+
font-size: 11px;
|
|
298
|
+
font-weight: 600;
|
|
299
|
+
color: #6b7280;
|
|
300
|
+
background: rgba(148, 163, 184, 0.16);
|
|
301
|
+
padding: 4px 10px;
|
|
302
|
+
border-radius: 999px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.sm-unread-divider {
|
|
306
|
+
display: flex;
|
|
307
|
+
align-items: center;
|
|
308
|
+
gap: 12px;
|
|
309
|
+
font-size: 11px;
|
|
310
|
+
font-weight: 700;
|
|
311
|
+
color: var(--sm-primary);
|
|
312
|
+
text-transform: uppercase;
|
|
313
|
+
letter-spacing: 0.04em;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.sm-unread-divider::before,
|
|
317
|
+
.sm-unread-divider::after {
|
|
318
|
+
content: '';
|
|
319
|
+
flex: 1;
|
|
320
|
+
height: 1px;
|
|
321
|
+
background: rgba(37, 99, 235, 0.24);
|
|
217
322
|
}
|
|
218
323
|
|
|
219
324
|
.sm-msg {
|
|
220
325
|
display: flex;
|
|
221
326
|
flex-direction: column;
|
|
222
|
-
|
|
327
|
+
gap: 6px;
|
|
328
|
+
max-width: 84%;
|
|
223
329
|
}
|
|
224
330
|
|
|
225
|
-
.sm-msg
|
|
331
|
+
.sm-msg-visitor {
|
|
226
332
|
align-self: flex-end;
|
|
227
333
|
}
|
|
228
334
|
|
|
229
|
-
.sm-msg
|
|
335
|
+
.sm-msg-rep {
|
|
230
336
|
align-self: flex-start;
|
|
231
337
|
}
|
|
232
338
|
|
|
233
|
-
.sm-msg
|
|
339
|
+
.sm-msg-system {
|
|
234
340
|
align-self: center;
|
|
235
|
-
max-width:
|
|
341
|
+
max-width: 92%;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.sm-msg-row {
|
|
345
|
+
display: flex;
|
|
346
|
+
align-items: flex-end;
|
|
347
|
+
gap: 8px;
|
|
236
348
|
}
|
|
237
349
|
|
|
238
350
|
.sm-msg-bubble {
|
|
239
|
-
padding: 10px
|
|
240
|
-
border-radius:
|
|
241
|
-
font-size: 14px;
|
|
351
|
+
padding: 10px 12px;
|
|
352
|
+
border-radius: 16px;
|
|
242
353
|
line-height: 1.4;
|
|
243
354
|
word-break: break-word;
|
|
355
|
+
background: #f3f4f6;
|
|
356
|
+
color: #111827;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.sm-msg-content {
|
|
360
|
+
white-space: pre-wrap;
|
|
244
361
|
}
|
|
245
362
|
|
|
246
363
|
.sm-msg-visitor .sm-msg-bubble {
|
|
247
|
-
background:
|
|
248
|
-
color:
|
|
249
|
-
border-bottom-right-radius:
|
|
364
|
+
background: var(--sm-primary);
|
|
365
|
+
color: var(--sm-primary-text);
|
|
366
|
+
border-bottom-right-radius: 6px;
|
|
250
367
|
}
|
|
251
368
|
|
|
252
369
|
.sm-msg-rep .sm-msg-bubble {
|
|
253
|
-
|
|
254
|
-
color: #1a1a1a;
|
|
255
|
-
border-bottom-left-radius: 4px;
|
|
370
|
+
border-bottom-left-radius: 6px;
|
|
256
371
|
}
|
|
257
372
|
|
|
258
373
|
.sm-msg-system .sm-msg-bubble {
|
|
259
374
|
background: transparent;
|
|
260
|
-
color: #
|
|
375
|
+
color: #94a3b8;
|
|
261
376
|
font-size: 12px;
|
|
262
377
|
text-align: center;
|
|
263
|
-
padding:
|
|
378
|
+
padding: 2px 8px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.sm-msg-action {
|
|
382
|
+
border: 1px solid #e5e7eb;
|
|
383
|
+
background: #ffffff;
|
|
384
|
+
color: #6b7280;
|
|
385
|
+
border-radius: 999px;
|
|
386
|
+
width: 28px;
|
|
387
|
+
height: 28px;
|
|
388
|
+
display: inline-flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
justify-content: center;
|
|
391
|
+
cursor: pointer;
|
|
392
|
+
flex-shrink: 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.sm-msg-action:hover {
|
|
396
|
+
color: var(--sm-primary);
|
|
397
|
+
border-color: rgba(37, 99, 235, 0.24);
|
|
264
398
|
}
|
|
265
399
|
|
|
266
400
|
.sm-msg-time {
|
|
267
401
|
font-size: 11px;
|
|
268
|
-
color: #
|
|
269
|
-
margin-top: 4px;
|
|
402
|
+
color: #94a3b8;
|
|
270
403
|
}
|
|
271
404
|
|
|
272
405
|
.sm-msg-visitor .sm-msg-time {
|
|
273
406
|
text-align: right;
|
|
274
407
|
}
|
|
275
408
|
|
|
409
|
+
.sm-reactions {
|
|
410
|
+
display: flex;
|
|
411
|
+
flex-wrap: wrap;
|
|
412
|
+
gap: 6px;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.sm-reaction-badge {
|
|
416
|
+
border: 1px solid #e5e7eb;
|
|
417
|
+
background: #ffffff;
|
|
418
|
+
color: #374151;
|
|
419
|
+
border-radius: 999px;
|
|
420
|
+
padding: 4px 8px;
|
|
421
|
+
font-size: 12px;
|
|
422
|
+
cursor: pointer;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.sm-reaction-badge-active {
|
|
426
|
+
border-color: rgba(37, 99, 235, 0.32);
|
|
427
|
+
background: rgba(37, 99, 235, 0.08);
|
|
428
|
+
color: var(--sm-primary);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.sm-reaction-picker {
|
|
432
|
+
display: inline-flex;
|
|
433
|
+
gap: 6px;
|
|
434
|
+
padding: 6px;
|
|
435
|
+
border-radius: 999px;
|
|
436
|
+
border: 1px solid #e5e7eb;
|
|
437
|
+
background: #ffffff;
|
|
438
|
+
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.sm-reaction-picker-btn {
|
|
442
|
+
border: none;
|
|
443
|
+
background: transparent;
|
|
444
|
+
cursor: pointer;
|
|
445
|
+
border-radius: 999px;
|
|
446
|
+
width: 30px;
|
|
447
|
+
height: 30px;
|
|
448
|
+
display: inline-flex;
|
|
449
|
+
align-items: center;
|
|
450
|
+
justify-content: center;
|
|
451
|
+
font-size: 17px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.sm-attachment {
|
|
455
|
+
display: block;
|
|
456
|
+
margin-top: 8px;
|
|
457
|
+
border-radius: 12px;
|
|
458
|
+
max-width: 100%;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.sm-attachment-image,
|
|
462
|
+
.sm-attachment-video {
|
|
463
|
+
max-width: 260px;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.sm-attachment-audio {
|
|
467
|
+
width: 100%;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.sm-attachment-link {
|
|
471
|
+
color: inherit;
|
|
472
|
+
text-decoration: underline;
|
|
473
|
+
font-size: 13px;
|
|
474
|
+
}
|
|
475
|
+
|
|
276
476
|
.sm-typing {
|
|
477
|
+
min-height: 22px;
|
|
478
|
+
padding: 0 16px 10px;
|
|
479
|
+
font-size: 12px;
|
|
480
|
+
color: #6b7280;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.sm-typing-indicator {
|
|
484
|
+
display: inline-flex;
|
|
485
|
+
align-items: center;
|
|
486
|
+
gap: 6px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.sm-typing-indicator span:nth-child(-n+3) {
|
|
490
|
+
width: 6px;
|
|
491
|
+
height: 6px;
|
|
492
|
+
border-radius: 999px;
|
|
493
|
+
background: #94a3b8;
|
|
494
|
+
animation: sm-bounce 1.2s infinite ease-in-out;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.sm-typing-indicator span:nth-child(2) {
|
|
498
|
+
animation-delay: 0.15s;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.sm-typing-indicator span:nth-child(3) {
|
|
502
|
+
animation-delay: 0.3s;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
@keyframes sm-bounce {
|
|
506
|
+
0%, 80%, 100% { transform: scale(0.85); opacity: 0.5; }
|
|
507
|
+
40% { transform: scale(1); opacity: 1; }
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.sm-upload-list {
|
|
511
|
+
display: flex;
|
|
512
|
+
flex-wrap: wrap;
|
|
513
|
+
gap: 8px;
|
|
514
|
+
padding: 0 16px 10px;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.sm-upload-chip {
|
|
518
|
+
display: inline-flex;
|
|
519
|
+
align-items: center;
|
|
520
|
+
gap: 8px;
|
|
521
|
+
max-width: 100%;
|
|
522
|
+
border-radius: 999px;
|
|
523
|
+
border: 1px solid #e5e7eb;
|
|
524
|
+
background: #ffffff;
|
|
525
|
+
padding: 6px 10px;
|
|
277
526
|
font-size: 12px;
|
|
278
|
-
color: #
|
|
279
|
-
|
|
280
|
-
|
|
527
|
+
color: #374151;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.sm-upload-chip-error {
|
|
531
|
+
border-color: #fecaca;
|
|
532
|
+
background: #fef2f2;
|
|
533
|
+
color: #b91c1c;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.sm-upload-name {
|
|
537
|
+
max-width: 140px;
|
|
538
|
+
overflow: hidden;
|
|
539
|
+
text-overflow: ellipsis;
|
|
540
|
+
white-space: nowrap;
|
|
281
541
|
}
|
|
282
542
|
|
|
283
|
-
|
|
543
|
+
.sm-upload-progress {
|
|
544
|
+
color: #6b7280;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.sm-upload-chip-error .sm-upload-progress {
|
|
548
|
+
color: inherit;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.sm-upload-remove {
|
|
552
|
+
border: none;
|
|
553
|
+
background: transparent;
|
|
554
|
+
color: inherit;
|
|
555
|
+
cursor: pointer;
|
|
556
|
+
font-size: 14px;
|
|
557
|
+
line-height: 1;
|
|
558
|
+
}
|
|
284
559
|
|
|
285
560
|
.sm-input-area {
|
|
286
|
-
border-top: 1px solid #e5e7eb;
|
|
287
|
-
padding: 12px 16px;
|
|
288
561
|
display: flex;
|
|
289
562
|
align-items: flex-end;
|
|
290
|
-
gap:
|
|
563
|
+
gap: 10px;
|
|
564
|
+
padding: 12px 16px 16px;
|
|
565
|
+
border-top: 1px solid #e5e7eb;
|
|
566
|
+
background: #ffffff;
|
|
291
567
|
}
|
|
292
568
|
|
|
293
569
|
.sm-input {
|
|
294
570
|
flex: 1;
|
|
295
|
-
|
|
296
|
-
border-radius: 20px;
|
|
297
|
-
padding: 10px 16px;
|
|
298
|
-
font-size: 14px;
|
|
299
|
-
font-family: inherit;
|
|
300
|
-
outline: none;
|
|
301
|
-
resize: none;
|
|
571
|
+
min-height: 44px;
|
|
302
572
|
max-height: 100px;
|
|
573
|
+
resize: none;
|
|
303
574
|
overflow-y: auto;
|
|
304
575
|
line-height: 1.4;
|
|
305
576
|
}
|
|
306
577
|
|
|
307
|
-
.sm-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
background: #
|
|
313
|
-
color: #
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
width: 36px;
|
|
317
|
-
height: 36px;
|
|
318
|
-
display: flex;
|
|
578
|
+
.sm-attach-btn {
|
|
579
|
+
width: 42px;
|
|
580
|
+
height: 42px;
|
|
581
|
+
border-radius: 14px;
|
|
582
|
+
border: 1px solid #d1d5db;
|
|
583
|
+
background: #ffffff;
|
|
584
|
+
color: #374151;
|
|
585
|
+
cursor: pointer;
|
|
586
|
+
display: inline-flex;
|
|
319
587
|
align-items: center;
|
|
320
588
|
justify-content: center;
|
|
321
|
-
cursor: pointer;
|
|
322
589
|
flex-shrink: 0;
|
|
323
|
-
transition: background 0.15s;
|
|
324
590
|
}
|
|
325
591
|
|
|
326
|
-
.sm-
|
|
327
|
-
|
|
592
|
+
.sm-attach-btn:hover {
|
|
593
|
+
border-color: rgba(37, 99, 235, 0.28);
|
|
594
|
+
color: var(--sm-primary);
|
|
328
595
|
}
|
|
329
596
|
|
|
330
|
-
.sm-send-btn
|
|
331
|
-
|
|
332
|
-
|
|
597
|
+
.sm-send-btn {
|
|
598
|
+
width: 42px;
|
|
599
|
+
height: 42px;
|
|
600
|
+
border-radius: 14px;
|
|
601
|
+
display: inline-flex;
|
|
602
|
+
align-items: center;
|
|
603
|
+
justify-content: center;
|
|
604
|
+
flex-shrink: 0;
|
|
333
605
|
}
|
|
334
606
|
|
|
335
|
-
/* ============ Footer ============ */
|
|
336
|
-
|
|
337
607
|
.sm-footer {
|
|
338
608
|
text-align: center;
|
|
339
609
|
padding: 8px;
|
|
340
610
|
font-size: 11px;
|
|
341
|
-
color: #
|
|
611
|
+
color: #94a3b8;
|
|
342
612
|
}
|
|
343
613
|
|
|
344
614
|
.sm-footer a {
|
|
@@ -350,54 +620,112 @@
|
|
|
350
620
|
text-decoration: underline;
|
|
351
621
|
}
|
|
352
622
|
|
|
353
|
-
/* ============ Responsive ============ */
|
|
354
|
-
|
|
355
623
|
@media (max-width: 440px) {
|
|
356
624
|
.sm-panel {
|
|
357
|
-
bottom: 0;
|
|
358
|
-
right: 0;
|
|
359
625
|
left: 0;
|
|
626
|
+
right: 0;
|
|
627
|
+
bottom: 0;
|
|
360
628
|
width: 100%;
|
|
361
629
|
max-height: 100vh;
|
|
362
|
-
border-radius:
|
|
630
|
+
border-radius: 18px 18px 0 0;
|
|
363
631
|
}
|
|
364
632
|
}
|
|
365
|
-
`;var
|
|
633
|
+
`;var re="sm_widget_";function N(c,s){try{localStorage.setItem(re+c,s)}catch{}}var oe=["\u{1F44D}","\u2764\uFE0F","\u{1F602}","\u{1F389}","\u{1F62E}","\u{1F440}"],m={title:"Support",subtitle:"We typically reply within a few minutes",primary_color:"#2563eb",position:"right",pre_chat_fields:[{key:"name",label:"Name",type:"text",required:!0},{key:"email",label:"Email",type:"email",required:!1}],business_hours:{},realtime_enabled:!1,welcome_message:"Hi! How can we help?",offline_message:"We're currently offline. Leave a message!",reps_online:!1,online_count:0};function d(c){return String(c??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function T(c=""){return{conversationId:c,messages:[],readStatuses:[],typingUsers:[],members:[],hasMore:!1,isLoading:!1,error:null}}function le(c){try{return new Date(c).toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit"})}catch{return""}}function ce(c){try{return new Date(c).toLocaleDateString(void 0,{month:"short",day:"numeric",year:"numeric"})}catch{return""}}function de(c,s){let e=new Date(c),t=new Date(s);return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()&&e.getDate()===t.getDate()}function F(c,s){let e=c.replace("#","");if(!/^[0-9a-fA-F]{6}$/.test(e))return c;let t=Number.parseInt(e,16),i=t>>16&255,n=t>>8&255,r=t&255,a=o=>Math.max(0,Math.min(255,Math.round(o+s*255))).toString(16).padStart(2,"0");return`#${a(i)}${a(n)}${a(r)}`}function H(c,s){return s.length?!c&&s.every(e=>e.mime_type.startsWith("image/"))?"image":"file":"text"}function he(c,s){return s?c.find(e=>e.user_id===s)?.last_read_at??null:null}function B(c,s,e){return{id:`pending-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,sender_id:c,sender_type:"human",content:s,message_type:H(s,e),attachments:e,reactions:[],is_edited:!1,created_at:new Date().toISOString()}}function pe(c,s){let e=d(s.file_name),t=d(s.presigned_url),i=d(s.file_id);return s.presigned_url?s.mime_type.startsWith("image/")?`<img class="sm-attachment sm-attachment-image" src="${t}" alt="${e}" loading="lazy" />`:s.mime_type.startsWith("video/")?`<video class="sm-attachment sm-attachment-video" src="${t}" controls preload="metadata"></video>`:s.mime_type.startsWith("audio/")?`<audio class="sm-attachment sm-attachment-audio" src="${t}" controls preload="metadata"></audio>`:`<a class="sm-attachment sm-attachment-link" href="${t}" target="_blank" rel="noreferrer">${e}</a>`:`<div class="sm-attachment sm-attachment-link" data-file-id="${i}" data-message-id="${d(c)}">${e}</div>`}var k=class{constructor(s,e,t={}){this.config=m;this.configLoaded=!1;this.conversation=null;this.controller=null;this.runtimeCleanups=[];this.runtimeState=T();this.panelEl=null;this.bodyEl=null;this.messagesEl=null;this.typingEl=null;this.uploadListEl=null;this.inputEl=null;this.fileInputEl=null;this.chatShellEl=null;this.errorBannerEl=null;this.isOpen=!1;this.unreadCount=0;this.unreadMarkerTimestamp=null;this.shouldScrollToUnread=!1;this.fallbackNotice=null;this.activeErrorMessage=null;this.realtimeFallbackActive=!1;this.pendingAttachments=[];this.openReactionMessageId=null;this.pollInterval=null;this.typingStopTimer=null;this.realtimeStatusCleanups=[];this.client=new C({apiKey:s,apiBaseUrl:e}),this.options=t,this.root=document.createElement("div"),this.root.id="scalemule-support-widget",this.shadow=this.root.attachShadow({mode:"closed"}),this.applyConfigTheme();let i=document.createElement("style");i.textContent=D,this.shadow.appendChild(i),this.renderBubble(),document.body.appendChild(this.root)}applyConfigTheme(){let s=this.options.color??this.config.primary_color??m.primary_color,e=this.options.position??this.config.position??m.position,t=F(s,-.12),i=F(s,.22);this.root.style.setProperty("--sm-primary",s),this.root.style.setProperty("--sm-primary-hover",t),this.root.style.setProperty("--sm-primary-disabled",i),this.root.style.setProperty("--sm-position",e),this.root.style.setProperty("--sm-bubble-left",e==="left"?"20px":"auto"),this.root.style.setProperty("--sm-bubble-right",e==="right"?"20px":"auto"),this.root.style.setProperty("--sm-panel-left",e==="left"?"20px":"auto"),this.root.style.setProperty("--sm-panel-right",e==="right"?"20px":"auto")}async ensureConfigLoaded(){if(!this.configLoaded){try{let s=await this.client.getWidgetConfig();this.config={...m,...s,primary_color:this.options.color??s.primary_color??m.primary_color,position:this.options.position??s.position??m.position}}catch{this.config={...m,primary_color:this.options.color??m.primary_color,position:this.options.position??m.position}}this.configLoaded=!0,this.applyConfigTheme()}}renderBubble(){let s=document.createElement("button");s.className="sm-bubble",s.innerHTML=P,s.setAttribute("aria-label","Open support chat"),s.addEventListener("click",()=>{this.toggle()}),this.shadow.appendChild(s)}renderPanel(){if(this.panelEl)return;let s=document.createElement("div");s.className="sm-panel sm-hidden",s.innerHTML=`
|
|
366
634
|
<div class="sm-header">
|
|
367
|
-
<div>
|
|
368
|
-
<div class="sm-header-title"
|
|
369
|
-
<div class="sm-header-
|
|
635
|
+
<div class="sm-header-copy">
|
|
636
|
+
<div class="sm-header-title"></div>
|
|
637
|
+
<div class="sm-header-status">
|
|
638
|
+
<span class="sm-status-dot"></span>
|
|
639
|
+
<span class="sm-status-label"></span>
|
|
640
|
+
</div>
|
|
641
|
+
<div class="sm-header-subtitle"></div>
|
|
370
642
|
</div>
|
|
371
643
|
<div class="sm-header-actions">
|
|
372
|
-
<button class="sm-header-btn sm-minimize-btn" aria-label="Minimize">${
|
|
373
|
-
<button class="sm-header-btn sm-close-btn" aria-label="Close">${
|
|
644
|
+
<button class="sm-header-btn sm-minimize-btn" aria-label="Minimize">${$}</button>
|
|
645
|
+
<button class="sm-header-btn sm-close-btn" aria-label="Close">${L}</button>
|
|
374
646
|
</div>
|
|
375
647
|
</div>
|
|
648
|
+
<div class="sm-error" hidden></div>
|
|
376
649
|
<div class="sm-body"></div>
|
|
377
650
|
<div class="sm-footer">Powered by <a href="https://scalemule.com" target="_blank" rel="noopener">ScaleMule</a></div>
|
|
378
|
-
`,s.querySelector(".sm-minimize-btn").addEventListener("click",()=>this.minimize()),s.querySelector(".sm-close-btn").addEventListener("click",()=>this.minimize()),this.panelEl=s,this.shadow.appendChild(s)}
|
|
651
|
+
`,s.querySelector(".sm-minimize-btn").addEventListener("click",()=>this.minimize()),s.querySelector(".sm-close-btn").addEventListener("click",()=>this.minimize()),this.panelEl=s,this.bodyEl=s.querySelector(".sm-body"),this.errorBannerEl=s.querySelector(".sm-error"),this.shadow.appendChild(s),this.updateHeader()}updateHeader(){if(!this.panelEl)return;let s=this.panelEl.querySelector(".sm-header-title"),e=this.panelEl.querySelector(".sm-header-subtitle"),t=this.panelEl.querySelector(".sm-status-dot"),i=this.panelEl.querySelector(".sm-status-label");if(!s||!e||!t||!i)return;let n=this.client.visitorUserId,a=this.runtimeState.members.filter(h=>h.userId!==n).map(h=>h.status),o=this.config.reps_online,l=this.config.reps_online?"Online":"Away";a.length&&(a.some(h=>h==="online")?(o=!0,l="Online"):(a.some(h=>h==="away"||h==="dnd"),o=!1,l="Away")),s.textContent=this.config.title,e.textContent=o?this.config.subtitle:this.config.offline_message,i.textContent=o?l:"We'll respond soon",t.className=`sm-status-dot ${o?"sm-status-dot-online":"sm-status-dot-away"}`}renderError(s){this.activeErrorMessage=s,this.syncErrorBanner()}setFallbackNotice(s){this.fallbackNotice=s,this.syncErrorBanner()}syncErrorBanner(){if(!this.errorBannerEl)return;let s=this.activeErrorMessage??this.fallbackNotice;if(!s){this.errorBannerEl.hidden=!0,this.errorBannerEl.textContent="";return}this.errorBannerEl.hidden=!1,this.errorBannerEl.textContent=s}getConfiguredPreChatFields(){return this.config.pre_chat_fields?.length?this.config.pre_chat_fields.filter(s=>s.key!=="message"):m.pre_chat_fields}renderPreChatForm(){if(!this.bodyEl)return;let s=this.getConfiguredPreChatFields().map(e=>{let t=`sm-prechat-${e.key}`,i=e.required?"required":"",n=`${d(e.label)}${e.required?" *":""}`,r=`id="${t}" data-prechat-key="${d(e.key)}" placeholder="${d(e.label)}" ${i}`,a=e.type==="textarea"?`<textarea ${r}></textarea>`:`<input type="${d(e.type||"text")}" ${r} />`;return`
|
|
652
|
+
<div class="sm-field">
|
|
653
|
+
<label for="${t}">${n}</label>
|
|
654
|
+
${a}
|
|
655
|
+
</div>
|
|
656
|
+
`}).join("");this.bodyEl.dataset.view="prechat",this.bodyEl.innerHTML=`
|
|
379
657
|
<div class="sm-prechat">
|
|
380
658
|
<div class="sm-prechat-title">Start a conversation</div>
|
|
381
|
-
<div class="sm-prechat-desc"
|
|
382
|
-
|
|
383
|
-
<label for="sm-name">Name *</label>
|
|
384
|
-
<input type="text" id="sm-name" placeholder="Your name" required />
|
|
385
|
-
</div>
|
|
659
|
+
<div class="sm-prechat-desc">${d(this.config.reps_online?this.config.welcome_message:this.config.offline_message)}</div>
|
|
660
|
+
${s}
|
|
386
661
|
<div class="sm-field">
|
|
387
|
-
<label for="sm-
|
|
388
|
-
<
|
|
662
|
+
<label for="sm-prechat-message">Message *</label>
|
|
663
|
+
<textarea id="sm-prechat-message" placeholder="How can we help?"></textarea>
|
|
389
664
|
</div>
|
|
390
|
-
<div class="sm-
|
|
391
|
-
|
|
392
|
-
<
|
|
665
|
+
<div class="sm-upload-list" id="sm-prechat-uploads"></div>
|
|
666
|
+
<div class="sm-prechat-actions">
|
|
667
|
+
<input type="file" id="sm-prechat-file-input" hidden multiple accept="image/*,video/*,audio/*" />
|
|
668
|
+
<button class="sm-attach-btn sm-prechat-attach" type="button" aria-label="Attach files">${E}</button>
|
|
669
|
+
<button class="sm-submit-btn" id="sm-start-btn">Start Chat</button>
|
|
393
670
|
</div>
|
|
394
|
-
<button class="sm-submit-btn" id="sm-start-btn">Start Chat</button>
|
|
395
|
-
</div>
|
|
396
|
-
`,s.querySelector("#sm-start-btn").addEventListener("click",()=>this.handleStartChat())}renderChatView(){let s=this.panelEl.querySelector(".sm-body");s.innerHTML=`
|
|
397
|
-
<div class="sm-messages" id="sm-messages"></div>
|
|
398
|
-
<div class="sm-typing" id="sm-typing"></div>
|
|
399
|
-
<div class="sm-input-area">
|
|
400
|
-
<textarea class="sm-input" id="sm-input" placeholder="Type a message..." rows="1"></textarea>
|
|
401
|
-
<button class="sm-send-btn" id="sm-send-btn">${_}</button>
|
|
402
671
|
</div>
|
|
403
|
-
`,this.
|
|
672
|
+
`,this.uploadListEl=this.bodyEl.querySelector("#sm-prechat-uploads"),this.fileInputEl=this.bodyEl.querySelector("#sm-prechat-file-input"),this.bodyEl.querySelector("#sm-start-btn")?.addEventListener("click",()=>{this.handleStartChat()}),this.bodyEl.querySelector(".sm-prechat-attach")?.addEventListener("click",()=>{this.fileInputEl?.click()}),this.fileInputEl?.addEventListener("change",()=>{this.fileInputEl?.files&&(this.handleFilesSelected(Array.from(this.fileInputEl.files)),this.fileInputEl.value="")}),this.renderPendingAttachments()}ensureChatView(){this.bodyEl&&(this.bodyEl.dataset.view!=="chat"&&(this.bodyEl.dataset.view="chat",this.bodyEl.innerHTML=`
|
|
673
|
+
<div class="sm-chat-shell" id="sm-chat-shell">
|
|
674
|
+
<div class="sm-messages" id="sm-messages"></div>
|
|
675
|
+
<div class="sm-typing" id="sm-typing"></div>
|
|
676
|
+
<div class="sm-upload-list" id="sm-upload-list"></div>
|
|
677
|
+
<div class="sm-input-area">
|
|
678
|
+
<input type="file" id="sm-file-input" hidden multiple accept="image/*,video/*,audio/*" />
|
|
679
|
+
<button class="sm-attach-btn" id="sm-attach-btn" type="button" aria-label="Attach files">${E}</button>
|
|
680
|
+
<textarea class="sm-input" id="sm-input" placeholder="Type a message..." rows="1"></textarea>
|
|
681
|
+
<button class="sm-send-btn" id="sm-send-btn" type="button" aria-label="Send message">${U}</button>
|
|
682
|
+
</div>
|
|
683
|
+
</div>
|
|
684
|
+
`,this.chatShellEl=this.bodyEl.querySelector("#sm-chat-shell"),this.messagesEl=this.bodyEl.querySelector("#sm-messages"),this.typingEl=this.bodyEl.querySelector("#sm-typing"),this.uploadListEl=this.bodyEl.querySelector("#sm-upload-list"),this.inputEl=this.bodyEl.querySelector("#sm-input"),this.fileInputEl=this.bodyEl.querySelector("#sm-file-input"),this.bodyEl.querySelector("#sm-send-btn")?.addEventListener("click",()=>{this.handleSendMessage()}),this.bodyEl.querySelector("#sm-attach-btn")?.addEventListener("click",()=>{this.fileInputEl?.click()}),this.fileInputEl?.addEventListener("change",()=>{this.fileInputEl?.files&&(this.handleFilesSelected(Array.from(this.fileInputEl.files)),this.fileInputEl.value="")}),this.inputEl?.addEventListener("keydown",s=>{s.key==="Enter"&&!s.shiftKey&&(s.preventDefault(),this.handleSendMessage())}),this.inputEl?.addEventListener("input",()=>{this.inputEl&&(this.inputEl.style.height="auto",this.inputEl.style.height=`${Math.min(this.inputEl.scrollHeight,100)}px`,this.sendTypingPulse())}),this.messagesEl?.addEventListener("click",s=>{let e=s.target.closest("[data-action]");if(!e)return;let t=e.dataset.action,i=e.dataset.messageId;if(i){if(t==="toggle-picker"){this.openReactionMessageId=this.openReactionMessageId===i?null:i,this.renderMessages();return}if(t==="set-reaction"){let n=e.dataset.emoji;if(!n)return;this.openReactionMessageId=null,this.handleReaction(i,n,e.dataset.reacted==="true")}}}),this.messagesEl?.addEventListener("scroll",()=>{this.clearUnreadMarkerIfViewed()}),this.chatShellEl?.addEventListener("dragover",s=>{s.preventDefault(),this.chatShellEl?.classList.add("sm-dragging")}),this.chatShellEl?.addEventListener("dragleave",s=>{let e=s.relatedTarget;(!e||!this.chatShellEl?.contains(e))&&this.chatShellEl?.classList.remove("sm-dragging")}),this.chatShellEl?.addEventListener("drop",s=>{s.preventDefault(),this.chatShellEl?.classList.remove("sm-dragging"),this.handleFilesSelected(Array.from(s.dataTransfer?.files??[]))})),this.renderMessages(),this.renderTypingIndicator(),this.renderPendingAttachments())}renderTypingIndicator(){if(!this.typingEl)return;let s=this.client.visitorUserId,e=this.runtimeState.typingUsers.filter(t=>t!==s);this.typingEl.innerHTML=e.length?'<div class="sm-typing-indicator"><span></span><span></span><span></span><span>Agent is typing...</span></div>':""}renderPendingAttachments(){if(this.uploadListEl){if(!this.pendingAttachments.length){this.uploadListEl.innerHTML="";return}this.uploadListEl.innerHTML=this.pendingAttachments.map(s=>`
|
|
685
|
+
<div class="sm-upload-chip ${s.error?"sm-upload-chip-error":""}">
|
|
686
|
+
<span class="sm-upload-name">${d(s.fileName)}</span>
|
|
687
|
+
<span class="sm-upload-progress">${d(s.error??`${s.progress}%`)}</span>
|
|
688
|
+
<button class="sm-upload-remove" type="button" data-pending-id="${d(s.id)}" aria-label="Remove attachment">\xD7</button>
|
|
689
|
+
</div>
|
|
690
|
+
`).join(""),this.uploadListEl.querySelectorAll(".sm-upload-remove").forEach(s=>{s.addEventListener("click",()=>{let e=s.dataset.pendingId;e&&(this.pendingAttachments=this.pendingAttachments.filter(t=>t.id!==e),this.renderPendingAttachments())})})}}renderMessages(){if(!this.messagesEl)return;let s=this.client.visitorUserId,e=this.runtimeState.messages,t=!1;if(this.messagesEl.innerHTML=e.map((i,n)=>{let r=e[n-1],a=!r||!de(r.created_at,i.created_at),o=i.sender_id===s,l=i.sender_type==="system"||i.message_type==="system",h=(i.attachments??[]).map(u=>pe(i.id,u)).join(""),f=!t&&!o&&!l&&this.unreadMarkerTimestamp!==null&&new Date(i.created_at).getTime()>new Date(this.unreadMarkerTimestamp).getTime();f&&(t=!0);let p=(i.reactions??[]).map(u=>{let v=!!(s&&u.user_ids.includes(s));return`
|
|
691
|
+
<button
|
|
692
|
+
type="button"
|
|
693
|
+
class="sm-reaction-badge ${v?"sm-reaction-badge-active":""}"
|
|
694
|
+
data-action="set-reaction"
|
|
695
|
+
data-message-id="${d(i.id)}"
|
|
696
|
+
data-emoji="${d(u.emoji)}"
|
|
697
|
+
data-reacted="${v?"true":"false"}"
|
|
698
|
+
>
|
|
699
|
+
${d(u.emoji)} ${u.count}
|
|
700
|
+
</button>
|
|
701
|
+
`}).join(""),z=this.openReactionMessageId===i.id?`
|
|
702
|
+
<div class="sm-reaction-picker">
|
|
703
|
+
${oe.map(u=>`
|
|
704
|
+
<button
|
|
705
|
+
type="button"
|
|
706
|
+
class="sm-reaction-picker-btn"
|
|
707
|
+
data-action="set-reaction"
|
|
708
|
+
data-message-id="${d(i.id)}"
|
|
709
|
+
data-emoji="${d(u)}"
|
|
710
|
+
data-reacted="${i.reactions?.some(v=>v.emoji===u&&!!(s&&v.user_ids.includes(s)))?"true":"false"}"
|
|
711
|
+
>
|
|
712
|
+
${d(u)}
|
|
713
|
+
</button>
|
|
714
|
+
`).join("")}
|
|
715
|
+
</div>
|
|
716
|
+
`:"",q=a?`<div class="sm-date-divider">${d(ce(i.created_at))}</div>`:"",W=f?'<div class="sm-unread-divider" id="sm-unread-divider"><span>New messages</span></div>':"",j=!l&&!o?`<button type="button" class="sm-msg-action" data-action="toggle-picker" data-message-id="${d(i.id)}" aria-label="Add reaction">${O}</button>`:"";return`
|
|
717
|
+
${q}
|
|
718
|
+
${W}
|
|
719
|
+
<div class="sm-msg ${l?"sm-msg-system":o?"sm-msg-visitor":"sm-msg-rep"}">
|
|
720
|
+
<div class="sm-msg-row">
|
|
721
|
+
<div class="sm-msg-bubble">
|
|
722
|
+
${i.content?`<div class="sm-msg-content">${d(i.content)}</div>`:""}
|
|
723
|
+
${h}
|
|
724
|
+
</div>
|
|
725
|
+
${j}
|
|
726
|
+
</div>
|
|
727
|
+
${l?"":`<div class="sm-msg-time">${d(le(i.created_at))}${i.is_edited?" \xB7 edited":""}</div>`}
|
|
728
|
+
${p?`<div class="sm-reactions">${p}</div>`:""}
|
|
729
|
+
${z}
|
|
730
|
+
</div>
|
|
731
|
+
`}).join(""),this.shouldScrollToUnread){let i=this.messagesEl.querySelector("#sm-unread-divider");i?i.scrollIntoView({block:"center"}):this.messagesEl.scrollTop=this.messagesEl.scrollHeight,this.shouldScrollToUnread=!1}else this.messagesEl.scrollTop=this.messagesEl.scrollHeight}captureUnreadMarkerFromState(){let s=he(this.runtimeState.readStatuses,this.client.visitorUserId);if(!s){this.unreadMarkerTimestamp=null;return}let e=this.runtimeState.messages.some(t=>t.sender_id!==this.client.visitorUserId&&new Date(t.created_at).getTime()>new Date(s).getTime());this.unreadMarkerTimestamp=e?s:null,this.shouldScrollToUnread=e}clearUnreadMarkerIfViewed(){if(!this.unreadMarkerTimestamp||!this.messagesEl)return;let s=this.messagesEl.querySelector("#sm-unread-divider");s&&this.messagesEl.scrollTop>=s.offsetTop-24&&(this.unreadMarkerTimestamp=null,this.renderMessages())}async initializeVisitorSession(){this.client.isInitialized||await this.client.initVisitorSession()}async toggle(){if(this.isOpen){this.minimize();return}await this.ensureConfigLoaded(),this.renderPanel(),this.isOpen=!0,this.unreadCount=0,this.updateBadge(),this.panelEl?.classList.remove("sm-hidden");try{await this.initializeVisitorSession()}catch{this.renderError("Unable to initialize support chat")}let s=this.conversation??await this.client.getActiveConversation();s?(this.conversation=s,await this.ensureConversationRuntime(!0),this.renderChatView(),await this.markConversationRead()):(this.renderPreChatForm(),this.renderError(null))}minimize(){this.isOpen=!1,this.panelEl?.classList.add("sm-hidden")}async ensureConversationRuntime(s){if(this.conversation){if(this.renderError(null),this.config.realtime_enabled){await this.ensureRealtimeRuntime(s);return}this.realtimeFallbackActive=!1,this.cleanupRealtimeStatusWatchers(),this.setFallbackNotice(null),this.cleanupRealtimeRuntime(),await this.loadPollingSnapshot(s),this.startPolling()}}cleanupRealtimeRuntime(){this.controller&&(this.controller.destroy(),this.controller=null);for(let s of this.runtimeCleanups)s();this.runtimeCleanups=[]}cleanupRealtimeStatusWatchers(){for(let s of this.realtimeStatusCleanups)s();this.realtimeStatusCleanups=[]}ensureRealtimeStatusWatchers(){this.realtimeStatusCleanups.length||(this.realtimeStatusCleanups.push(this.client.chat.on("reconnecting",()=>{!this.config.realtime_enabled||!this.conversation||this.realtimeFallbackActive||this.setFallbackNotice("Realtime reconnecting\u2026")})),this.realtimeStatusCleanups.push(this.client.chat.on("disconnected",()=>{!this.config.realtime_enabled||!this.conversation||this.enterRealtimeFallback()})),this.realtimeStatusCleanups.push(this.client.chat.on("connected",()=>{if(!(!this.config.realtime_enabled||!this.conversation)){if(this.realtimeFallbackActive){this.restoreRealtimeRuntime();return}this.setFallbackNotice(null)}})))}async ensureRealtimeRuntime(s){if(this.conversation){if(this.ensureRealtimeStatusWatchers(),!this.controller||this.runtimeState.conversationId!==this.conversation.conversation_id){this.cleanupRealtimeRuntime(),this.stopPolling(),this.runtimeState=T(this.conversation.conversation_id),this.realtimeFallbackActive=!1,this.setFallbackNotice(null),this.controller=new b(this.client.chat,this.conversation.conversation_id),this.runtimeCleanups.push(this.controller.on("state",t=>{this.applyRuntimeState(t,!1)})),this.runtimeCleanups.push(this.controller.on("error",({message:t})=>{this.renderError(t)}));let e=await this.controller.init({realtime:!0,presence:!0});this.applyRuntimeState(e,s);return}s&&this.captureUnreadMarkerFromState()}}async enterRealtimeFallback(s="Realtime connection lost. Falling back to polling."){if(this.conversation){this.realtimeFallbackActive=!0,this.setFallbackNotice(s),this.cleanupRealtimeRuntime();try{await this.loadPollingSnapshot(!1),this.startPolling(),this.isOpen&&await this.markConversationRead()}catch(e){this.renderError(e instanceof Error?e.message:"Failed to refresh chat state")}}}async restoreRealtimeRuntime(){if(!(!this.conversation||!this.config.realtime_enabled))try{this.realtimeFallbackActive=!1,this.setFallbackNotice(null),await this.ensureRealtimeRuntime(!1)}catch{this.realtimeFallbackActive=!0,this.setFallbackNotice("Realtime reconnect failed. Staying on polling fallback."),this.startPolling()}}applyRuntimeState(s,e){let t=new Set(this.runtimeState.messages.map(n=>n.id)),i=s.messages.filter(n=>!t.has(n.id)&&n.sender_id!==this.client.visitorUserId&&n.sender_type!=="system").length;this.runtimeState=s,e&&this.captureUnreadMarkerFromState(),!this.isOpen&&i>0&&(this.unreadCount+=i,this.updateBadge()),this.updateHeader(),this.renderError(s.error),this.conversation&&this.panelEl&&this.bodyEl?.dataset.view==="chat"&&this.renderChatView(),this.isOpen&&i>0&&this.markConversationRead()}async loadPollingSnapshot(s){if(!this.conversation)return;let[e,t]=await Promise.all([this.client.chat.getMessages(this.conversation.conversation_id,{limit:50}),this.client.chat.getReadStatus(this.conversation.conversation_id)]),i={conversationId:this.conversation.conversation_id,messages:e.data?.messages??this.runtimeState.messages,readStatuses:t.data?.statuses??this.runtimeState.readStatuses,typingUsers:[],members:[],hasMore:e.data?.has_more??!1,isLoading:!1,error:e.error?.message??t.error?.message??null};this.applyRuntimeState(i,s)}startPolling(){this.pollInterval||!this.conversation||(this.pollInterval=setInterval(()=>{this.pollForUpdates()},3e4))}stopPolling(){this.pollInterval&&(clearInterval(this.pollInterval),this.pollInterval=null)}async pollForUpdates(){!this.conversation||this.config.realtime_enabled&&!this.realtimeFallbackActive||(await this.loadPollingSnapshot(!1),this.isOpen&&await this.markConversationRead())}getReadyAttachments(){return this.pendingAttachments.filter(s=>s.attachment).map(s=>s.attachment)}async handleFilesSelected(s){if(s.length){try{await this.initializeVisitorSession()}catch{this.renderError("Unable to prepare attachment upload");return}for(let e of s){let t=`${e.name}:${e.size}:${Date.now()}:${Math.random().toString(36).slice(2)}`;this.pendingAttachments=[...this.pendingAttachments,{id:t,fileName:e.name,progress:0}],this.renderPendingAttachments();let i=await this.client.chat.uploadAttachment(e,n=>{this.pendingAttachments=this.pendingAttachments.map(r=>r.id===t?{...r,progress:n}:r),this.renderPendingAttachments()});this.pendingAttachments=this.pendingAttachments.map(n=>n.id!==t?n:i.data?{...n,progress:100,attachment:i.data}:{...n,error:i.error?.message??"Upload failed"}),this.renderPendingAttachments()}}}collectPreChatValues(){let s={},e,t,i=!0;for(let n of this.getConfiguredPreChatFields()){let r=this.bodyEl?.querySelector(`[data-prechat-key="${n.key}"]`),a=r?.value.trim()??"";if(n.required&&!a){i=!1,r?.focus();break}a&&(n.key==="name"?e=a:n.key==="email"?t=a:s[n.key]=a)}return{name:e,email:t,metadata:s,valid:i}}async handleStartChat(){let s=this.bodyEl?.querySelector("#sm-start-btn"),t=this.bodyEl?.querySelector("#sm-prechat-message")?.value.trim()??"",i=this.getReadyAttachments(),{name:n,email:r,metadata:a,valid:o}=this.collectPreChatValues();if(!o||!t&&!i.length||!s){this.renderError("Please complete the required fields before starting the chat.");return}s.disabled=!0,s.textContent="Connecting...";try{await this.client.initVisitorSession({name:n,email:r}),this.conversation=await this.client.startConversation(t,{page_url:typeof location<"u"?location.href:void 0,attachments:i,metadata:a}),N("conversation_id",this.conversation.conversation_id),this.pendingAttachments=[],this.runtimeState={...T(this.conversation.conversation_id),messages:[B(this.client.visitorUserId??"visitor",t,i)]},this.unreadMarkerTimestamp=null,this.shouldScrollToUnread=!1,await this.ensureConversationRuntime(!1),this.renderChatView(),await this.markConversationRead()}catch(l){this.renderError(l instanceof Error?l.message:"Failed to start chat"),s.disabled=!1,s.textContent="Start Chat"}}async handleSendMessage(){if(!this.conversation)return;let s=this.inputEl?.value.trim()??"",e=this.getReadyAttachments();if(!s&&!e.length)return;this.inputEl.value="",this.inputEl.style.height="auto",this.pendingAttachments=[],this.renderPendingAttachments();let t=B(this.client.visitorUserId??"visitor",s,e);this.controller?this.controller.stageOptimisticMessage(t):(this.client.chat.stageOptimisticMessage(this.conversation.conversation_id,t),this.runtimeState={...this.runtimeState,messages:[...this.client.chat.getCachedMessages(this.conversation.conversation_id)]},this.renderChatView());try{if(this.controller)await this.controller.sendMessage(s,e);else{let i=await this.client.chat.sendMessage(this.conversation.conversation_id,{content:s,attachments:e,message_type:H(s,e)});if(i.error)throw new Error(i.error.message);await this.loadPollingSnapshot(!1)}await this.markConversationRead()}catch(i){this.renderError(i instanceof Error?i.message:"Failed to send message")}}async handleReaction(s,e,t){try{t?this.controller?await this.controller.removeReaction(s,e):(await this.client.chat.removeReaction(s,e),await this.loadPollingSnapshot(!1)):this.controller?await this.controller.addReaction(s,e):(await this.client.chat.addReaction(s,e),await this.loadPollingSnapshot(!1))}catch(i){this.renderError(i instanceof Error?i.message:"Failed to update reaction")}}sendTypingPulse(){!this.controller||!this.config.realtime_enabled||(this.controller.sendTyping(!0),this.typingStopTimer&&clearTimeout(this.typingStopTimer),this.typingStopTimer=setTimeout(()=>{this.controller?.sendTyping(!1)},2500))}async markConversationRead(){if(this.conversation){this.unreadCount=0,this.updateBadge();try{this.controller?(await this.controller.markRead(),await this.controller.refreshReadStatus()):await this.client.chat.markRead(this.conversation.conversation_id)}catch{}}}renderChatView(){this.ensureChatView(),this.updateHeader(),this.renderError(this.runtimeState.error)}updateBadge(){let s=this.shadow.querySelector(".sm-bubble");if(!s)return;let e=s.querySelector(".sm-badge");this.unreadCount>0?(e||(e=document.createElement("span"),e.className="sm-badge",s.appendChild(e)),e.textContent=String(this.unreadCount)):e&&e.remove()}destroy(){this.stopPolling(),this.typingStopTimer&&(clearTimeout(this.typingStopTimer),this.typingStopTimer=null),this.cleanupRealtimeStatusWatchers(),this.cleanupRealtimeRuntime(),this.client.destroy(),this.panelEl?.remove(),this.root.remove()}};(function(){let s=document.querySelectorAll("script[data-api-key]"),e=s[s.length-1];if(!e){console.warn("[ScaleMule] Support widget: missing data-api-key on script tag");return}let t=e.getAttribute("data-api-key");if(!t){console.warn("[ScaleMule] Support widget: data-api-key is empty");return}let i=e.getAttribute("data-api-url")||void 0,n=e.getAttribute("data-color")||void 0,r=e.getAttribute("data-position"),a=r==="left"||r==="right"?r:void 0,o=()=>{new k(t,i,{color:n,position:a})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",o):o()})();})();
|