@scalemule/chat 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ChatClient-COmdEJ11.d.ts → ChatClient-DQPHdUHX.d.cts} +21 -2
- package/dist/{ChatClient-BoZaTtyM.d.cts → ChatClient-DtUKF-4c.d.ts} +21 -2
- package/dist/chat.embed.global.js +1 -1
- package/dist/chat.umd.global.js +288 -12
- package/dist/{chunk-ZLMMNFZL.js → chunk-5O5YLRJL.js} +386 -16
- package/dist/chunk-GTMAK3IA.js +285 -0
- package/dist/chunk-TRCELAZQ.cjs +287 -0
- package/dist/{chunk-YDLRISR7.cjs → chunk-W2PWFS3E.cjs} +386 -15
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core/ChatClient.d.ts +96 -0
- package/dist/core/ChatClient.d.ts.map +1 -0
- package/dist/core/EventEmitter.d.ts +11 -0
- package/dist/core/EventEmitter.d.ts.map +1 -0
- package/dist/core/MessageCache.d.ts +18 -0
- package/dist/core/MessageCache.d.ts.map +1 -0
- package/dist/core/OfflineQueue.d.ts +19 -0
- package/dist/core/OfflineQueue.d.ts.map +1 -0
- package/dist/element.cjs +542 -51
- package/dist/element.d.ts +2 -2
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +541 -50
- package/dist/embed/index.d.ts +2 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/iframe.d.cts +1 -1
- package/dist/iframe.d.ts +3 -5
- package/dist/iframe.d.ts.map +1 -0
- package/dist/index.cjs +34 -5
- package/dist/index.d.cts +93 -4
- package/dist/index.d.ts +8 -77
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -4
- package/dist/react-components/ChatInput.d.ts +16 -0
- package/dist/react-components/ChatInput.d.ts.map +1 -0
- package/dist/react-components/ChatMessageItem.d.ts +13 -0
- package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
- package/dist/react-components/ChatMessageList.d.ts +15 -0
- package/dist/react-components/ChatMessageList.d.ts.map +1 -0
- package/dist/react-components/ChatThread.d.ts +12 -0
- package/dist/react-components/ChatThread.d.ts.map +1 -0
- package/dist/react-components/ConversationList.d.ts +13 -0
- package/dist/react-components/ConversationList.d.ts.map +1 -0
- package/dist/react-components/EmojiPicker.d.ts +8 -0
- package/dist/react-components/EmojiPicker.d.ts.map +1 -0
- package/dist/react-components/index.d.ts +8 -0
- package/dist/react-components/index.d.ts.map +1 -0
- package/dist/react-components/theme.d.ts +19 -0
- package/dist/react-components/theme.d.ts.map +1 -0
- package/dist/react-components/utils.d.ts +4 -0
- package/dist/react-components/utils.d.ts.map +1 -0
- package/dist/react.cjs +1212 -50
- package/dist/react.d.cts +100 -4
- package/dist/react.d.ts +38 -15
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1164 -13
- package/dist/shared/ChatController.d.ts +65 -0
- package/dist/shared/ChatController.d.ts.map +1 -0
- package/dist/shared/upload.d.ts +3 -0
- package/dist/shared/upload.d.ts.map +1 -0
- package/dist/support-widget.global.js +485 -157
- package/dist/support.d.ts +99 -0
- package/dist/support.d.ts.map +1 -0
- package/dist/transport/HttpTransport.d.ts +20 -0
- package/dist/transport/HttpTransport.d.ts.map +1 -0
- package/dist/transport/WebSocketTransport.d.ts +78 -0
- package/dist/transport/WebSocketTransport.d.ts.map +1 -0
- package/dist/{types-BmD7f1gV.d.cts → types-COPVrm3K.d.cts} +25 -1
- package/dist/{types-BmD7f1gV.d.ts → types-COPVrm3K.d.ts} +25 -1
- package/dist/types.d.ts +271 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/umd.d.ts +6 -0
- package/dist/umd.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/widget/icons.d.ts +8 -0
- package/dist/widget/icons.d.ts.map +1 -0
- package/dist/widget/index.d.ts +2 -0
- package/dist/widget/index.d.ts.map +1 -0
- package/dist/widget/storage.d.ts +5 -0
- package/dist/widget/storage.d.ts.map +1 -0
- package/dist/widget/styles.d.ts +3 -0
- package/dist/widget/styles.d.ts.map +1 -0
- package/package.json +5 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as ChatEventMap, C as ChatConfig, b as ConnectionStatus, c as CreateConversationOptions, A as ApiResponse, d as Conversation, L as ListConversationsOptions, S as SendMessageOptions, e as ChatMessage, G as GetMessagesOptions, M as MessagesResponse, f as Attachment, U as UnreadTotalResponse, R as ReadStatus, g as ChannelSettings, h as ChannelWithSettings, i as CreateEphemeralChannelOptions, j as CreateLargeRoomOptions } from './types-COPVrm3K.cjs';
|
|
2
2
|
|
|
3
3
|
type Listener<T> = (data: T) => void;
|
|
4
4
|
declare class EventEmitter<EventMap extends Record<string, any>> {
|
|
@@ -17,8 +17,10 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
17
17
|
private offlineQueue;
|
|
18
18
|
private conversationSubs;
|
|
19
19
|
private conversationTypes;
|
|
20
|
+
private currentUserId?;
|
|
20
21
|
constructor(config: ChatConfig);
|
|
21
22
|
get status(): ConnectionStatus;
|
|
23
|
+
get userId(): string | undefined;
|
|
22
24
|
connect(): void;
|
|
23
25
|
disconnect(): void;
|
|
24
26
|
createConversation(options: CreateConversationOptions): Promise<ApiResponse<Conversation>>;
|
|
@@ -29,9 +31,23 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
29
31
|
getMessages(conversationId: string, options?: GetMessagesOptions): Promise<ApiResponse<MessagesResponse>>;
|
|
30
32
|
editMessage(messageId: string, content: string): Promise<ApiResponse<void>>;
|
|
31
33
|
deleteMessage(messageId: string): Promise<ApiResponse<void>>;
|
|
34
|
+
uploadAttachment(file: File | Blob, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<ApiResponse<Attachment>>;
|
|
35
|
+
refreshAttachmentUrl(messageId: string, fileId: string): Promise<ApiResponse<{
|
|
36
|
+
url: string;
|
|
37
|
+
}>>;
|
|
32
38
|
getCachedMessages(conversationId: string): ChatMessage[];
|
|
39
|
+
stageOptimisticMessage(conversationId: string, message: ChatMessage): ChatMessage;
|
|
33
40
|
addReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
|
|
34
41
|
removeReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
|
|
42
|
+
reportMessage(messageId: string, reason: 'spam' | 'harassment' | 'hate' | 'violence' | 'other', description?: string): Promise<ApiResponse<{
|
|
43
|
+
reported: boolean;
|
|
44
|
+
}>>;
|
|
45
|
+
muteConversation(conversationId: string, mutedUntil?: string): Promise<ApiResponse<{
|
|
46
|
+
muted: boolean;
|
|
47
|
+
}>>;
|
|
48
|
+
unmuteConversation(conversationId: string): Promise<ApiResponse<{
|
|
49
|
+
muted: boolean;
|
|
50
|
+
}>>;
|
|
35
51
|
getUnreadTotal(): Promise<ApiResponse<UnreadTotalResponse>>;
|
|
36
52
|
sendTyping(conversationId: string, isTyping?: boolean): Promise<void>;
|
|
37
53
|
markRead(conversationId: string): Promise<void>;
|
|
@@ -81,8 +97,11 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
81
97
|
destroy(): void;
|
|
82
98
|
private handleRealtimeMessage;
|
|
83
99
|
private handlePrivateMessage;
|
|
100
|
+
private normalizeMessage;
|
|
101
|
+
private buildEditedMessage;
|
|
102
|
+
private applyReactionEvent;
|
|
84
103
|
private handleConversationMessage;
|
|
85
104
|
private flushOfflineQueue;
|
|
86
105
|
}
|
|
87
106
|
|
|
88
|
-
export { ChatClient as C };
|
|
107
|
+
export { ChatClient as C, EventEmitter as E };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as ChatEventMap, C as ChatConfig, b as ConnectionStatus, c as CreateConversationOptions, A as ApiResponse, d as Conversation, L as ListConversationsOptions, S as SendMessageOptions, e as ChatMessage, G as GetMessagesOptions, M as MessagesResponse, f as Attachment, U as UnreadTotalResponse, R as ReadStatus, g as ChannelSettings, h as ChannelWithSettings, i as CreateEphemeralChannelOptions, j as CreateLargeRoomOptions } from './types-COPVrm3K.js';
|
|
2
2
|
|
|
3
3
|
type Listener<T> = (data: T) => void;
|
|
4
4
|
declare class EventEmitter<EventMap extends Record<string, any>> {
|
|
@@ -17,8 +17,10 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
17
17
|
private offlineQueue;
|
|
18
18
|
private conversationSubs;
|
|
19
19
|
private conversationTypes;
|
|
20
|
+
private currentUserId?;
|
|
20
21
|
constructor(config: ChatConfig);
|
|
21
22
|
get status(): ConnectionStatus;
|
|
23
|
+
get userId(): string | undefined;
|
|
22
24
|
connect(): void;
|
|
23
25
|
disconnect(): void;
|
|
24
26
|
createConversation(options: CreateConversationOptions): Promise<ApiResponse<Conversation>>;
|
|
@@ -29,9 +31,23 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
29
31
|
getMessages(conversationId: string, options?: GetMessagesOptions): Promise<ApiResponse<MessagesResponse>>;
|
|
30
32
|
editMessage(messageId: string, content: string): Promise<ApiResponse<void>>;
|
|
31
33
|
deleteMessage(messageId: string): Promise<ApiResponse<void>>;
|
|
34
|
+
uploadAttachment(file: File | Blob, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<ApiResponse<Attachment>>;
|
|
35
|
+
refreshAttachmentUrl(messageId: string, fileId: string): Promise<ApiResponse<{
|
|
36
|
+
url: string;
|
|
37
|
+
}>>;
|
|
32
38
|
getCachedMessages(conversationId: string): ChatMessage[];
|
|
39
|
+
stageOptimisticMessage(conversationId: string, message: ChatMessage): ChatMessage;
|
|
33
40
|
addReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
|
|
34
41
|
removeReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
|
|
42
|
+
reportMessage(messageId: string, reason: 'spam' | 'harassment' | 'hate' | 'violence' | 'other', description?: string): Promise<ApiResponse<{
|
|
43
|
+
reported: boolean;
|
|
44
|
+
}>>;
|
|
45
|
+
muteConversation(conversationId: string, mutedUntil?: string): Promise<ApiResponse<{
|
|
46
|
+
muted: boolean;
|
|
47
|
+
}>>;
|
|
48
|
+
unmuteConversation(conversationId: string): Promise<ApiResponse<{
|
|
49
|
+
muted: boolean;
|
|
50
|
+
}>>;
|
|
35
51
|
getUnreadTotal(): Promise<ApiResponse<UnreadTotalResponse>>;
|
|
36
52
|
sendTyping(conversationId: string, isTyping?: boolean): Promise<void>;
|
|
37
53
|
markRead(conversationId: string): Promise<void>;
|
|
@@ -81,8 +97,11 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
81
97
|
destroy(): void;
|
|
82
98
|
private handleRealtimeMessage;
|
|
83
99
|
private handlePrivateMessage;
|
|
100
|
+
private normalizeMessage;
|
|
101
|
+
private buildEditedMessage;
|
|
102
|
+
private applyReactionEvent;
|
|
84
103
|
private handleConversationMessage;
|
|
85
104
|
private flushOfflineQueue;
|
|
86
105
|
}
|
|
87
106
|
|
|
88
|
-
export { ChatClient as C };
|
|
107
|
+
export { ChatClient as C, EventEmitter as E };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ChatEmbed=(()=>{var p=class{constructor(){this.listeners=new Map}on(n,e){return this.listeners.has(n)||this.listeners.set(n,new Set),this.listeners.get(n).add(e),()=>this.off(n,e)}off(n,e){this.listeners.get(n)?.delete(e)}once(n,e){let t=(s=>{this.off(n,t),e(s)});return this.on(n,t)}emit(n,...[e]){let t=this.listeners.get(n);if(t)for(let s of t)try{s(e)}catch(i){console.error(`[ScaleMuleChat] Error in ${String(n)} listener:`,i)}}removeAllListeners(n){n?this.listeners.delete(n):this.listeners.clear()}};var v="https://api.scalemule.com";var d=class{constructor(n,e){this.cache=new Map;this.maxMessages=n??200,this.maxConversations=e??50}getMessages(n){return this.cache.get(n)??[]}setMessages(n,e){this.cache.set(n,e.slice(0,this.maxMessages)),this.evictOldConversations()}addMessage(n,e){let t=this.cache.get(n)??[];t.some(s=>s.id===e.id)||(t.push(e),t.sort((s,i)=>s.created_at.localeCompare(i.created_at)),t.length>this.maxMessages&&t.splice(0,t.length-this.maxMessages),this.cache.set(n,t))}updateMessage(n,e){let t=this.cache.get(n);if(!t)return;let s=t.findIndex(i=>i.id===e.id);s>=0&&(t[s]=e)}removeMessage(n,e){let t=this.cache.get(n);if(!t)return;let s=t.findIndex(i=>i.id===e);s>=0&&t.splice(s,1)}clear(n){n?this.cache.delete(n):this.cache.clear()}evictOldConversations(){for(;this.cache.size>this.maxConversations;){let n=this.cache.keys().next().value;n&&this.cache.delete(n)}}};var f="scalemule_chat_offline_queue",u=class{constructor(n=!0){this.queue=[];this.enabled=n,this.enabled&&this.load()}enqueue(n,e,t="text",s){this.enabled&&(this.queue.push({conversationId:n,content:e,message_type:t,attachments:s,timestamp:Date.now()}),this.save())}drain(){let n=[...this.queue];return this.queue=[],this.save(),n}get size(){return this.queue.length}save(){try{typeof localStorage<"u"&&localStorage.setItem(f,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let n=localStorage.getItem(f);n&&(this.queue=JSON.parse(n))}}catch{this.queue=[]}}};var l=class{constructor(n){this.baseUrl=n.baseUrl.replace(/\/$/,""),this.apiKey=n.apiKey,this.getToken=n.getToken,this.timeout=n.timeout??1e4}async get(n){return this.request("GET",n)}async post(n,e){return this.request("POST",n,e)}async patch(n,e){return this.request("PATCH",n,e)}async del(n){return this.request("DELETE",n)}async request(n,e,t){let s={"Content-Type":"application/json"};if(this.apiKey&&(s["x-api-key"]=this.apiKey),this.getToken){let r=await this.getToken();r&&(s.Authorization=`Bearer ${r}`)}let i=new AbortController,o=setTimeout(()=>i.abort(),this.timeout);try{let r=await fetch(`${this.baseUrl}${e}`,{method:n,headers:s,body:t?JSON.stringify(t):void 0,signal:i.signal,credentials:"include"});if(clearTimeout(o),r.status===204)return{data:null,error:null};let a=await r.json().catch(()=>null);return r.ok?{data:a?.data!==void 0?a.data:a,error:null}:{data:null,error:{code:a?.error?.code??a?.code??"unknown",message:a?.error?.message??a?.message??r.statusText,status:r.status,details:a?.error?.details??a?.details}}}catch(r){return clearTimeout(o),{data:null,error:{code:"network_error",message:r instanceof Error?r.message:"Network error",status:0}}}}};var g=class extends p{constructor(e){super();this.ws=null;this.status="disconnected";this.subscriptions=new Set;this.presenceChannels=new Map;this.reconnectAttempt=0;this.reconnectTimer=null;this.heartbeatTimer=null;this.config=e,this.maxRetries=e.reconnect?.maxRetries??1/0,this.baseDelay=e.reconnect?.baseDelay??1e3,this.maxDelay=e.reconnect?.maxDelay??3e4;let t=e.baseUrl.replace(/\/$/,"");this.ticketUrl=`${t}/v1/realtime/ws/ticket`;let s=e.wsUrl?e.wsUrl.replace(/\/$/,""):t;this.wsBaseUrl=s.replace(/^http/,"ws")+"/v1/realtime/ws"}getStatus(){return this.status}async connect(){if(!(this.status==="connected"||this.status==="connecting")){this.setStatus("connecting");try{let e=await this.obtainTicket();if(!e){this.setStatus("disconnected"),this.emit("error",{message:"Failed to obtain WS ticket"});return}let t=`${this.wsBaseUrl}?ticket=${encodeURIComponent(e)}`;this.ws=new WebSocket(t),this.ws.onopen=()=>{this.setStatus("connected"),this.reconnectAttempt=0,this.startHeartbeat(),this.resubscribeAll()},this.ws.onmessage=s=>{this.handleMessage(s.data)},this.ws.onclose=()=>{this.stopHeartbeat(),this.status!=="disconnected"&&this.scheduleReconnect()},this.ws.onerror=()=>{}}catch{this.setStatus("disconnected"),this.scheduleReconnect()}}}disconnect(){this.setStatus("disconnected"),this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.onclose=null,this.ws.close(),this.ws=null)}subscribe(e){return this.subscriptions.add(e),this.status==="connected"?this.send({type:"subscribe",channel:e}):this.status==="disconnected"&&this.connect(),()=>this.unsubscribe(e)}unsubscribe(e){this.subscriptions.delete(e),this.status==="connected"&&this.send({type:"unsubscribe",channel:e})}publish(e,t){this.status==="connected"&&this.send({type:"publish",channel:e,data:t})}joinPresence(e,t){this.presenceChannels.set(e,t),this.status==="connected"&&this.send({type:"presence_join",channel:e,user_data:t??{}})}leavePresence(e){this.presenceChannels.delete(e),this.status==="connected"&&this.send({type:"presence_leave",channel:e})}async obtainTicket(){let e={"Content-Type":"application/json"};if(this.config.apiKey&&(e["x-api-key"]=this.config.apiKey),this.config.getToken){let t=await this.config.getToken();t&&(e.Authorization=`Bearer ${t}`)}try{let t=await fetch(this.ticketUrl,{method:"POST",headers:e,credentials:"include"});if(!t.ok)return null;let s=await t.json();return s.ticket??s.data?.ticket??null}catch{return null}}send(e){this.ws?.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}handleMessage(e){if(e!=="pong")try{let t=JSON.parse(e);switch(t.type){case"auth_success":break;case"subscribed":break;case"message":this.emit("message",{channel:t.channel,data:t.data});break;case"presence_state":this.emit("presence:state",{channel:t.channel,members:t.members??[]});break;case"presence_join":this.emit("presence:join",{channel:t.channel,user:t.user});break;case"presence_leave":this.emit("presence:leave",{channel:t.channel,userId:t.user_id});break;case"presence_update":this.emit("presence:update",{channel:t.channel,userId:t.user_id,status:t.status,userData:t.user_data});break;case"error":this.emit("error",{message:t.message??"Unknown error"});break;case"token_expiring":this.reconnectAttempt=0,this.scheduleReconnect();break}}catch{}}resubscribeAll(){for(let e of this.subscriptions)this.send({type:"subscribe",channel:e});for(let[e,t]of this.presenceChannels)this.send({type:"presence_join",channel:e,user_data:t??{}})}startHeartbeat(){this.heartbeatTimer=setInterval(()=>{this.ws?.readyState===WebSocket.OPEN&&this.ws.send("ping")},3e4)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.reconnectAttempt>=this.maxRetries){this.setStatus("disconnected");return}this.setStatus("reconnecting"),this.emit("reconnecting",{attempt:this.reconnectAttempt+1});let e=Math.min(this.baseDelay*Math.pow(2,this.reconnectAttempt)+Math.random()*this.baseDelay*.3,this.maxDelay);this.reconnectTimer=setTimeout(()=>{this.reconnectAttempt++,this.connect()},e)}setStatus(e){this.status!==e&&(this.status=e,this.emit("status",e))}};var m=class extends p{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??v;this.http=new l({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0)}),this.ws=new g({baseUrl:t,wsUrl:e.wsUrl,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0),reconnect:e.reconnect}),this.cache=new d(e.messageCache?.maxMessages,e.messageCache?.maxConversations),this.offlineQueue=new u(e.offlineQueue??!0),this.ws.on("status",s=>{switch(s){case"connected":this.emit("connected"),this.flushOfflineQueue();break;case"disconnected":this.emit("disconnected");break}}),this.ws.on("reconnecting",s=>{this.emit("reconnecting",s)}),this.ws.on("message",({channel:s,data:i})=>{this.handleRealtimeMessage(s,i)}),this.ws.on("presence:state",({channel:s,members:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:state",{conversationId:o,members:i})}),this.ws.on("presence:join",({channel:s,user:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:join",{userId:i.user_id,conversationId:o,userData:i.user_data})}),this.ws.on("presence:leave",({channel:s,userId:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:leave",{userId:i,conversationId:o})}),this.ws.on("presence:update",({channel:s,userId:i,status:o,userData:r})=>{let a=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:update",{userId:i,conversationId:a,status:o,userData:r})}),this.ws.on("error",({message:s})=>{this.emit("error",{code:"ws_error",message:s})})}get status(){return this.ws.getStatus()}connect(){this.ws.connect()}disconnect(){for(let e of this.conversationSubs.values())e();this.conversationSubs.clear(),this.ws.disconnect()}async createConversation(e){let t={...e,conversation_type:e.conversation_type??"direct"},s=await this.http.post("/v1/chat/conversations",t);return s.data&&this.trackConversationType(s.data),s}async listConversations(e){let t=new URLSearchParams;e?.page&&t.set("page",String(e.page)),e?.per_page&&t.set("per_page",String(e.per_page)),e?.conversation_type&&t.set("conversation_type",e.conversation_type);let s=t.toString(),i=await this.http.get(`/v1/chat/conversations${s?"?"+s:""}`);return i.data&&i.data.forEach(o=>this.trackConversationType(o)),i}async getConversation(e){let t=await this.http.get(`/v1/chat/conversations/${e}`);return t.data&&this.trackConversationType(t.data),t}trackConversationType(e){e.conversation_type!=="direct"&&e.conversation_type!=="group"&&this.conversationTypes.set(e.id,e.conversation_type)}async sendMessage(e,t){let s=await this.http.post(`/v1/chat/conversations/${e}/messages`,{content:t.content,message_type:t.message_type??"text",attachments:t.attachments});return s.data?this.cache.addMessage(e,s.data):s.error?.status===0&&this.offlineQueue.enqueue(e,t.content,t.message_type??"text"),s}async getMessages(e,t){let s=new URLSearchParams;t?.limit&&s.set("limit",String(t.limit)),t?.before&&s.set("before",t.before),t?.after&&s.set("after",t.after);let i=s.toString(),o=await this.http.get(`/v1/chat/conversations/${e}/messages${i?"?"+i:""}`);return o.data?.messages&&(t?.after||(o.data.messages=o.data.messages.slice().reverse()),!t?.before&&!t?.after&&this.cache.setMessages(e,o.data.messages)),o}async editMessage(e,t){return this.http.patch(`/v1/chat/messages/${e}`,{content:t})}async deleteMessage(e){return this.http.del(`/v1/chat/messages/${e}`)}getCachedMessages(e){return this.cache.getMessages(e)}async addReaction(e,t){return this.http.post(`/v1/chat/messages/${e}/reactions`,{emoji:t})}async removeReaction(e,t){return this.http.del(`/v1/chat/messages/${e}/reactions/${encodeURIComponent(t)}`)}async getUnreadTotal(){return this.http.get("/v1/chat/conversations/unread-total")}async sendTyping(e,t=!0){await this.http.post(`/v1/chat/conversations/${e}/typing`,{is_typing:t})}async markRead(e){await this.http.post(`/v1/chat/conversations/${e}/read`)}async getReadStatus(e){return this.http.get(`/v1/chat/conversations/${e}/read-status`)}async addParticipant(e,t){return this.http.post(`/v1/chat/conversations/${e}/participants`,{user_id:t})}async removeParticipant(e,t){return this.http.del(`/v1/chat/conversations/${e}/participants/${t}`)}joinPresence(e,t){let s=this.channelName(e);this.ws.joinPresence(s,t)}leavePresence(e){let t=this.channelName(e);this.ws.leavePresence(t)}updatePresence(e,t,s){let i=this.channelName(e);this.ws.send({type:"presence_update",channel:i,status:t,user_data:s})}async getChannelSettings(e){return this.http.get(`/v1/chat/channels/${e}/settings`)}setConversationType(e,t){this.conversationTypes.set(e,t)}async findChannelBySessionId(e){let t=await this.http.get(`/v1/chat/channels/by-session?linked_session_id=${encodeURIComponent(e)}`);return t.error?.status===404?null:(t.data&&this.conversationTypes.set(t.data.id,t.data.channel_type),t.data)}async joinChannel(e){return this.http.post(`/v1/chat/channels/${e}/join`,{})}async createEphemeralChannel(e){let t=await this.http.post("/v1/chat/channels/ephemeral",e);return t.data&&this.conversationTypes.set(t.data.id,"ephemeral"),t}async createLargeRoom(e){let t=await this.http.post("/v1/chat/channels/large-room",e);return t.data&&this.conversationTypes.set(t.data.id,"large_room"),t}async getSubscriberCount(e){return(await this.http.get(`/v1/chat/conversations/${e}/subscriber-count`)).data?.count??0}subscribeToConversation(e){if(this.conversationSubs.has(e))return this.conversationSubs.get(e);let t=this.channelName(e),s=this.ws.subscribe(t);return this.conversationSubs.set(e,s),()=>{this.conversationSubs.delete(e),s()}}channelName(e){switch(this.conversationTypes.get(e)){case"large_room":return`conversation:lr:${e}`;case"broadcast":return`conversation:bc:${e}`;case"support":return`conversation:support:${e}`;default:return`conversation:${e}`}}destroy(){this.disconnect(),this.cache.clear(),this.removeAllListeners()}handleRealtimeMessage(e,t){if(e.startsWith("conversation:")){this.handleConversationMessage(e,t);return}if(e.startsWith("private:")){this.handlePrivateMessage(t);return}}handlePrivateMessage(e){let t=e;if(!t)return;let s=t.event??t.type,i=t.data??t;switch(s){case"new_message":{let o=i.conversation_id,r=i.id??i.message_id,a=i.sender_id,c=i.content??"";o&&this.emit("inbox:update",{conversationId:o,messageId:r,senderId:a,preview:c});break}case"support:new_conversation":{let o=i.conversation_id,r=i.visitor_name;o&&this.emit("support:new",{conversationId:o,visitorName:r});break}case"support:assigned":{let o=i.conversation_id,r=i.visitor_name,a=i.visitor_email;o&&this.emit("support:assigned",{conversationId:o,visitorName:r,visitorEmail:a});break}}}handleConversationMessage(e,t){let s=e.replace(/^conversation:(?:lr:|bc:|support:)?/,""),i=t;if(!i)return;let o=i.event??i.type,r=i.data??i;switch(o){case"new_message":{let a=r;this.cache.addMessage(s,a),this.emit("message",{message:a,conversationId:s});break}case"message_edited":{let a=r;this.cache.updateMessage(s,a),this.emit("message:updated",{message:a,conversationId:s});break}case"message_deleted":{let a=r.message_id??r.id;a&&(this.cache.removeMessage(s,a),this.emit("message:deleted",{messageId:a,conversationId:s}));break}case"user_typing":{let a=r.user_id;a&&this.emit("typing",{userId:a,conversationId:s});break}case"user_stopped_typing":{let a=r.user_id;a&&this.emit("typing:stop",{userId:a,conversationId:s});break}case"typing_batch":{let a=r.users??[];for(let c of a)this.emit("typing",{userId:c,conversationId:s});break}case"messages_read":{let a=r.user_id,c=r.last_read_at;a&&c&&this.emit("read",{userId:a,conversationId:s,lastReadAt:c});break}case"room_upgraded":{if(r.new_type==="large_room"){this.conversationTypes.set(s,"large_room");let c=this.conversationSubs.get(s);c&&(c(),this.conversationSubs.delete(s),this.subscribeToConversation(s)),this.emit("room_upgraded",{conversationId:s,newType:"large_room"})}break}}}async flushOfflineQueue(){let e=this.offlineQueue.drain();for(let t of e)await this.sendMessage(t.conversationId,{content:t.content,message_type:t.message_type})}};function y(){let h=new URLSearchParams(window.location.search),n=window.location.pathname.split("/"),e=n[n.length-1]||void 0,t=h.get("token")??void 0,s;try{s={...window.location.hash?JSON.parse(decodeURIComponent(window.location.hash.slice(1))):{},embedToken:t}}catch{s={embedToken:t}}let i=new m(s);i.on("message",({message:r,conversationId:a})=>{window.parent.postMessage({type:"message",payload:{message:r,conversationId:a}},"*")}),i.on("connected",()=>{window.parent.postMessage({type:"connected"},"*")});let o=!1;window.addEventListener("message",r=>{let{method:a,args:c}=r.data??{};switch(a){case"sendMessage":e&&c?.content&&i.sendMessage(e,{content:c.content});break;case"disconnect":o=!0,i.disconnect();break}}),e&&i.getConversation(e).then(()=>{o||(i.subscribeToConversation(e),i.connect())})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",y):y());})();
|
|
1
|
+
"use strict";var ChatEmbed=(()=>{var h=class{constructor(){this.listeners=new Map}on(i,e){return this.listeners.has(i)||this.listeners.set(i,new Set),this.listeners.get(i).add(e),()=>this.off(i,e)}off(i,e){this.listeners.get(i)?.delete(e)}once(i,e){let t=(s=>{this.off(i,t),e(s)});return this.on(i,t)}emit(i,...[e]){let t=this.listeners.get(i);if(t)for(let s of t)try{s(e)}catch(n){console.error(`[ScaleMuleChat] Error in ${String(i)} listener:`,n)}}removeAllListeners(i){i?this.listeners.delete(i):this.listeners.clear()}};var b="https://api.scalemule.com";var l=class{constructor(i,e){this.cache=new Map;this.maxMessages=i??200,this.maxConversations=e??50}getMessages(i){return this.cache.get(i)??[]}getMessage(i,e){return this.getMessages(i).find(t=>t.id===e)}setMessages(i,e){this.cache.set(i,e.slice(0,this.maxMessages)),this.evictOldConversations()}addMessage(i,e){let t=this.cache.get(i)??[];t.some(s=>s.id===e.id)||(t.push(e),t.sort((s,n)=>s.created_at.localeCompare(n.created_at)),t.length>this.maxMessages&&t.splice(0,t.length-this.maxMessages),this.cache.set(i,t))}upsertMessage(i,e){if(this.getMessage(i,e.id)){this.updateMessage(i,e);return}this.addMessage(i,e)}updateMessage(i,e){let t=this.cache.get(i);if(!t)return;let s=t.findIndex(n=>n.id===e.id);s>=0&&(t[s]=e)}reconcileOptimisticMessage(i,e){let t=this.cache.get(i);if(!t)return e;let s=(e.attachments??[]).map(o=>o.file_id).sort(),n=t.findIndex(o=>{if(!o.id.startsWith("pending-")||o.sender_id!==e.sender_id||o.content!==e.content)return!1;let a=(o.attachments??[]).map(r=>r.file_id).sort();return a.length!==s.length?!1:a.every((r,c)=>r===s[c])});return n>=0&&(t[n]=e),e}removeMessage(i,e){let t=this.cache.get(i);if(!t)return;let s=t.findIndex(n=>n.id===e);s>=0&&t.splice(s,1)}clear(i){i?this.cache.delete(i):this.cache.clear()}evictOldConversations(){for(;this.cache.size>this.maxConversations;){let i=this.cache.keys().next().value;i&&this.cache.delete(i)}}};var y="scalemule_chat_offline_queue",g=class{constructor(i=!0){this.queue=[];this.enabled=i,this.enabled&&this.load()}enqueue(i,e,t="text",s){this.enabled&&(this.queue.push({conversationId:i,content:e,message_type:t,attachments:s,timestamp:Date.now()}),this.save())}drain(){let i=[...this.queue];return this.queue=[],this.save(),i}get size(){return this.queue.length}save(){try{typeof localStorage<"u"&&localStorage.setItem(y,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let i=localStorage.getItem(y);i&&(this.queue=JSON.parse(i))}}catch{this.queue=[]}}};var m=class{constructor(i){this.baseUrl=i.baseUrl.replace(/\/$/,""),this.apiKey=i.apiKey,this.getToken=i.getToken,this.timeout=i.timeout??1e4}async get(i){return this.request("GET",i)}async post(i,e){return this.request("POST",i,e)}async patch(i,e){return this.request("PATCH",i,e)}async del(i){return this.request("DELETE",i)}async request(i,e,t){let s={"Content-Type":"application/json"};if(this.apiKey&&(s["x-api-key"]=this.apiKey),this.getToken){let a=await this.getToken();a&&(s.Authorization=`Bearer ${a}`)}let n=new AbortController,o=setTimeout(()=>n.abort(),this.timeout);try{let a=await fetch(`${this.baseUrl}${e}`,{method:i,headers:s,body:t?JSON.stringify(t):void 0,signal:n.signal});if(clearTimeout(o),a.status===204)return{data:null,error:null};let r=await a.json().catch(()=>null);return a.ok?{data:r?.data!==void 0?r.data:r,error:null}:{data:null,error:{code:r?.error?.code??r?.code??"unknown",message:r?.error?.message??r?.message??a.statusText,status:a.status,details:r?.error?.details??r?.details}}}catch(a){return clearTimeout(o),{data:null,error:{code:"network_error",message:a instanceof Error?a.message:"Network error",status:0}}}}};var v=class extends h{constructor(e){super();this.ws=null;this.status="disconnected";this.subscriptions=new Set;this.presenceChannels=new Map;this.reconnectAttempt=0;this.reconnectTimer=null;this.heartbeatTimer=null;this.config=e,this.maxRetries=e.reconnect?.maxRetries??1/0,this.baseDelay=e.reconnect?.baseDelay??1e3,this.maxDelay=e.reconnect?.maxDelay??3e4;let t=e.baseUrl.replace(/\/$/,"");this.ticketUrl=`${t}/v1/realtime/ws/ticket`;let s=e.wsUrl?e.wsUrl.replace(/\/$/,""):t;this.wsBaseUrl=s.replace(/^http/,"ws")+"/v1/realtime/ws"}getStatus(){return this.status}async connect(){if(!(this.status==="connected"||this.status==="connecting")){this.setStatus("connecting");try{let e=await this.obtainTicket();if(!e){this.setStatus("disconnected"),this.emit("error",{message:"Failed to obtain WS ticket"});return}let t=`${this.wsBaseUrl}?ticket=${encodeURIComponent(e)}`;this.ws=new WebSocket(t),this.ws.onopen=()=>{this.setStatus("connected"),this.reconnectAttempt=0,this.startHeartbeat(),this.resubscribeAll()},this.ws.onmessage=s=>{this.handleMessage(s.data)},this.ws.onclose=()=>{this.stopHeartbeat(),this.status!=="disconnected"&&this.scheduleReconnect()},this.ws.onerror=()=>{}}catch{this.setStatus("disconnected"),this.scheduleReconnect()}}}disconnect(){this.setStatus("disconnected"),this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.onclose=null,this.ws.close(),this.ws=null)}subscribe(e){return this.subscriptions.add(e),this.status==="connected"?this.send({type:"subscribe",channel:e}):this.status==="disconnected"&&this.connect(),()=>this.unsubscribe(e)}unsubscribe(e){this.subscriptions.delete(e),this.status==="connected"&&this.send({type:"unsubscribe",channel:e})}publish(e,t){this.status==="connected"&&this.send({type:"publish",channel:e,data:t})}joinPresence(e,t){this.presenceChannels.set(e,t),this.status==="connected"&&this.send({type:"presence_join",channel:e,user_data:t??{}})}leavePresence(e){this.presenceChannels.delete(e),this.status==="connected"&&this.send({type:"presence_leave",channel:e})}async obtainTicket(){let e={"Content-Type":"application/json"};if(this.config.apiKey&&(e["x-api-key"]=this.config.apiKey),this.config.getToken){let t=await this.config.getToken();t&&(e.Authorization=`Bearer ${t}`)}try{let t=await fetch(this.ticketUrl,{method:"POST",headers:e});if(!t.ok)return null;let s=await t.json();return s.ticket??s.data?.ticket??null}catch{return null}}send(e){this.ws?.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}handleMessage(e){if(e!=="pong")try{let t=JSON.parse(e);switch(t.type){case"auth_success":break;case"subscribed":break;case"message":this.emit("message",{channel:t.channel,data:t.data});break;case"presence_state":this.emit("presence:state",{channel:t.channel,members:t.members??[]});break;case"presence_join":this.emit("presence:join",{channel:t.channel,user:t.user});break;case"presence_leave":this.emit("presence:leave",{channel:t.channel,userId:t.user_id});break;case"presence_update":this.emit("presence:update",{channel:t.channel,userId:t.user_id,status:t.status,userData:t.user_data});break;case"error":this.emit("error",{message:t.message??"Unknown error"});break;case"token_expiring":this.reconnectAttempt=0,this.scheduleReconnect();break}}catch{}}resubscribeAll(){for(let e of this.subscriptions)this.send({type:"subscribe",channel:e});for(let[e,t]of this.presenceChannels)this.send({type:"presence_join",channel:e,user_data:t??{}})}startHeartbeat(){this.heartbeatTimer=setInterval(()=>{this.ws?.readyState===WebSocket.OPEN&&this.ws.send("ping")},3e4)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.reconnectAttempt>=this.maxRetries){this.setStatus("disconnected");return}this.setStatus("reconnecting"),this.emit("reconnecting",{attempt:this.reconnectAttempt+1});let e=Math.min(this.baseDelay*Math.pow(2,this.reconnectAttempt)+Math.random()*this.baseDelay*.3,this.maxDelay);this.reconnectTimer=setTimeout(()=>{this.reconnectAttempt++,this.connect()},e)}setStatus(e){this.status!==e&&(this.status=e,this.emit("status",e))}};var P=[0,1e3,3e3],w=45e3,x=new Set([0,408,429,500,502,503,504]);function L(d){return new Promise(i=>setTimeout(i,d))}function I(d,i){return i==="aborted"?!1:x.has(d)||i==="upload_stalled"}function O(d,i,e,t){return new Promise(s=>{if(typeof XMLHttpRequest>"u"){s({data:null,error:{code:"unsupported_environment",message:"XMLHttpRequest is not available in this environment",status:0}});return}let n=new XMLHttpRequest,o=!1,a=null,r=0,c=i.size,p=u=>{o||(o=!0,a&&(clearTimeout(a),a=null),s(u))},_=()=>{a&&clearTimeout(a),a=setTimeout(()=>{n.abort(),p({data:null,error:{code:"upload_stalled",message:`Upload stalled (no progress for ${w/1e3}s)`,status:0,details:{bytes_sent:r,total_bytes:c}}})},w)};if(t){if(t.aborted){p({data:null,error:{code:"aborted",message:"Upload aborted",status:0}});return}t.addEventListener("abort",()=>{n.abort(),p({data:null,error:{code:"aborted",message:"Upload aborted",status:0}})},{once:!0})}n.upload.addEventListener("progress",u=>{_(),r=u.loaded,c=u.total||c,u.lengthComputable&&e?.(Math.round(u.loaded/u.total*100))}),n.addEventListener("load",()=>{if(n.status>=200&&n.status<300){e?.(100),p({data:null,error:null});return}p({data:null,error:{code:"upload_error",message:`S3 upload failed: ${n.status}`,status:n.status,details:{bytes_sent:r,total_bytes:c}}})}),n.addEventListener("error",()=>{p({data:null,error:{code:"upload_error",message:"S3 upload failed",status:n.status||0,details:{bytes_sent:r,total_bytes:c}}})}),n.addEventListener("abort",()=>{o||p({data:null,error:{code:"aborted",message:"Upload aborted",status:0}})}),n.open("PUT",d,!0),i.type&&n.setRequestHeader("Content-Type",i.type),_(),n.send(i)})}async function T(d,i,e,t){let s=null;for(let[n,o]of P.entries()){o>0&&await L(o);let a=await O(d,i,e,t);if(!a.error)return a;if(s={...a.error,details:{...a.error.details,attempt:n+1}},!I(a.error.status,a.error.code))break}return{data:null,error:s??{code:"upload_error",message:"Upload failed",status:0}}}var f=class extends h{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??b;this.currentUserId=e.userId,this.http=new m({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0)}),this.ws=new v({baseUrl:t,wsUrl:e.wsUrl,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0),reconnect:e.reconnect}),this.cache=new l(e.messageCache?.maxMessages,e.messageCache?.maxConversations),this.offlineQueue=new g(e.offlineQueue??!0),this.ws.on("status",s=>{switch(s){case"connected":this.emit("connected"),this.flushOfflineQueue();break;case"disconnected":this.emit("disconnected");break}}),this.ws.on("reconnecting",s=>{this.emit("reconnecting",s)}),this.ws.on("message",({channel:s,data:n})=>{this.handleRealtimeMessage(s,n)}),this.ws.on("presence:state",({channel:s,members:n})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:state",{conversationId:o,members:n})}),this.ws.on("presence:join",({channel:s,user:n})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:join",{userId:n.user_id,conversationId:o,userData:n.user_data})}),this.ws.on("presence:leave",({channel:s,userId:n})=>{let o=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:leave",{userId:n,conversationId:o})}),this.ws.on("presence:update",({channel:s,userId:n,status:o,userData:a})=>{let r=s.replace(/^conversation:(?:lr:|bc:|support:)?/,"");this.emit("presence:update",{userId:n,conversationId:r,status:o,userData:a})}),this.ws.on("error",({message:s})=>{this.emit("error",{code:"ws_error",message:s})})}get status(){return this.ws.getStatus()}get userId(){return this.currentUserId}connect(){this.ws.connect()}disconnect(){for(let e of this.conversationSubs.values())e();this.conversationSubs.clear(),this.ws.disconnect()}async createConversation(e){let t={...e,conversation_type:e.conversation_type??"direct"},s=await this.http.post("/v1/chat/conversations",t);return s.data&&this.trackConversationType(s.data),s}async listConversations(e){let t=new URLSearchParams;e?.page&&t.set("page",String(e.page)),e?.per_page&&t.set("per_page",String(e.per_page)),e?.conversation_type&&t.set("conversation_type",e.conversation_type);let s=t.toString(),n=await this.http.get(`/v1/chat/conversations${s?"?"+s:""}`);return n.data&&n.data.forEach(o=>this.trackConversationType(o)),n}async getConversation(e){let t=await this.http.get(`/v1/chat/conversations/${e}`);return t.data&&this.trackConversationType(t.data),t}trackConversationType(e){e.conversation_type!=="direct"&&e.conversation_type!=="group"&&this.conversationTypes.set(e.id,e.conversation_type)}async sendMessage(e,t){let s=await this.http.post(`/v1/chat/conversations/${e}/messages`,{content:t.content,message_type:t.message_type??"text",attachments:t.attachments});if(s.data){let n=this.cache.reconcileOptimisticMessage(e,s.data);this.cache.upsertMessage(e,n)}else s.error?.status===0&&this.offlineQueue.enqueue(e,t.content,t.message_type??"text",t.attachments);return s}async getMessages(e,t){let s=new URLSearchParams;t?.limit&&s.set("limit",String(t.limit)),t?.before&&s.set("before",t.before),t?.after&&s.set("after",t.after);let n=s.toString(),o=await this.http.get(`/v1/chat/conversations/${e}/messages${n?"?"+n:""}`);return o.data?.messages&&(t?.after||(o.data.messages=o.data.messages.slice().reverse()),!t?.before&&!t?.after&&this.cache.setMessages(e,o.data.messages)),o}async editMessage(e,t){return this.http.patch(`/v1/chat/messages/${e}`,{content:t})}async deleteMessage(e){return this.http.del(`/v1/chat/messages/${e}`)}async uploadAttachment(e,t,s){let n=typeof File<"u"&&e instanceof File?e.name:"attachment",o=e.type||"application/octet-stream",a=await this.http.post("/v1/storage/signed-url/upload",{filename:n,content_type:o,size_bytes:e.size,is_public:!1,metadata:{source:"chat_sdk"}});if(a.error||!a.data)return{data:null,error:a.error};let r=await T(a.data.upload_url,e,t,s);if(r.error)return{data:null,error:r.error};let c=await this.http.post("/v1/storage/signed-url/complete",{file_id:a.data.file_id,completion_token:a.data.completion_token});return c.error||!c.data?{data:null,error:c.error}:{data:{file_id:c.data.file_id,file_name:c.data.filename,file_size:c.data.size_bytes,mime_type:c.data.content_type,presigned_url:c.data.url},error:null}}async refreshAttachmentUrl(e,t){return this.http.get(`/v1/chat/messages/${e}/attachment/${t}/url`)}getCachedMessages(e){return this.cache.getMessages(e)}stageOptimisticMessage(e,t){return this.cache.upsertMessage(e,t),t}async addReaction(e,t){return this.http.post(`/v1/chat/messages/${e}/reactions`,{emoji:t})}async removeReaction(e,t){return this.http.del(`/v1/chat/messages/${e}/reactions/${encodeURIComponent(t)}`)}async reportMessage(e,t,s){return this.http.post(`/v1/chat/messages/${e}/report`,{reason:t,description:s})}async muteConversation(e,t){return this.http.post(`/v1/chat/conversations/${e}/mute`,{muted_until:t})}async unmuteConversation(e){return this.http.del(`/v1/chat/conversations/${e}/mute`)}async getUnreadTotal(){return this.http.get("/v1/chat/conversations/unread-total")}async sendTyping(e,t=!0){await this.http.post(`/v1/chat/conversations/${e}/typing`,{is_typing:t})}async markRead(e){await this.http.post(`/v1/chat/conversations/${e}/read`)}async getReadStatus(e){return this.http.get(`/v1/chat/conversations/${e}/read-status`)}async addParticipant(e,t){return this.http.post(`/v1/chat/conversations/${e}/participants`,{user_id:t})}async removeParticipant(e,t){return this.http.del(`/v1/chat/conversations/${e}/participants/${t}`)}joinPresence(e,t){let s=this.channelName(e);this.ws.joinPresence(s,t)}leavePresence(e){let t=this.channelName(e);this.ws.leavePresence(t)}updatePresence(e,t,s){let n=this.channelName(e);this.ws.send({type:"presence_update",channel:n,status:t,user_data:s})}async getChannelSettings(e){return this.http.get(`/v1/chat/channels/${e}/settings`)}setConversationType(e,t){this.conversationTypes.set(e,t)}async findChannelBySessionId(e){let t=await this.http.get(`/v1/chat/channels/by-session?linked_session_id=${encodeURIComponent(e)}`);return t.error?.status===404?null:(t.data&&this.conversationTypes.set(t.data.id,t.data.channel_type),t.data)}async joinChannel(e){return this.http.post(`/v1/chat/channels/${e}/join`,{})}async createEphemeralChannel(e){let t=await this.http.post("/v1/chat/channels/ephemeral",e);return t.data&&this.conversationTypes.set(t.data.id,"ephemeral"),t}async createLargeRoom(e){let t=await this.http.post("/v1/chat/channels/large-room",e);return t.data&&this.conversationTypes.set(t.data.id,"large_room"),t}async getSubscriberCount(e){return(await this.http.get(`/v1/chat/conversations/${e}/subscriber-count`)).data?.count??0}subscribeToConversation(e){if(this.conversationSubs.has(e))return this.conversationSubs.get(e);let t=this.channelName(e),s=this.ws.subscribe(t);return this.conversationSubs.set(e,s),()=>{this.conversationSubs.delete(e),s()}}channelName(e){switch(this.conversationTypes.get(e)){case"large_room":return`conversation:lr:${e}`;case"broadcast":return`conversation:bc:${e}`;case"support":return`conversation:support:${e}`;default:return`conversation:${e}`}}destroy(){this.disconnect(),this.cache.clear(),this.removeAllListeners()}handleRealtimeMessage(e,t){if(e.startsWith("conversation:")){this.handleConversationMessage(e,t);return}if(e.startsWith("private:")){this.handlePrivateMessage(t);return}}handlePrivateMessage(e){let t=e;if(!t)return;let s=t.event??t.type,n=t.data??t;switch(s){case"new_message":{let o=n.conversation_id,a=n.id??n.message_id,r=n.sender_id,c=n.content??"";o&&this.emit("inbox:update",{conversationId:o,messageId:a,senderId:r,preview:c});break}case"support:new_conversation":{let o=n.conversation_id,a=n.visitor_name;o&&this.emit("support:new",{conversationId:o,visitorName:a});break}case"support:assigned":{let o=n.conversation_id,a=n.visitor_name,r=n.visitor_email;o&&this.emit("support:assigned",{conversationId:o,visitorName:a,visitorEmail:r});break}}}normalizeMessage(e){return{id:e.id??e.message_id??"",content:e.content??"",message_type:e.message_type??"text",sender_id:e.sender_id??e.sender_user_id??"",sender_type:e.sender_type,sender_agent_model:e.sender_agent_model,attachments:e.attachments,reactions:e.reactions,is_edited:!!(e.is_edited??!1),created_at:e.created_at??e.updated_at??e.timestamp??new Date().toISOString()}}buildEditedMessage(e,t){let s=t.message_id??t.id;if(!s)return null;let n=this.cache.getMessage(e,s);return{id:n?.id??s,content:t.content??t.new_content??n?.content??"",message_type:n?.message_type??"text",sender_id:n?.sender_id??"",sender_type:n?.sender_type,sender_agent_model:n?.sender_agent_model,attachments:n?.attachments,reactions:n?.reactions,is_edited:!0,created_at:n?.created_at??t.updated_at??t.timestamp??new Date().toISOString()}}applyReactionEvent(e,t){let s={id:`${t.message_id}:${t.user_id}:${t.emoji}`,message_id:t.message_id,user_id:t.user_id,emoji:t.emoji,action:t.action,timestamp:t.timestamp},n=this.cache.getMessage(e,t.message_id);if(!n)return s;let o=[...n.reactions??[]],a=o.findIndex(r=>r.emoji===t.emoji);if(t.action==="added")if(a>=0){let r=o[a];if(!r.user_ids.includes(t.user_id)){let c=[...r.user_ids,t.user_id];o[a]={...r,user_ids:c,count:c.length}}}else o.push({emoji:t.emoji,count:1,user_ids:[t.user_id]});else if(a>=0){let r=o[a],c=r.user_ids.filter(p=>p!==t.user_id);c.length===0?o.splice(a,1):o[a]={...r,user_ids:c,count:c.length}}return this.cache.updateMessage(e,{...n,reactions:o}),s}handleConversationMessage(e,t){let s=e.replace(/^conversation:(?:lr:|bc:|support:)?/,""),n=t;if(!n)return;let o=n.event??n.type,a=n.data??n;switch(o){case"new_message":{let r=this.normalizeMessage(a),c=this.cache.reconcileOptimisticMessage(s,r);this.cache.upsertMessage(s,c),this.emit("message",{message:c,conversationId:s});break}case"message_edited":{let r=a,c=this.buildEditedMessage(s,r);c&&(this.cache.upsertMessage(s,c),this.emit("message:updated",{message:c,conversationId:s,update:r}));break}case"reaction":{let r=a;if(!r.message_id||!r.user_id||!r.emoji)break;let c=this.applyReactionEvent(s,r);this.emit("reaction",{reaction:c,conversationId:s,action:r.action});break}case"message_deleted":{let r=a.message_id??a.id;r&&(this.cache.removeMessage(s,r),this.emit("message:deleted",{messageId:r,conversationId:s}));break}case"user_typing":{let r=a.user_id;r&&this.emit("typing",{userId:r,conversationId:s});break}case"user_stopped_typing":{let r=a.user_id;r&&this.emit("typing:stop",{userId:r,conversationId:s});break}case"typing_batch":{let r=a.users??[];for(let c of r)this.emit("typing",{userId:c,conversationId:s});break}case"messages_read":{let r=a.user_id,c=a.last_read_at;r&&c&&this.emit("read",{userId:r,conversationId:s,lastReadAt:c});break}case"room_upgraded":{if(a.new_type==="large_room"){this.conversationTypes.set(s,"large_room");let c=this.conversationSubs.get(s);c&&(c(),this.conversationSubs.delete(s),this.subscribeToConversation(s)),this.emit("room_upgraded",{conversationId:s,newType:"large_room"})}break}}}async flushOfflineQueue(){let e=this.offlineQueue.drain();for(let t of e)await this.sendMessage(t.conversationId,{content:t.content,message_type:t.message_type,attachments:t.attachments})}};function C(){let d=new URLSearchParams(window.location.search),i=window.location.pathname.split("/"),e=i[i.length-1]||void 0,t=d.get("token")??void 0,s;try{s={...window.location.hash?JSON.parse(decodeURIComponent(window.location.hash.slice(1))):{},embedToken:t}}catch{s={embedToken:t}}let n=new f(s);n.on("message",({message:a,conversationId:r})=>{window.parent.postMessage({type:"message",payload:{message:a,conversationId:r}},"*")}),n.on("connected",()=>{window.parent.postMessage({type:"connected"},"*")});let o=!1;window.addEventListener("message",a=>{let{method:r,args:c}=a.data??{};switch(r){case"sendMessage":e&&c?.content&&n.sendMessage(e,{content:c.content});break;case"disconnect":o=!0,n.disconnect();break}}),e&&n.getConversation(e).then(()=>{o||(n.subscribeToConversation(e),n.connect())})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",C):C());})();
|