@scalemule/chat 0.0.8 → 0.0.9
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-DQPHdUHX.d.cts +107 -0
- package/dist/ChatClient-DtUKF-4c.d.ts +107 -0
- 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.d.cts +2 -0
- package/dist/element.d.ts +2 -0
- package/dist/element.d.ts.map +1 -0
- 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 +17 -0
- package/dist/iframe.d.ts +15 -0
- package/dist/iframe.d.ts.map +1 -0
- package/dist/index.d.cts +166 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/react-components/ChatInput.d.ts +24 -0
- package/dist/react-components/ChatInput.d.ts.map +1 -0
- package/dist/react-components/ChatMessageItem.d.ts +24 -0
- package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
- package/dist/react-components/ChatMessageList.d.ts +31 -0
- package/dist/react-components/ChatMessageList.d.ts.map +1 -0
- package/dist/react-components/ChatThread.d.ts +19 -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 +15 -0
- package/dist/react-components/EmojiPicker.d.ts.map +1 -0
- package/dist/react-components/ReactionBar.d.ts +10 -0
- package/dist/react-components/ReactionBar.d.ts.map +1 -0
- package/dist/react-components/ReportDialog.d.ts +30 -0
- package/dist/react-components/ReportDialog.d.ts.map +1 -0
- package/dist/react-components/index.d.ts +10 -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 +1833 -459
- package/dist/react.d.cts +229 -0
- package/dist/react.d.ts +72 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1832 -461
- 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 +50 -25
- 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-COPVrm3K.d.cts +256 -0
- package/dist/types-COPVrm3K.d.ts +256 -0
- 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 +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { EventEmitter } from '../core/EventEmitter';
|
|
2
|
+
import type { ChatClient } from '../core/ChatClient';
|
|
3
|
+
import type { ChatMessage, ReadStatus, Attachment } from '../types';
|
|
4
|
+
interface PresenceStateMember {
|
|
5
|
+
userId: string;
|
|
6
|
+
status: string;
|
|
7
|
+
userData?: unknown;
|
|
8
|
+
}
|
|
9
|
+
interface ChatControllerInitOptions {
|
|
10
|
+
realtime?: boolean;
|
|
11
|
+
presence?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ChatControllerState {
|
|
14
|
+
conversationId: string;
|
|
15
|
+
messages: ChatMessage[];
|
|
16
|
+
readStatuses: ReadStatus[];
|
|
17
|
+
typingUsers: string[];
|
|
18
|
+
members: PresenceStateMember[];
|
|
19
|
+
hasMore: boolean;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
error: string | null;
|
|
22
|
+
}
|
|
23
|
+
interface ChatControllerEvents {
|
|
24
|
+
state: ChatControllerState;
|
|
25
|
+
ready: ChatControllerState;
|
|
26
|
+
error: {
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export declare class ChatController extends EventEmitter<ChatControllerEvents> {
|
|
31
|
+
private readonly client;
|
|
32
|
+
private readonly conversationId;
|
|
33
|
+
private readonly typingTimers;
|
|
34
|
+
private unsubscribers;
|
|
35
|
+
private state;
|
|
36
|
+
constructor(client: ChatClient, conversationId: string);
|
|
37
|
+
getState(): ChatControllerState;
|
|
38
|
+
init(options?: ChatControllerInitOptions): Promise<ChatControllerState>;
|
|
39
|
+
loadMore(): Promise<void>;
|
|
40
|
+
sendMessage(content: string, attachments?: Attachment[]): Promise<void>;
|
|
41
|
+
stageOptimisticMessage(message: ChatMessage): ChatMessage;
|
|
42
|
+
uploadAttachment(file: File | Blob, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<import("..").ApiResponse<Attachment>>;
|
|
43
|
+
refreshAttachmentUrl(messageId: string, fileId: string): Promise<import("..").ApiResponse<{
|
|
44
|
+
url: string;
|
|
45
|
+
}>>;
|
|
46
|
+
addReaction(messageId: string, emoji: string): Promise<void>;
|
|
47
|
+
removeReaction(messageId: string, emoji: string): Promise<void>;
|
|
48
|
+
reportMessage(messageId: string, reason: 'spam' | 'harassment' | 'hate' | 'violence' | 'other', description?: string): Promise<import("..").ApiResponse<{
|
|
49
|
+
reported: boolean;
|
|
50
|
+
}>>;
|
|
51
|
+
muteConversation(mutedUntil?: string): Promise<import("..").ApiResponse<{
|
|
52
|
+
muted: boolean;
|
|
53
|
+
}>>;
|
|
54
|
+
unmuteConversation(): Promise<import("..").ApiResponse<{
|
|
55
|
+
muted: boolean;
|
|
56
|
+
}>>;
|
|
57
|
+
markRead(): Promise<void>;
|
|
58
|
+
refreshReadStatus(): Promise<ReadStatus[]>;
|
|
59
|
+
sendTyping(isTyping?: boolean): void;
|
|
60
|
+
destroy(): void;
|
|
61
|
+
private bindEvents;
|
|
62
|
+
private patchState;
|
|
63
|
+
}
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=ChatController.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatController.d.ts","sourceRoot":"","sources":["../../src/shared/ChatController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEpE,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,yBAAyB;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,UAAU,oBAAoB;IAC5B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5B;AAED,qBAAa,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IACpE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoD;IACjF,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,KAAK,CAAsB;gBAEvB,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM;IAgBtD,QAAQ,IAAI,mBAAmB;IAIzB,IAAI,CAAC,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA8C3E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAezB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,GAAE,UAAU,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBjF,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW;IAQnD,gBAAgB,CACpB,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACtC,MAAM,CAAC,EAAE,WAAW;IAKhB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;IAItD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5D,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/D,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,EAC7D,WAAW,CAAC,EAAE,MAAM;;;IAKhB,gBAAgB,CAAC,UAAU,CAAC,EAAE,MAAM;;;IAIpC,kBAAkB;;;IAIlB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,iBAAiB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAchD,UAAU,CAAC,QAAQ,UAAO,GAAG,IAAI;IAIjC,OAAO,IAAI,IAAI;IAaf,OAAO,CAAC,UAAU;IAwIlB,OAAO,CAAC,UAAU;CAQnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/shared/upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAkK5C,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,EACV,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACtC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAkC5B"}
|
|
@@ -1,18 +1,26 @@
|
|
|
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=`
|
|
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=u=>{r||(r=!0,a&&(clearTimeout(a),a=null),i(u))},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",u=>{f(),o=u.loaded,l=u.total||l,u.lengthComputable&&e?.(Math.round(u.loaded/u.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
5
|
--sm-primary-hover: #1d4ed8;
|
|
6
6
|
--sm-primary-disabled: #93c5fd;
|
|
7
7
|
--sm-primary-text: #ffffff;
|
|
8
|
+
--sm-visitor-bubble: var(--sm-primary);
|
|
9
|
+
--sm-visitor-text: var(--sm-primary-text);
|
|
10
|
+
--sm-rep-bubble: #f3f4f6;
|
|
11
|
+
--sm-rep-text: #1f2937;
|
|
12
|
+
--sm-bubble-radius: 16px;
|
|
13
|
+
--sm-panel-width: 380px;
|
|
14
|
+
--sm-panel-height: 560px;
|
|
8
15
|
--sm-badge-bg: #ef4444;
|
|
9
16
|
--sm-bubble-left: auto;
|
|
10
17
|
--sm-bubble-right: 20px;
|
|
11
18
|
--sm-panel-left: auto;
|
|
12
19
|
--sm-panel-right: 20px;
|
|
13
|
-
--sm-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
20
|
+
--sm-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
21
|
+
--sm-font-size: 14px;
|
|
14
22
|
font-family: var(--sm-font-family);
|
|
15
|
-
font-size:
|
|
23
|
+
font-size: var(--sm-font-size);
|
|
16
24
|
line-height: 1.5;
|
|
17
25
|
color: #111827;
|
|
18
26
|
}
|
|
@@ -70,8 +78,8 @@
|
|
|
70
78
|
bottom: 90px;
|
|
71
79
|
left: var(--sm-panel-left);
|
|
72
80
|
right: var(--sm-panel-right);
|
|
73
|
-
width:
|
|
74
|
-
max-height: min(
|
|
81
|
+
width: var(--sm-panel-width);
|
|
82
|
+
max-height: min(var(--sm-panel-height), calc(100vh - 110px));
|
|
75
83
|
background: #ffffff;
|
|
76
84
|
border-radius: 18px;
|
|
77
85
|
box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);
|
|
@@ -276,6 +284,7 @@
|
|
|
276
284
|
display: flex;
|
|
277
285
|
flex-direction: column;
|
|
278
286
|
background: #f8fafc;
|
|
287
|
+
position: relative;
|
|
279
288
|
}
|
|
280
289
|
|
|
281
290
|
.sm-chat-shell.sm-dragging {
|
|
@@ -349,11 +358,11 @@
|
|
|
349
358
|
|
|
350
359
|
.sm-msg-bubble {
|
|
351
360
|
padding: 10px 12px;
|
|
352
|
-
border-radius:
|
|
361
|
+
border-radius: var(--sm-bubble-radius);
|
|
353
362
|
line-height: 1.4;
|
|
354
363
|
word-break: break-word;
|
|
355
|
-
background:
|
|
356
|
-
color:
|
|
364
|
+
background: var(--sm-rep-bubble);
|
|
365
|
+
color: var(--sm-rep-text);
|
|
357
366
|
}
|
|
358
367
|
|
|
359
368
|
.sm-msg-content {
|
|
@@ -361,8 +370,8 @@
|
|
|
361
370
|
}
|
|
362
371
|
|
|
363
372
|
.sm-msg-visitor .sm-msg-bubble {
|
|
364
|
-
background: var(--sm-
|
|
365
|
-
color: var(--sm-
|
|
373
|
+
background: var(--sm-visitor-bubble);
|
|
374
|
+
color: var(--sm-visitor-text);
|
|
366
375
|
border-bottom-right-radius: 6px;
|
|
367
376
|
}
|
|
368
377
|
|
|
@@ -620,6 +629,22 @@
|
|
|
620
629
|
text-decoration: underline;
|
|
621
630
|
}
|
|
622
631
|
|
|
632
|
+
.sm-drag-overlay {
|
|
633
|
+
position: absolute;
|
|
634
|
+
inset: 0;
|
|
635
|
+
background: rgba(37, 99, 235, 0.08);
|
|
636
|
+
border: 2px dashed var(--sm-primary);
|
|
637
|
+
border-radius: 12px;
|
|
638
|
+
display: flex;
|
|
639
|
+
align-items: center;
|
|
640
|
+
justify-content: center;
|
|
641
|
+
font-size: 13px;
|
|
642
|
+
font-weight: 600;
|
|
643
|
+
color: var(--sm-primary);
|
|
644
|
+
pointer-events: none;
|
|
645
|
+
z-index: 10;
|
|
646
|
+
}
|
|
647
|
+
|
|
623
648
|
@media (max-width: 440px) {
|
|
624
649
|
.sm-panel {
|
|
625
650
|
left: 0;
|
|
@@ -630,7 +655,7 @@
|
|
|
630
655
|
border-radius: 18px 18px 0 0;
|
|
631
656
|
}
|
|
632
657
|
}
|
|
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}"],
|
|
658
|
+
`;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}"],le={"image/":10*1024*1024,"video/":25*1024*1024,"audio/":5*1024*1024};function ce(c){for(let[s,e]of Object.entries(le))if(c.type.startsWith(s)&&c.size>e){let t=e/1048576;return`${c.name} exceeds ${t}MB limit`}return null}var p={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 de(c){try{return new Date(c).toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit"})}catch{return""}}function he(c){try{return new Date(c).toLocaleDateString(void 0,{month:"short",day:"numeric",year:"numeric"})}catch{return""}}function pe(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 B(c,s){return s.length?!c&&s.every(e=>e.mime_type.startsWith("image/"))?"image":"file":"text"}function ue(c,s){return s?c.find(e=>e.user_id===s)?.last_read_at??null:null}function z(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:B(s,e),attachments:e,reactions:[],is_edited:!1,created_at:new Date().toISOString()}}function me(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=p;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??p.primary_color,e=this.options.position??this.config.position??p.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={...p,...s,title:this.options.title??s.title??p.title,primary_color:this.options.color??s.primary_color??p.primary_color,position:this.options.position??s.position??p.position}}catch{this.config={...p,title:this.options.title??p.title,primary_color:this.options.color??p.primary_color,position:this.options.position??p.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=`
|
|
634
659
|
<div class="sm-header">
|
|
635
660
|
<div class="sm-header-copy">
|
|
636
661
|
<div class="sm-header-title"></div>
|
|
@@ -648,7 +673,7 @@
|
|
|
648
673
|
<div class="sm-error" hidden></div>
|
|
649
674
|
<div class="sm-body"></div>
|
|
650
675
|
<div class="sm-footer">Powered by <a href="https://scalemule.com" target="_blank" rel="noopener">ScaleMule</a></div>
|
|
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"):
|
|
676
|
+
`,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"):p.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
677
|
<div class="sm-field">
|
|
653
678
|
<label for="${t}">${n}</label>
|
|
654
679
|
${a}
|
|
@@ -687,33 +712,33 @@
|
|
|
687
712
|
<span class="sm-upload-progress">${d(s.error??`${s.progress}%`)}</span>
|
|
688
713
|
<button class="sm-upload-remove" type="button" data-pending-id="${d(s.id)}" aria-label="Remove attachment">\xD7</button>
|
|
689
714
|
</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||!
|
|
715
|
+
`).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||!pe(r.created_at,i.created_at),o=i.sender_id===s,l=i.sender_type==="system"||i.message_type==="system",h=(i.attachments??[]).map(m=>me(i.id,m)).join(""),f=!t&&!o&&!l&&this.unreadMarkerTimestamp!==null&&new Date(i.created_at).getTime()>new Date(this.unreadMarkerTimestamp).getTime();f&&(t=!0);let u=(i.reactions??[]).map(m=>{let v=!!(s&&m.user_ids.includes(s));return`
|
|
691
716
|
<button
|
|
692
717
|
type="button"
|
|
693
718
|
class="sm-reaction-badge ${v?"sm-reaction-badge-active":""}"
|
|
694
719
|
data-action="set-reaction"
|
|
695
720
|
data-message-id="${d(i.id)}"
|
|
696
|
-
data-emoji="${d(
|
|
721
|
+
data-emoji="${d(m.emoji)}"
|
|
697
722
|
data-reacted="${v?"true":"false"}"
|
|
698
723
|
>
|
|
699
|
-
${d(
|
|
724
|
+
${d(m.emoji)} ${m.count}
|
|
700
725
|
</button>
|
|
701
|
-
`}).join(""),
|
|
726
|
+
`}).join(""),H=this.openReactionMessageId===i.id?`
|
|
702
727
|
<div class="sm-reaction-picker">
|
|
703
|
-
${oe.map(
|
|
728
|
+
${oe.map(m=>`
|
|
704
729
|
<button
|
|
705
730
|
type="button"
|
|
706
731
|
class="sm-reaction-picker-btn"
|
|
707
732
|
data-action="set-reaction"
|
|
708
733
|
data-message-id="${d(i.id)}"
|
|
709
|
-
data-emoji="${d(
|
|
710
|
-
data-reacted="${i.reactions?.some(v=>v.emoji===
|
|
734
|
+
data-emoji="${d(m)}"
|
|
735
|
+
data-reacted="${i.reactions?.some(v=>v.emoji===m&&!!(s&&v.user_ids.includes(s)))?"true":"false"}"
|
|
711
736
|
>
|
|
712
|
-
${d(
|
|
737
|
+
${d(m)}
|
|
713
738
|
</button>
|
|
714
739
|
`).join("")}
|
|
715
740
|
</div>
|
|
716
|
-
`:"",q=a?`<div class="sm-date-divider">${d(
|
|
741
|
+
`:"",q=a?`<div class="sm-date-divider">${d(he(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
742
|
${q}
|
|
718
743
|
${W}
|
|
719
744
|
<div class="sm-msg ${l?"sm-msg-system":o?"sm-msg-visitor":"sm-msg-rep"}">
|
|
@@ -724,8 +749,8 @@
|
|
|
724
749
|
</div>
|
|
725
750
|
${j}
|
|
726
751
|
</div>
|
|
727
|
-
${l?"":`<div class="sm-msg-time">${d(
|
|
728
|
-
${
|
|
729
|
-
${
|
|
752
|
+
${l?"":`<div class="sm-msg-time">${d(de(i.created_at))}${i.is_edited?" \xB7 edited":""}</div>`}
|
|
753
|
+
${u?`<div class="sm-reactions">${u}</div>`:""}
|
|
754
|
+
${H}
|
|
730
755
|
</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()})();})();
|
|
756
|
+
`}).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=ue(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=ce(e);if(t){this.renderError(t);continue}let i=`${e.name}:${e.size}:${Date.now()}:${Math.random().toString(36).slice(2)}`;this.pendingAttachments=[...this.pendingAttachments,{id:i,fileName:e.name,progress:0}],this.renderPendingAttachments();let n=await this.client.chat.uploadAttachment(e,r=>{this.pendingAttachments=this.pendingAttachments.map(a=>a.id===i?{...a,progress:r}:a),this.renderPendingAttachments()});this.pendingAttachments=this.pendingAttachments.map(r=>r.id!==i?r:n.data?{...r,progress:100,attachment:n.data}:{...r,error:n.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:[z(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=z(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:B(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)},2e3))}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()})();})();
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SupportClient — High-level client for the support chat widget.
|
|
3
|
+
*
|
|
4
|
+
* Handles visitor session lifecycle (create/restore/refresh) and exposes
|
|
5
|
+
* the underlying ChatClient for real-time messaging.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* const support = new SupportClient({ apiKey: 'pb_...', apiBaseUrl: 'https://api.scalemule.com' });
|
|
10
|
+
* await support.initVisitorSession({ name: 'Jane' });
|
|
11
|
+
* const conversation = await support.startConversation('Hi, I need help!', { page_url: location.href });
|
|
12
|
+
* support.chat.on('message', (msg) => console.log(msg));
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { ChatClient } from './core/ChatClient';
|
|
16
|
+
import type { Attachment } from './types';
|
|
17
|
+
export interface SupportConversation {
|
|
18
|
+
id: string;
|
|
19
|
+
conversation_id: string;
|
|
20
|
+
status: string;
|
|
21
|
+
visitor_name?: string;
|
|
22
|
+
visitor_email?: string;
|
|
23
|
+
visitor_page_url?: string;
|
|
24
|
+
assigned_rep_id?: string;
|
|
25
|
+
assigned_rep_name?: string;
|
|
26
|
+
last_message_preview?: string;
|
|
27
|
+
last_message_at?: string;
|
|
28
|
+
created_at: string;
|
|
29
|
+
existing?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface SupportWidgetPreChatField {
|
|
32
|
+
key: string;
|
|
33
|
+
label: string;
|
|
34
|
+
type: string;
|
|
35
|
+
required: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface SupportWidgetConfig {
|
|
38
|
+
title: string;
|
|
39
|
+
subtitle: string;
|
|
40
|
+
primary_color: string;
|
|
41
|
+
position: 'left' | 'right';
|
|
42
|
+
pre_chat_fields: SupportWidgetPreChatField[];
|
|
43
|
+
business_hours: Record<string, unknown>;
|
|
44
|
+
realtime_enabled: boolean;
|
|
45
|
+
welcome_message: string;
|
|
46
|
+
offline_message: string;
|
|
47
|
+
reps_online: boolean;
|
|
48
|
+
online_count: number;
|
|
49
|
+
}
|
|
50
|
+
export interface SupportClientConfig {
|
|
51
|
+
apiKey: string;
|
|
52
|
+
apiBaseUrl?: string;
|
|
53
|
+
wsUrl?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare class SupportClient {
|
|
56
|
+
private chatClient;
|
|
57
|
+
private apiKey;
|
|
58
|
+
private apiBaseUrl;
|
|
59
|
+
private wsUrl?;
|
|
60
|
+
private storageKey;
|
|
61
|
+
private anonymousId;
|
|
62
|
+
private refreshToken;
|
|
63
|
+
private accessToken;
|
|
64
|
+
private tokenExpiresAt;
|
|
65
|
+
private userId;
|
|
66
|
+
private visitorName;
|
|
67
|
+
private visitorEmail;
|
|
68
|
+
constructor(config: SupportClientConfig);
|
|
69
|
+
/** Create or restore a visitor session. Call before startConversation(). */
|
|
70
|
+
initVisitorSession(info?: {
|
|
71
|
+
name?: string;
|
|
72
|
+
email?: string;
|
|
73
|
+
}): Promise<void>;
|
|
74
|
+
/** Start a new support conversation with the first message. */
|
|
75
|
+
startConversation(message: string, meta?: {
|
|
76
|
+
page_url?: string;
|
|
77
|
+
attachments?: Attachment[];
|
|
78
|
+
metadata?: Record<string, unknown>;
|
|
79
|
+
}): Promise<SupportConversation>;
|
|
80
|
+
/** Get the visitor's active/waiting support conversation, if any. */
|
|
81
|
+
getActiveConversation(): Promise<SupportConversation | null>;
|
|
82
|
+
/** Fetch widget configuration and live support availability. */
|
|
83
|
+
getWidgetConfig(): Promise<SupportWidgetConfig>;
|
|
84
|
+
/** Get the underlying ChatClient for messaging, events, typing indicators, etc. */
|
|
85
|
+
get chat(): ChatClient;
|
|
86
|
+
/** Whether a visitor session has been initialized. */
|
|
87
|
+
get isInitialized(): boolean;
|
|
88
|
+
connect(): void;
|
|
89
|
+
disconnect(): void;
|
|
90
|
+
/** The visitor's user ID (available after initVisitorSession). */
|
|
91
|
+
get visitorUserId(): string | null;
|
|
92
|
+
/** Clean up all resources. */
|
|
93
|
+
destroy(): void;
|
|
94
|
+
private initChatClient;
|
|
95
|
+
private refreshAccessToken;
|
|
96
|
+
private loadState;
|
|
97
|
+
private saveState;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=support.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"support.d.ts","sourceRoot":"","sources":["../src/support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,SAAS,CAAC;AAItD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,eAAe,EAAE,yBAAyB,EAAE,CAAC;IAC7C,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAQD,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,YAAY,CAAqB;gBAE7B,MAAM,EAAE,mBAAmB;IAkBvC,4EAA4E;IACtE,kBAAkB,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgDjF,+DAA+D;IACzD,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;QAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GACA,OAAO,CAAC,mBAAmB,CAAC;IAuC/B,qEAAqE;IAC/D,qBAAqB,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAuBlE,gEAAgE;IAC1D,eAAe,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAgBrD,mFAAmF;IACnF,IAAI,IAAI,IAAI,UAAU,CAKrB;IAED,sDAAsD;IACtD,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,OAAO,IAAI,IAAI;IAIf,UAAU,IAAI,IAAI;IAIlB,kEAAkE;IAClE,IAAI,aAAa,IAAI,MAAM,GAAG,IAAI,CAEjC;IAED,8BAA8B;IAC9B,OAAO,IAAI,IAAI;IAOf,OAAO,CAAC,cAAc;YA6BR,kBAAkB;IAsBhC,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,SAAS;CAclB"}
|