@scalemule/chat 0.0.1

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.
@@ -0,0 +1,59 @@
1
+ import { d as ChatEventMap, c as ChatConfig, g as ConnectionStatus, i as CreateConversationOptions, a as ApiResponse, h as Conversation, L as ListConversationsOptions, S as SendMessageOptions, e as ChatMessage, G as GetMessagesOptions, M as MessagesResponse, R as ReadStatus, C as ChannelSettings } from './types-BwgD_Etd.cjs';
2
+
3
+ type Listener<T> = (data: T) => void;
4
+ declare class EventEmitter<EventMap extends Record<string, any>> {
5
+ private listeners;
6
+ on<K extends keyof EventMap>(event: K, callback: Listener<EventMap[K]>): () => void;
7
+ off<K extends keyof EventMap>(event: K, callback: Listener<EventMap[K]>): void;
8
+ once<K extends keyof EventMap>(event: K, callback: Listener<EventMap[K]>): () => void;
9
+ emit<K extends keyof EventMap>(event: K, ...[data]: EventMap[K] extends void ? [] : [EventMap[K]]): void;
10
+ removeAllListeners(event?: keyof EventMap): void;
11
+ }
12
+
13
+ declare class ChatClient extends EventEmitter<ChatEventMap> {
14
+ private http;
15
+ private ws;
16
+ private cache;
17
+ private offlineQueue;
18
+ private conversationSubs;
19
+ private conversationTypes;
20
+ constructor(config: ChatConfig);
21
+ get status(): ConnectionStatus;
22
+ connect(): void;
23
+ disconnect(): void;
24
+ createConversation(options: CreateConversationOptions): Promise<ApiResponse<Conversation>>;
25
+ listConversations(options?: ListConversationsOptions): Promise<ApiResponse<Conversation[]>>;
26
+ getConversation(id: string): Promise<ApiResponse<Conversation>>;
27
+ private trackConversationType;
28
+ sendMessage(conversationId: string, options: SendMessageOptions): Promise<ApiResponse<ChatMessage>>;
29
+ getMessages(conversationId: string, options?: GetMessagesOptions): Promise<ApiResponse<MessagesResponse>>;
30
+ editMessage(messageId: string, content: string): Promise<ApiResponse<void>>;
31
+ deleteMessage(messageId: string): Promise<ApiResponse<void>>;
32
+ getCachedMessages(conversationId: string): ChatMessage[];
33
+ addReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
34
+ sendTyping(conversationId: string, isTyping?: boolean): Promise<void>;
35
+ markRead(conversationId: string): Promise<void>;
36
+ getReadStatus(conversationId: string): Promise<ApiResponse<{
37
+ statuses: ReadStatus[];
38
+ }>>;
39
+ addParticipant(conversationId: string, userId: string): Promise<ApiResponse<void>>;
40
+ removeParticipant(conversationId: string, userId: string): Promise<ApiResponse<void>>;
41
+ joinPresence(conversationId: string, userData?: unknown): void;
42
+ leavePresence(conversationId: string): void;
43
+ /** Update presence status (online/away/dnd) without leaving the channel. */
44
+ updatePresence(conversationId: string, status: 'online' | 'away' | 'dnd', userData?: unknown): void;
45
+ getChannelSettings(channelId: string): Promise<ApiResponse<ChannelSettings>>;
46
+ /**
47
+ * Set the conversation type for channel name routing.
48
+ * Large rooms use `conversation:lr:` prefix to skip MySQL in the realtime service.
49
+ */
50
+ setConversationType(conversationId: string, type: Conversation['conversation_type']): void;
51
+ subscribeToConversation(conversationId: string): () => void;
52
+ /** Build channel name with correct prefix based on conversation type. */
53
+ private channelName;
54
+ destroy(): void;
55
+ private handleRealtimeMessage;
56
+ private flushOfflineQueue;
57
+ }
58
+
59
+ export { ChatClient as C };
@@ -0,0 +1,59 @@
1
+ import { d as ChatEventMap, c as ChatConfig, g as ConnectionStatus, i as CreateConversationOptions, a as ApiResponse, h as Conversation, L as ListConversationsOptions, S as SendMessageOptions, e as ChatMessage, G as GetMessagesOptions, M as MessagesResponse, R as ReadStatus, C as ChannelSettings } from './types-BwgD_Etd.js';
2
+
3
+ type Listener<T> = (data: T) => void;
4
+ declare class EventEmitter<EventMap extends Record<string, any>> {
5
+ private listeners;
6
+ on<K extends keyof EventMap>(event: K, callback: Listener<EventMap[K]>): () => void;
7
+ off<K extends keyof EventMap>(event: K, callback: Listener<EventMap[K]>): void;
8
+ once<K extends keyof EventMap>(event: K, callback: Listener<EventMap[K]>): () => void;
9
+ emit<K extends keyof EventMap>(event: K, ...[data]: EventMap[K] extends void ? [] : [EventMap[K]]): void;
10
+ removeAllListeners(event?: keyof EventMap): void;
11
+ }
12
+
13
+ declare class ChatClient extends EventEmitter<ChatEventMap> {
14
+ private http;
15
+ private ws;
16
+ private cache;
17
+ private offlineQueue;
18
+ private conversationSubs;
19
+ private conversationTypes;
20
+ constructor(config: ChatConfig);
21
+ get status(): ConnectionStatus;
22
+ connect(): void;
23
+ disconnect(): void;
24
+ createConversation(options: CreateConversationOptions): Promise<ApiResponse<Conversation>>;
25
+ listConversations(options?: ListConversationsOptions): Promise<ApiResponse<Conversation[]>>;
26
+ getConversation(id: string): Promise<ApiResponse<Conversation>>;
27
+ private trackConversationType;
28
+ sendMessage(conversationId: string, options: SendMessageOptions): Promise<ApiResponse<ChatMessage>>;
29
+ getMessages(conversationId: string, options?: GetMessagesOptions): Promise<ApiResponse<MessagesResponse>>;
30
+ editMessage(messageId: string, content: string): Promise<ApiResponse<void>>;
31
+ deleteMessage(messageId: string): Promise<ApiResponse<void>>;
32
+ getCachedMessages(conversationId: string): ChatMessage[];
33
+ addReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
34
+ sendTyping(conversationId: string, isTyping?: boolean): Promise<void>;
35
+ markRead(conversationId: string): Promise<void>;
36
+ getReadStatus(conversationId: string): Promise<ApiResponse<{
37
+ statuses: ReadStatus[];
38
+ }>>;
39
+ addParticipant(conversationId: string, userId: string): Promise<ApiResponse<void>>;
40
+ removeParticipant(conversationId: string, userId: string): Promise<ApiResponse<void>>;
41
+ joinPresence(conversationId: string, userData?: unknown): void;
42
+ leavePresence(conversationId: string): void;
43
+ /** Update presence status (online/away/dnd) without leaving the channel. */
44
+ updatePresence(conversationId: string, status: 'online' | 'away' | 'dnd', userData?: unknown): void;
45
+ getChannelSettings(channelId: string): Promise<ApiResponse<ChannelSettings>>;
46
+ /**
47
+ * Set the conversation type for channel name routing.
48
+ * Large rooms use `conversation:lr:` prefix to skip MySQL in the realtime service.
49
+ */
50
+ setConversationType(conversationId: string, type: Conversation['conversation_type']): void;
51
+ subscribeToConversation(conversationId: string): () => void;
52
+ /** Build channel name with correct prefix based on conversation type. */
53
+ private channelName;
54
+ destroy(): void;
55
+ private handleRealtimeMessage;
56
+ private flushOfflineQueue;
57
+ }
58
+
59
+ export { ChatClient as C };
@@ -0,0 +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 u=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",d=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 g=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 l=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`,this.wsBaseUrl=t.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 g({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0)}),this.ws=new l({baseUrl:t,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0),reconnect:e.reconnect}),this.cache=new u(e.messageCache?.maxMessages,e.messageCache?.maxConversations),this.offlineQueue=new d(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:)?/,"");this.emit("presence:state",{conversationId:o,members:i})}),this.ws.on("presence:join",({channel:s,user:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:)?/,"");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:)?/,"");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:)?/,"");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=await this.http.post("/v1/chat/conversations",e);return t.data&&this.trackConversationType(t.data),t}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));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?.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 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)}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}`;default:return`conversation:${e}`}}destroy(){this.disconnect(),this.cache.clear(),this.removeAllListeners()}handleRealtimeMessage(e,t){if(!e.startsWith("conversation:"))return;let s=e.replace(/^conversation:(?:lr:|bc:)?/,""),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}}}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());})();
@@ -0,0 +1,18 @@
1
+ "use strict";var ScaleMuleChat=(()=>{var y=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var x=(c,n)=>{for(var e in n)y(c,e,{get:n[e],enumerable:!0})},R=(c,n,e,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of S(n))!A.call(c,s)&&s!==e&&y(c,s,{get:()=>n[s],enumerable:!(t=M(n,s))||t.enumerable});return c};var P=c=>R(y({},"__esModule",{value:!0}),c);var W={};x(W,{ChatClient:()=>p,create:()=>$,version:()=>H});var u=class{constructor(){this.listeners=new Map}on(n,e){return this.listeners.has(n)||this.listeners.set(n,new Set),this.listeners.get(n).add(e),()=>this.off(n,e)}off(n,e){this.listeners.get(n)?.delete(e)}once(n,e){let t=(s=>{this.off(n,t),e(s)});return this.on(n,t)}emit(n,...[e]){let t=this.listeners.get(n);if(t)for(let s of t)try{s(e)}catch(i){console.error(`[ScaleMuleChat] Error in ${String(n)} listener:`,i)}}removeAllListeners(n){n?this.listeners.delete(n):this.listeners.clear()}};var T="https://api.scalemule.com";var l=class{constructor(n,e){this.cache=new Map;this.maxMessages=n??200,this.maxConversations=e??50}getMessages(n){return this.cache.get(n)??[]}setMessages(n,e){this.cache.set(n,e.slice(0,this.maxMessages)),this.evictOldConversations()}addMessage(n,e){let t=this.cache.get(n)??[];t.some(s=>s.id===e.id)||(t.push(e),t.sort((s,i)=>s.created_at.localeCompare(i.created_at)),t.length>this.maxMessages&&t.splice(0,t.length-this.maxMessages),this.cache.set(n,t))}updateMessage(n,e){let t=this.cache.get(n);if(!t)return;let s=t.findIndex(i=>i.id===e.id);s>=0&&(t[s]=e)}removeMessage(n,e){let t=this.cache.get(n);if(!t)return;let s=t.findIndex(i=>i.id===e);s>=0&&t.splice(s,1)}clear(n){n?this.cache.delete(n):this.cache.clear()}evictOldConversations(){for(;this.cache.size>this.maxConversations;){let n=this.cache.keys().next().value;n&&this.cache.delete(n)}}};var w="scalemule_chat_offline_queue",g=class{constructor(n=!0){this.queue=[];this.enabled=n,this.enabled&&this.load()}enqueue(n,e,t="text",s){this.enabled&&(this.queue.push({conversationId:n,content:e,message_type:t,attachments:s,timestamp:Date.now()}),this.save())}drain(){let n=[...this.queue];return this.queue=[],this.save(),n}get size(){return this.queue.length}save(){try{typeof localStorage<"u"&&localStorage.setItem(w,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let n=localStorage.getItem(w);n&&(this.queue=JSON.parse(n))}}catch{this.queue=[]}}};var m=class{constructor(n){this.baseUrl=n.baseUrl.replace(/\/$/,""),this.apiKey=n.apiKey,this.getToken=n.getToken,this.timeout=n.timeout??1e4}async get(n){return this.request("GET",n)}async post(n,e){return this.request("POST",n,e)}async patch(n,e){return this.request("PATCH",n,e)}async del(n){return this.request("DELETE",n)}async request(n,e,t){let s={"Content-Type":"application/json"};if(this.apiKey&&(s["x-api-key"]=this.apiKey),this.getToken){let r=await this.getToken();r&&(s.Authorization=`Bearer ${r}`)}let i=new AbortController,o=setTimeout(()=>i.abort(),this.timeout);try{let r=await fetch(`${this.baseUrl}${e}`,{method:n,headers:s,body:t?JSON.stringify(t):void 0,signal:i.signal,credentials:"include"});if(clearTimeout(o),r.status===204)return{data:null,error:null};let a=await r.json().catch(()=>null);return r.ok?{data:a?.data!==void 0?a.data:a,error:null}:{data:null,error:{code:a?.error?.code??a?.code??"unknown",message:a?.error?.message??a?.message??r.statusText,status:r.status,details:a?.error?.details??a?.details}}}catch(r){return clearTimeout(o),{data:null,error:{code:"network_error",message:r instanceof Error?r.message:"Network error",status:0}}}}};var v=class extends u{constructor(e){super();this.ws=null;this.status="disconnected";this.subscriptions=new Set;this.presenceChannels=new Map;this.reconnectAttempt=0;this.reconnectTimer=null;this.heartbeatTimer=null;this.config=e,this.maxRetries=e.reconnect?.maxRetries??1/0,this.baseDelay=e.reconnect?.baseDelay??1e3,this.maxDelay=e.reconnect?.maxDelay??3e4;let t=e.baseUrl.replace(/\/$/,"");this.ticketUrl=`${t}/v1/realtime/ws/ticket`,this.wsBaseUrl=t.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 p=class extends u{constructor(e){super();this.conversationSubs=new Map;this.conversationTypes=new Map;let t=e.apiBaseUrl??T;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,apiKey:e.apiKey,getToken:e.getToken??(e.sessionToken?()=>Promise.resolve(e.sessionToken):void 0),reconnect:e.reconnect}),this.cache=new l(e.messageCache?.maxMessages,e.messageCache?.maxConversations),this.offlineQueue=new g(e.offlineQueue??!0),this.ws.on("status",s=>{switch(s){case"connected":this.emit("connected"),this.flushOfflineQueue();break;case"disconnected":this.emit("disconnected");break}}),this.ws.on("reconnecting",s=>{this.emit("reconnecting",s)}),this.ws.on("message",({channel:s,data:i})=>{this.handleRealtimeMessage(s,i)}),this.ws.on("presence:state",({channel:s,members:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:)?/,"");this.emit("presence:state",{conversationId:o,members:i})}),this.ws.on("presence:join",({channel:s,user:i})=>{let o=s.replace(/^conversation:(?:lr:|bc:)?/,"");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:)?/,"");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:)?/,"");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=await this.http.post("/v1/chat/conversations",e);return t.data&&this.trackConversationType(t.data),t}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));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?.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 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)}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}`;default:return`conversation:${e}`}}destroy(){this.disconnect(),this.cache.clear(),this.removeAllListeners()}handleRealtimeMessage(e,t){if(!e.startsWith("conversation:"))return;let s=e.replace(/^conversation:(?:lr:|bc:)?/,""),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 d of a)this.emit("typing",{userId:d,conversationId:s});break}case"messages_read":{let a=r.user_id,d=r.last_read_at;a&&d&&this.emit("read",{userId:a,conversationId:s,lastReadAt:d});break}}}async flushOfflineQueue(){let e=this.offlineQueue.drain();for(let t of e)await this.sendMessage(t.conversationId,{content:t.content,message_type:t.message_type})}};var _="0.0.1";var f={create(c){return new p(c)},version:_};var C=class extends HTMLElement{constructor(){super();this.client=null;this.unsub=null;this.shadow=this.attachShadow({mode:"open"})}static get observedAttributes(){return["api-key","conversation-id","api-base-url","embed-token"]}connectedCallback(){this.initialize()}disconnectedCallback(){this.cleanup()}attributeChangedCallback(){this.cleanup(),this.initialize()}initialize(){let e=this.getAttribute("api-key")??void 0,t=this.getAttribute("conversation-id"),s=this.getAttribute("api-base-url")??void 0,i=this.getAttribute("embed-token")??void 0;if(!e&&!i)return;let o={apiKey:e,embedToken:i,apiBaseUrl:s};this.client=new p(o),this.shadow.innerHTML=`
2
+ <style>
3
+ :host { display: block; width: 100%; height: 100%; }
4
+ .chat-container { width: 100%; height: 100%; display: flex; flex-direction: column; font-family: system-ui, sans-serif; }
5
+ .messages { flex: 1; overflow-y: auto; padding: 8px; }
6
+ .message { margin: 4px 0; padding: 6px 10px; background: #f0f0f0; border-radius: 8px; max-width: 80%; }
7
+ .input-area { display: flex; padding: 8px; border-top: 1px solid #e0e0e0; }
8
+ .input-area input { flex: 1; padding: 8px; border: 1px solid #d0d0d0; border-radius: 6px; outline: none; }
9
+ .input-area button { margin-left: 8px; padding: 8px 16px; background: #0066ff; color: white; border: none; border-radius: 6px; cursor: pointer; }
10
+ </style>
11
+ <div class="chat-container">
12
+ <div class="messages" id="messages"></div>
13
+ <div class="input-area">
14
+ <input type="text" placeholder="Type a message..." id="input" />
15
+ <button id="send">Send</button>
16
+ </div>
17
+ </div>
18
+ `;let r=this.shadow.getElementById("messages"),a=this.shadow.getElementById("input"),d=this.shadow.getElementById("send");this.client.on("message",({message:h})=>{this.appendMessage(r,h),this.dispatchEvent(new CustomEvent("chat-message",{detail:h,composed:!0,bubbles:!0}))});let b=()=>{let h=a.value.trim();!h||!t||!this.client||(this.client.sendMessage(t,{content:h}),a.value="")};d.addEventListener("click",b),a.addEventListener("keydown",h=>{h.key==="Enter"&&b()});let E=this.client;t&&(this.client.getConversation(t).then(()=>{this.client===E&&(this.unsub=this.client.subscribeToConversation(t),this.client.connect())}),this.client.getMessages(t).then(h=>{if(h.data?.messages)for(let k of h.data.messages)this.appendMessage(r,k)}))}appendMessage(e,t){let s=document.createElement("div");s.className="message",s.textContent=t.content,e.appendChild(s),e.scrollTop=e.scrollHeight}cleanup(){this.unsub?.(),this.unsub=null,this.client?.destroy(),this.client=null,this.shadow.innerHTML=""}};typeof customElements<"u"&&!customElements.get("scalemule-chat")&&customElements.define("scalemule-chat",C);var $=f.create.bind(f),H=f.version;return P(W);})();