@scalemule/chat 0.0.1 → 0.0.2
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-CXPywp1w.d.cts → ChatClient-BD0sMudg.d.ts} +30 -1
- package/dist/{ChatClient-DCid_mmU.d.ts → ChatClient-lwyQYmNf.d.cts} +30 -1
- package/dist/chat.embed.global.js +1 -1
- package/dist/chat.umd.global.js +2 -2
- package/dist/{chunk-6QRI4CJZ.js → chunk-FXO4ZYQB.js} +106 -2
- package/dist/{chunk-NFOVLPF2.cjs → chunk-WYFNZUAL.cjs} +106 -2
- package/dist/element.cjs +2 -2
- package/dist/element.js +1 -1
- package/dist/iframe.d.cts +1 -1
- package/dist/iframe.d.ts +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/react.cjs +72 -3
- package/dist/react.d.cts +14 -4
- package/dist/react.d.ts +14 -4
- package/dist/react.js +70 -3
- package/dist/{types-BwgD_Etd.d.cts → types-FQcFhbJx.d.cts} +45 -1
- package/dist/{types-BwgD_Etd.d.ts → types-FQcFhbJx.d.ts} +45 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { e as ChatEventMap, d as ChatConfig, h as ConnectionStatus, j as CreateConversationOptions, a as ApiResponse, i as Conversation, L as ListConversationsOptions, S as SendMessageOptions, f as ChatMessage, G as GetMessagesOptions, M as MessagesResponse, U as UnreadTotalResponse, n as ReadStatus, C as ChannelSettings, c as ChannelWithSettings, k as CreateEphemeralChannelOptions, l as CreateLargeRoomOptions } from './types-FQcFhbJx.js';
|
|
2
2
|
|
|
3
3
|
type Listener<T> = (data: T) => void;
|
|
4
4
|
declare class EventEmitter<EventMap extends Record<string, any>> {
|
|
@@ -31,6 +31,8 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
31
31
|
deleteMessage(messageId: string): Promise<ApiResponse<void>>;
|
|
32
32
|
getCachedMessages(conversationId: string): ChatMessage[];
|
|
33
33
|
addReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
|
|
34
|
+
removeReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
|
|
35
|
+
getUnreadTotal(): Promise<ApiResponse<UnreadTotalResponse>>;
|
|
34
36
|
sendTyping(conversationId: string, isTyping?: boolean): Promise<void>;
|
|
35
37
|
markRead(conversationId: string): Promise<void>;
|
|
36
38
|
getReadStatus(conversationId: string): Promise<ApiResponse<{
|
|
@@ -48,11 +50,38 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
48
50
|
* Large rooms use `conversation:lr:` prefix to skip MySQL in the realtime service.
|
|
49
51
|
*/
|
|
50
52
|
setConversationType(conversationId: string, type: Conversation['conversation_type']): void;
|
|
53
|
+
/**
|
|
54
|
+
* Find an active ephemeral or large_room channel by linked session ID.
|
|
55
|
+
* Returns null (not an error) if no active channel exists.
|
|
56
|
+
*/
|
|
57
|
+
findChannelBySessionId(linkedSessionId: string): Promise<ChannelWithSettings | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Self-join an ephemeral or large_room channel. Idempotent.
|
|
60
|
+
*/
|
|
61
|
+
joinChannel(channelId: string): Promise<ApiResponse<{
|
|
62
|
+
participant_id: string;
|
|
63
|
+
role: string;
|
|
64
|
+
joined_at: string;
|
|
65
|
+
}>>;
|
|
66
|
+
/**
|
|
67
|
+
* Create an ephemeral channel tied to a session (e.g., a video snap).
|
|
68
|
+
*/
|
|
69
|
+
createEphemeralChannel(options: CreateEphemeralChannelOptions): Promise<ApiResponse<ChannelWithSettings>>;
|
|
70
|
+
/**
|
|
71
|
+
* Create a large room channel (high-concurrency, skips MySQL tracking in realtime).
|
|
72
|
+
*/
|
|
73
|
+
createLargeRoom(options: CreateLargeRoomOptions): Promise<ApiResponse<ChannelWithSettings>>;
|
|
74
|
+
/**
|
|
75
|
+
* Get the global concurrent subscriber count for a conversation.
|
|
76
|
+
*/
|
|
77
|
+
getSubscriberCount(conversationId: string): Promise<number>;
|
|
51
78
|
subscribeToConversation(conversationId: string): () => void;
|
|
52
79
|
/** Build channel name with correct prefix based on conversation type. */
|
|
53
80
|
private channelName;
|
|
54
81
|
destroy(): void;
|
|
55
82
|
private handleRealtimeMessage;
|
|
83
|
+
private handlePrivateMessage;
|
|
84
|
+
private handleConversationMessage;
|
|
56
85
|
private flushOfflineQueue;
|
|
57
86
|
}
|
|
58
87
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { e as ChatEventMap, d as ChatConfig, h as ConnectionStatus, j as CreateConversationOptions, a as ApiResponse, i as Conversation, L as ListConversationsOptions, S as SendMessageOptions, f as ChatMessage, G as GetMessagesOptions, M as MessagesResponse, U as UnreadTotalResponse, n as ReadStatus, C as ChannelSettings, c as ChannelWithSettings, k as CreateEphemeralChannelOptions, l as CreateLargeRoomOptions } from './types-FQcFhbJx.cjs';
|
|
2
2
|
|
|
3
3
|
type Listener<T> = (data: T) => void;
|
|
4
4
|
declare class EventEmitter<EventMap extends Record<string, any>> {
|
|
@@ -31,6 +31,8 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
31
31
|
deleteMessage(messageId: string): Promise<ApiResponse<void>>;
|
|
32
32
|
getCachedMessages(conversationId: string): ChatMessage[];
|
|
33
33
|
addReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
|
|
34
|
+
removeReaction(messageId: string, emoji: string): Promise<ApiResponse<void>>;
|
|
35
|
+
getUnreadTotal(): Promise<ApiResponse<UnreadTotalResponse>>;
|
|
34
36
|
sendTyping(conversationId: string, isTyping?: boolean): Promise<void>;
|
|
35
37
|
markRead(conversationId: string): Promise<void>;
|
|
36
38
|
getReadStatus(conversationId: string): Promise<ApiResponse<{
|
|
@@ -48,11 +50,38 @@ declare class ChatClient extends EventEmitter<ChatEventMap> {
|
|
|
48
50
|
* Large rooms use `conversation:lr:` prefix to skip MySQL in the realtime service.
|
|
49
51
|
*/
|
|
50
52
|
setConversationType(conversationId: string, type: Conversation['conversation_type']): void;
|
|
53
|
+
/**
|
|
54
|
+
* Find an active ephemeral or large_room channel by linked session ID.
|
|
55
|
+
* Returns null (not an error) if no active channel exists.
|
|
56
|
+
*/
|
|
57
|
+
findChannelBySessionId(linkedSessionId: string): Promise<ChannelWithSettings | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Self-join an ephemeral or large_room channel. Idempotent.
|
|
60
|
+
*/
|
|
61
|
+
joinChannel(channelId: string): Promise<ApiResponse<{
|
|
62
|
+
participant_id: string;
|
|
63
|
+
role: string;
|
|
64
|
+
joined_at: string;
|
|
65
|
+
}>>;
|
|
66
|
+
/**
|
|
67
|
+
* Create an ephemeral channel tied to a session (e.g., a video snap).
|
|
68
|
+
*/
|
|
69
|
+
createEphemeralChannel(options: CreateEphemeralChannelOptions): Promise<ApiResponse<ChannelWithSettings>>;
|
|
70
|
+
/**
|
|
71
|
+
* Create a large room channel (high-concurrency, skips MySQL tracking in realtime).
|
|
72
|
+
*/
|
|
73
|
+
createLargeRoom(options: CreateLargeRoomOptions): Promise<ApiResponse<ChannelWithSettings>>;
|
|
74
|
+
/**
|
|
75
|
+
* Get the global concurrent subscriber count for a conversation.
|
|
76
|
+
*/
|
|
77
|
+
getSubscriberCount(conversationId: string): Promise<number>;
|
|
51
78
|
subscribeToConversation(conversationId: string): () => void;
|
|
52
79
|
/** Build channel name with correct prefix based on conversation type. */
|
|
53
80
|
private channelName;
|
|
54
81
|
destroy(): void;
|
|
55
82
|
private handleRealtimeMessage;
|
|
83
|
+
private handlePrivateMessage;
|
|
84
|
+
private handleConversationMessage;
|
|
56
85
|
private flushOfflineQueue;
|
|
57
86
|
}
|
|
58
87
|
|
|
@@ -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 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());})();
|
|
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 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 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:)?/,"");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={...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}`;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;if(s==="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})}}handleConversationMessage(e,t){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}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());})();
|
package/dist/chat.umd.global.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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=`
|
|
1
|
+
"use strict";var ScaleMuleChat=(()=>{var b=Object.defineProperty;var S=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var R=(c,n)=>{for(var e in n)b(c,e,{get:n[e],enumerable:!0})},x=(c,n,e,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of M(n))!A.call(c,s)&&s!==e&&b(c,s,{get:()=>n[s],enumerable:!(t=S(n,s))||t.enumerable});return c};var P=c=>x(b({},"__esModule",{value:!0}),c);var H={};R(H,{ChatClient:()=>d,create:()=>K,version:()=>W});var u=class{constructor(){this.listeners=new Map}on(n,e){return this.listeners.has(n)||this.listeners.set(n,new Set),this.listeners.get(n).add(e),()=>this.off(n,e)}off(n,e){this.listeners.get(n)?.delete(e)}once(n,e){let t=(s=>{this.off(n,t),e(s)});return this.on(n,t)}emit(n,...[e]){let t=this.listeners.get(n);if(t)for(let s of t)try{s(e)}catch(i){console.error(`[ScaleMuleChat] Error in ${String(n)} listener:`,i)}}removeAllListeners(n){n?this.listeners.delete(n):this.listeners.clear()}};var 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 _="scalemule_chat_offline_queue",g=class{constructor(n=!0){this.queue=[];this.enabled=n,this.enabled&&this.load()}enqueue(n,e,t="text",s){this.enabled&&(this.queue.push({conversationId:n,content:e,message_type:t,attachments:s,timestamp:Date.now()}),this.save())}drain(){let n=[...this.queue];return this.queue=[],this.save(),n}get size(){return this.queue.length}save(){try{typeof localStorage<"u"&&localStorage.setItem(_,JSON.stringify(this.queue))}catch{}}load(){try{if(typeof localStorage<"u"){let n=localStorage.getItem(_);n&&(this.queue=JSON.parse(n))}}catch{this.queue=[]}}};var m=class{constructor(n){this.baseUrl=n.baseUrl.replace(/\/$/,""),this.apiKey=n.apiKey,this.getToken=n.getToken,this.timeout=n.timeout??1e4}async get(n){return this.request("GET",n)}async post(n,e){return this.request("POST",n,e)}async patch(n,e){return this.request("PATCH",n,e)}async del(n){return this.request("DELETE",n)}async request(n,e,t){let s={"Content-Type":"application/json"};if(this.apiKey&&(s["x-api-key"]=this.apiKey),this.getToken){let r=await this.getToken();r&&(s.Authorization=`Bearer ${r}`)}let i=new AbortController,o=setTimeout(()=>i.abort(),this.timeout);try{let r=await fetch(`${this.baseUrl}${e}`,{method:n,headers:s,body:t?JSON.stringify(t):void 0,signal:i.signal,credentials:"include"});if(clearTimeout(o),r.status===204)return{data:null,error:null};let a=await r.json().catch(()=>null);return r.ok?{data:a?.data!==void 0?a.data:a,error:null}:{data:null,error:{code:a?.error?.code??a?.code??"unknown",message:a?.error?.message??a?.message??r.statusText,status:r.status,details:a?.error?.details??a?.details}}}catch(r){return clearTimeout(o),{data:null,error:{code:"network_error",message:r instanceof Error?r.message:"Network error",status:0}}}}};var v=class extends u{constructor(e){super();this.ws=null;this.status="disconnected";this.subscriptions=new Set;this.presenceChannels=new Map;this.reconnectAttempt=0;this.reconnectTimer=null;this.heartbeatTimer=null;this.config=e,this.maxRetries=e.reconnect?.maxRetries??1/0,this.baseDelay=e.reconnect?.baseDelay??1e3,this.maxDelay=e.reconnect?.maxDelay??3e4;let t=e.baseUrl.replace(/\/$/,"");this.ticketUrl=`${t}/v1/realtime/ws/ticket`,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 d=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={...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}`;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;if(s==="new_message"){let o=i.conversation_id,r=i.id??i.message_id,a=i.sender_id,h=i.content??"";o&&this.emit("inbox:update",{conversationId:o,messageId:r,senderId:a,preview:h})}}handleConversationMessage(e,t){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 h of a)this.emit("typing",{userId:h,conversationId:s});break}case"messages_read":{let a=r.user_id,h=r.last_read_at;a&&h&&this.emit("read",{userId:a,conversationId:s,lastReadAt:h});break}case"room_upgraded":{if(r.new_type==="large_room"){this.conversationTypes.set(s,"large_room");let h=this.conversationSubs.get(s);h&&(h(),this.conversationSubs.delete(s),this.subscribeToConversation(s)),this.emit("room_upgraded",{conversationId:s,newType:"large_room"})}break}}}async flushOfflineQueue(){let e=this.offlineQueue.drain();for(let t of e)await this.sendMessage(t.conversationId,{content:t.content,message_type:t.message_type})}};var w="0.0.1";var f={create(c){return new d(c)},version:w};var C=class extends HTMLElement{constructor(){super();this.client=null;this.unsub=null;this.shadow=this.attachShadow({mode:"open"})}static get observedAttributes(){return["api-key","conversation-id","api-base-url","embed-token"]}connectedCallback(){this.initialize()}disconnectedCallback(){this.cleanup()}attributeChangedCallback(){this.cleanup(),this.initialize()}initialize(){let e=this.getAttribute("api-key")??void 0,t=this.getAttribute("conversation-id"),s=this.getAttribute("api-base-url")??void 0,i=this.getAttribute("embed-token")??void 0;if(!e&&!i)return;let o={apiKey:e,embedToken:i,apiBaseUrl:s};this.client=new d(o),this.shadow.innerHTML=`
|
|
2
2
|
<style>
|
|
3
3
|
:host { display: block; width: 100%; height: 100%; }
|
|
4
4
|
.chat-container { width: 100%; height: 100%; display: flex; flex-direction: column; font-family: system-ui, sans-serif; }
|
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
<button id="send">Send</button>
|
|
16
16
|
</div>
|
|
17
17
|
</div>
|
|
18
|
-
`;let r=this.shadow.getElementById("messages"),a=this.shadow.getElementById("input"),
|
|
18
|
+
`;let r=this.shadow.getElementById("messages"),a=this.shadow.getElementById("input"),h=this.shadow.getElementById("send");this.client.on("message",({message:p})=>{this.appendMessage(r,p),this.dispatchEvent(new CustomEvent("chat-message",{detail:p,composed:!0,bubbles:!0}))});let y=()=>{let p=a.value.trim();!p||!t||!this.client||(this.client.sendMessage(t,{content:p}),a.value="")};h.addEventListener("click",y),a.addEventListener("keydown",p=>{p.key==="Enter"&&y()});let k=this.client;t&&(this.client.getConversation(t).then(()=>{this.client===k&&(this.unsub=this.client.subscribeToConversation(t),this.client.connect())}),this.client.getMessages(t).then(p=>{if(p.data?.messages)for(let E of p.data.messages)this.appendMessage(r,E)}))}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 K=f.create.bind(f),W=f.version;return P(H);})();
|
|
@@ -521,7 +521,11 @@ var ChatClient = class extends EventEmitter {
|
|
|
521
521
|
}
|
|
522
522
|
// ============ Conversations ============
|
|
523
523
|
async createConversation(options) {
|
|
524
|
-
const
|
|
524
|
+
const body = {
|
|
525
|
+
...options,
|
|
526
|
+
conversation_type: options.conversation_type ?? "direct"
|
|
527
|
+
};
|
|
528
|
+
const result = await this.http.post("/v1/chat/conversations", body);
|
|
525
529
|
if (result.data) this.trackConversationType(result.data);
|
|
526
530
|
return result;
|
|
527
531
|
}
|
|
@@ -529,6 +533,7 @@ var ChatClient = class extends EventEmitter {
|
|
|
529
533
|
const params = new URLSearchParams();
|
|
530
534
|
if (options?.page) params.set("page", String(options.page));
|
|
531
535
|
if (options?.per_page) params.set("per_page", String(options.per_page));
|
|
536
|
+
if (options?.conversation_type) params.set("conversation_type", options.conversation_type);
|
|
532
537
|
const qs = params.toString();
|
|
533
538
|
const result = await this.http.get(`/v1/chat/conversations${qs ? "?" + qs : ""}`);
|
|
534
539
|
if (result.data) result.data.forEach((c) => this.trackConversationType(c));
|
|
@@ -571,6 +576,9 @@ var ChatClient = class extends EventEmitter {
|
|
|
571
576
|
`/v1/chat/conversations/${conversationId}/messages${qs ? "?" + qs : ""}`
|
|
572
577
|
);
|
|
573
578
|
if (result.data?.messages) {
|
|
579
|
+
if (!options?.after) {
|
|
580
|
+
result.data.messages = result.data.messages.slice().reverse();
|
|
581
|
+
}
|
|
574
582
|
if (!options?.before && !options?.after) {
|
|
575
583
|
this.cache.setMessages(conversationId, result.data.messages);
|
|
576
584
|
}
|
|
@@ -590,6 +598,13 @@ var ChatClient = class extends EventEmitter {
|
|
|
590
598
|
async addReaction(messageId, emoji) {
|
|
591
599
|
return this.http.post(`/v1/chat/messages/${messageId}/reactions`, { emoji });
|
|
592
600
|
}
|
|
601
|
+
async removeReaction(messageId, emoji) {
|
|
602
|
+
return this.http.del(`/v1/chat/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`);
|
|
603
|
+
}
|
|
604
|
+
// ============ Unread Count ============
|
|
605
|
+
async getUnreadTotal() {
|
|
606
|
+
return this.http.get("/v1/chat/conversations/unread-total");
|
|
607
|
+
}
|
|
593
608
|
// ============ Typing & Read Receipts ============
|
|
594
609
|
async sendTyping(conversationId, isTyping = true) {
|
|
595
610
|
await this.http.post(`/v1/chat/conversations/${conversationId}/typing`, { is_typing: isTyping });
|
|
@@ -634,6 +649,54 @@ var ChatClient = class extends EventEmitter {
|
|
|
634
649
|
setConversationType(conversationId, type) {
|
|
635
650
|
this.conversationTypes.set(conversationId, type);
|
|
636
651
|
}
|
|
652
|
+
// ============ Channel Methods ============
|
|
653
|
+
/**
|
|
654
|
+
* Find an active ephemeral or large_room channel by linked session ID.
|
|
655
|
+
* Returns null (not an error) if no active channel exists.
|
|
656
|
+
*/
|
|
657
|
+
async findChannelBySessionId(linkedSessionId) {
|
|
658
|
+
const result = await this.http.get(
|
|
659
|
+
`/v1/chat/channels/by-session?linked_session_id=${encodeURIComponent(linkedSessionId)}`
|
|
660
|
+
);
|
|
661
|
+
if (result.error?.status === 404) return null;
|
|
662
|
+
if (result.data) {
|
|
663
|
+
this.conversationTypes.set(result.data.id, result.data.channel_type);
|
|
664
|
+
}
|
|
665
|
+
return result.data;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Self-join an ephemeral or large_room channel. Idempotent.
|
|
669
|
+
*/
|
|
670
|
+
async joinChannel(channelId) {
|
|
671
|
+
return this.http.post(`/v1/chat/channels/${channelId}/join`, {});
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Create an ephemeral channel tied to a session (e.g., a video snap).
|
|
675
|
+
*/
|
|
676
|
+
async createEphemeralChannel(options) {
|
|
677
|
+
const result = await this.http.post("/v1/chat/channels/ephemeral", options);
|
|
678
|
+
if (result.data) {
|
|
679
|
+
this.conversationTypes.set(result.data.id, "ephemeral");
|
|
680
|
+
}
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Create a large room channel (high-concurrency, skips MySQL tracking in realtime).
|
|
685
|
+
*/
|
|
686
|
+
async createLargeRoom(options) {
|
|
687
|
+
const result = await this.http.post("/v1/chat/channels/large-room", options);
|
|
688
|
+
if (result.data) {
|
|
689
|
+
this.conversationTypes.set(result.data.id, "large_room");
|
|
690
|
+
}
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Get the global concurrent subscriber count for a conversation.
|
|
695
|
+
*/
|
|
696
|
+
async getSubscriberCount(conversationId) {
|
|
697
|
+
const result = await this.http.get(`/v1/chat/conversations/${conversationId}/subscriber-count`);
|
|
698
|
+
return result.data?.count ?? 0;
|
|
699
|
+
}
|
|
637
700
|
// ============ Realtime Subscriptions ============
|
|
638
701
|
subscribeToConversation(conversationId) {
|
|
639
702
|
if (this.conversationSubs.has(conversationId)) {
|
|
@@ -667,7 +730,34 @@ var ChatClient = class extends EventEmitter {
|
|
|
667
730
|
}
|
|
668
731
|
// ============ Private ============
|
|
669
732
|
handleRealtimeMessage(channel, data) {
|
|
670
|
-
if (
|
|
733
|
+
if (channel.startsWith("conversation:")) {
|
|
734
|
+
this.handleConversationMessage(channel, data);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (channel.startsWith("private:")) {
|
|
738
|
+
this.handlePrivateMessage(data);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
handlePrivateMessage(data) {
|
|
743
|
+
const raw = data;
|
|
744
|
+
if (!raw) return;
|
|
745
|
+
const event = raw.event ?? raw.type;
|
|
746
|
+
const payload = raw.data ?? raw;
|
|
747
|
+
switch (event) {
|
|
748
|
+
case "new_message": {
|
|
749
|
+
const conversationId = payload.conversation_id;
|
|
750
|
+
const messageId = payload.id ?? payload.message_id;
|
|
751
|
+
const senderId = payload.sender_id;
|
|
752
|
+
const preview = payload.content ?? "";
|
|
753
|
+
if (conversationId) {
|
|
754
|
+
this.emit("inbox:update", { conversationId, messageId, senderId, preview });
|
|
755
|
+
}
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
handleConversationMessage(channel, data) {
|
|
671
761
|
const conversationId = channel.replace(/^conversation:(?:lr:|bc:)?/, "");
|
|
672
762
|
const raw = data;
|
|
673
763
|
if (!raw) return;
|
|
@@ -723,6 +813,20 @@ var ChatClient = class extends EventEmitter {
|
|
|
723
813
|
}
|
|
724
814
|
break;
|
|
725
815
|
}
|
|
816
|
+
case "room_upgraded": {
|
|
817
|
+
const newType = payload.new_type;
|
|
818
|
+
if (newType === "large_room") {
|
|
819
|
+
this.conversationTypes.set(conversationId, "large_room");
|
|
820
|
+
const oldUnsub = this.conversationSubs.get(conversationId);
|
|
821
|
+
if (oldUnsub) {
|
|
822
|
+
oldUnsub();
|
|
823
|
+
this.conversationSubs.delete(conversationId);
|
|
824
|
+
this.subscribeToConversation(conversationId);
|
|
825
|
+
}
|
|
826
|
+
this.emit("room_upgraded", { conversationId, newType: "large_room" });
|
|
827
|
+
}
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
726
830
|
}
|
|
727
831
|
}
|
|
728
832
|
async flushOfflineQueue() {
|
|
@@ -523,7 +523,11 @@ var ChatClient = class extends EventEmitter {
|
|
|
523
523
|
}
|
|
524
524
|
// ============ Conversations ============
|
|
525
525
|
async createConversation(options) {
|
|
526
|
-
const
|
|
526
|
+
const body = {
|
|
527
|
+
...options,
|
|
528
|
+
conversation_type: options.conversation_type ?? "direct"
|
|
529
|
+
};
|
|
530
|
+
const result = await this.http.post("/v1/chat/conversations", body);
|
|
527
531
|
if (result.data) this.trackConversationType(result.data);
|
|
528
532
|
return result;
|
|
529
533
|
}
|
|
@@ -531,6 +535,7 @@ var ChatClient = class extends EventEmitter {
|
|
|
531
535
|
const params = new URLSearchParams();
|
|
532
536
|
if (options?.page) params.set("page", String(options.page));
|
|
533
537
|
if (options?.per_page) params.set("per_page", String(options.per_page));
|
|
538
|
+
if (options?.conversation_type) params.set("conversation_type", options.conversation_type);
|
|
534
539
|
const qs = params.toString();
|
|
535
540
|
const result = await this.http.get(`/v1/chat/conversations${qs ? "?" + qs : ""}`);
|
|
536
541
|
if (result.data) result.data.forEach((c) => this.trackConversationType(c));
|
|
@@ -573,6 +578,9 @@ var ChatClient = class extends EventEmitter {
|
|
|
573
578
|
`/v1/chat/conversations/${conversationId}/messages${qs ? "?" + qs : ""}`
|
|
574
579
|
);
|
|
575
580
|
if (result.data?.messages) {
|
|
581
|
+
if (!options?.after) {
|
|
582
|
+
result.data.messages = result.data.messages.slice().reverse();
|
|
583
|
+
}
|
|
576
584
|
if (!options?.before && !options?.after) {
|
|
577
585
|
this.cache.setMessages(conversationId, result.data.messages);
|
|
578
586
|
}
|
|
@@ -592,6 +600,13 @@ var ChatClient = class extends EventEmitter {
|
|
|
592
600
|
async addReaction(messageId, emoji) {
|
|
593
601
|
return this.http.post(`/v1/chat/messages/${messageId}/reactions`, { emoji });
|
|
594
602
|
}
|
|
603
|
+
async removeReaction(messageId, emoji) {
|
|
604
|
+
return this.http.del(`/v1/chat/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`);
|
|
605
|
+
}
|
|
606
|
+
// ============ Unread Count ============
|
|
607
|
+
async getUnreadTotal() {
|
|
608
|
+
return this.http.get("/v1/chat/conversations/unread-total");
|
|
609
|
+
}
|
|
595
610
|
// ============ Typing & Read Receipts ============
|
|
596
611
|
async sendTyping(conversationId, isTyping = true) {
|
|
597
612
|
await this.http.post(`/v1/chat/conversations/${conversationId}/typing`, { is_typing: isTyping });
|
|
@@ -636,6 +651,54 @@ var ChatClient = class extends EventEmitter {
|
|
|
636
651
|
setConversationType(conversationId, type) {
|
|
637
652
|
this.conversationTypes.set(conversationId, type);
|
|
638
653
|
}
|
|
654
|
+
// ============ Channel Methods ============
|
|
655
|
+
/**
|
|
656
|
+
* Find an active ephemeral or large_room channel by linked session ID.
|
|
657
|
+
* Returns null (not an error) if no active channel exists.
|
|
658
|
+
*/
|
|
659
|
+
async findChannelBySessionId(linkedSessionId) {
|
|
660
|
+
const result = await this.http.get(
|
|
661
|
+
`/v1/chat/channels/by-session?linked_session_id=${encodeURIComponent(linkedSessionId)}`
|
|
662
|
+
);
|
|
663
|
+
if (result.error?.status === 404) return null;
|
|
664
|
+
if (result.data) {
|
|
665
|
+
this.conversationTypes.set(result.data.id, result.data.channel_type);
|
|
666
|
+
}
|
|
667
|
+
return result.data;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Self-join an ephemeral or large_room channel. Idempotent.
|
|
671
|
+
*/
|
|
672
|
+
async joinChannel(channelId) {
|
|
673
|
+
return this.http.post(`/v1/chat/channels/${channelId}/join`, {});
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Create an ephemeral channel tied to a session (e.g., a video snap).
|
|
677
|
+
*/
|
|
678
|
+
async createEphemeralChannel(options) {
|
|
679
|
+
const result = await this.http.post("/v1/chat/channels/ephemeral", options);
|
|
680
|
+
if (result.data) {
|
|
681
|
+
this.conversationTypes.set(result.data.id, "ephemeral");
|
|
682
|
+
}
|
|
683
|
+
return result;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Create a large room channel (high-concurrency, skips MySQL tracking in realtime).
|
|
687
|
+
*/
|
|
688
|
+
async createLargeRoom(options) {
|
|
689
|
+
const result = await this.http.post("/v1/chat/channels/large-room", options);
|
|
690
|
+
if (result.data) {
|
|
691
|
+
this.conversationTypes.set(result.data.id, "large_room");
|
|
692
|
+
}
|
|
693
|
+
return result;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Get the global concurrent subscriber count for a conversation.
|
|
697
|
+
*/
|
|
698
|
+
async getSubscriberCount(conversationId) {
|
|
699
|
+
const result = await this.http.get(`/v1/chat/conversations/${conversationId}/subscriber-count`);
|
|
700
|
+
return result.data?.count ?? 0;
|
|
701
|
+
}
|
|
639
702
|
// ============ Realtime Subscriptions ============
|
|
640
703
|
subscribeToConversation(conversationId) {
|
|
641
704
|
if (this.conversationSubs.has(conversationId)) {
|
|
@@ -669,7 +732,34 @@ var ChatClient = class extends EventEmitter {
|
|
|
669
732
|
}
|
|
670
733
|
// ============ Private ============
|
|
671
734
|
handleRealtimeMessage(channel, data) {
|
|
672
|
-
if (
|
|
735
|
+
if (channel.startsWith("conversation:")) {
|
|
736
|
+
this.handleConversationMessage(channel, data);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (channel.startsWith("private:")) {
|
|
740
|
+
this.handlePrivateMessage(data);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
handlePrivateMessage(data) {
|
|
745
|
+
const raw = data;
|
|
746
|
+
if (!raw) return;
|
|
747
|
+
const event = raw.event ?? raw.type;
|
|
748
|
+
const payload = raw.data ?? raw;
|
|
749
|
+
switch (event) {
|
|
750
|
+
case "new_message": {
|
|
751
|
+
const conversationId = payload.conversation_id;
|
|
752
|
+
const messageId = payload.id ?? payload.message_id;
|
|
753
|
+
const senderId = payload.sender_id;
|
|
754
|
+
const preview = payload.content ?? "";
|
|
755
|
+
if (conversationId) {
|
|
756
|
+
this.emit("inbox:update", { conversationId, messageId, senderId, preview });
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
handleConversationMessage(channel, data) {
|
|
673
763
|
const conversationId = channel.replace(/^conversation:(?:lr:|bc:)?/, "");
|
|
674
764
|
const raw = data;
|
|
675
765
|
if (!raw) return;
|
|
@@ -725,6 +815,20 @@ var ChatClient = class extends EventEmitter {
|
|
|
725
815
|
}
|
|
726
816
|
break;
|
|
727
817
|
}
|
|
818
|
+
case "room_upgraded": {
|
|
819
|
+
const newType = payload.new_type;
|
|
820
|
+
if (newType === "large_room") {
|
|
821
|
+
this.conversationTypes.set(conversationId, "large_room");
|
|
822
|
+
const oldUnsub = this.conversationSubs.get(conversationId);
|
|
823
|
+
if (oldUnsub) {
|
|
824
|
+
oldUnsub();
|
|
825
|
+
this.conversationSubs.delete(conversationId);
|
|
826
|
+
this.subscribeToConversation(conversationId);
|
|
827
|
+
}
|
|
828
|
+
this.emit("room_upgraded", { conversationId, newType: "large_room" });
|
|
829
|
+
}
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
728
832
|
}
|
|
729
833
|
}
|
|
730
834
|
async flushOfflineQueue() {
|
package/dist/element.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkWYFNZUAL_cjs = require('./chunk-WYFNZUAL.cjs');
|
|
4
4
|
|
|
5
5
|
// src/element.ts
|
|
6
6
|
var ScaleMuleChatElement = class extends HTMLElement {
|
|
@@ -34,7 +34,7 @@ var ScaleMuleChatElement = class extends HTMLElement {
|
|
|
34
34
|
embedToken,
|
|
35
35
|
apiBaseUrl
|
|
36
36
|
};
|
|
37
|
-
this.client = new
|
|
37
|
+
this.client = new chunkWYFNZUAL_cjs.ChatClient(config);
|
|
38
38
|
this.shadow.innerHTML = `
|
|
39
39
|
<style>
|
|
40
40
|
:host { display: block; width: 100%; height: 100%; }
|
package/dist/element.js
CHANGED
package/dist/iframe.d.cts
CHANGED
package/dist/iframe.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkWYFNZUAL_cjs = require('./chunk-WYFNZUAL.cjs');
|
|
4
4
|
|
|
5
5
|
// src/version.ts
|
|
6
6
|
var CHAT_VERSION = "0.0.1";
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "ChatClient", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkWYFNZUAL_cjs.ChatClient; }
|
|
11
11
|
});
|
|
12
12
|
exports.CHAT_VERSION = CHAT_VERSION;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { C as ChatClient } from './ChatClient-
|
|
2
|
-
export { A as ApiError, a as ApiResponse, b as Attachment, C as ChannelSettings, c as
|
|
1
|
+
export { C as ChatClient } from './ChatClient-lwyQYmNf.cjs';
|
|
2
|
+
export { A as ApiError, a as ApiResponse, b as Attachment, C as ChannelSettings, c as ChannelWithSettings, d as ChatConfig, e as ChatEventMap, f as ChatMessage, g as ChatReaction, h as ConnectionStatus, i as Conversation, j as CreateConversationOptions, k as CreateEphemeralChannelOptions, l as CreateLargeRoomOptions, G as GetMessagesOptions, L as ListConversationsOptions, M as MessagesResponse, P as Participant, m as PresenceMember, R as ReactionSummary, n as ReadStatus, S as SendMessageOptions, U as UnreadTotalResponse } from './types-FQcFhbJx.cjs';
|
|
3
3
|
|
|
4
4
|
declare const CHAT_VERSION = "0.0.1";
|
|
5
5
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { C as ChatClient } from './ChatClient-
|
|
2
|
-
export { A as ApiError, a as ApiResponse, b as Attachment, C as ChannelSettings, c as
|
|
1
|
+
export { C as ChatClient } from './ChatClient-BD0sMudg.js';
|
|
2
|
+
export { A as ApiError, a as ApiResponse, b as Attachment, C as ChannelSettings, c as ChannelWithSettings, d as ChatConfig, e as ChatEventMap, f as ChatMessage, g as ChatReaction, h as ConnectionStatus, i as Conversation, j as CreateConversationOptions, k as CreateEphemeralChannelOptions, l as CreateLargeRoomOptions, G as GetMessagesOptions, L as ListConversationsOptions, M as MessagesResponse, P as Participant, m as PresenceMember, R as ReactionSummary, n as ReadStatus, S as SendMessageOptions, U as UnreadTotalResponse } from './types-FQcFhbJx.js';
|
|
3
3
|
|
|
4
4
|
declare const CHAT_VERSION = "0.0.1";
|
|
5
5
|
|
package/dist/index.js
CHANGED
package/dist/react.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkWYFNZUAL_cjs = require('./chunk-WYFNZUAL.cjs');
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ function useChatContext() {
|
|
|
11
11
|
return ctx;
|
|
12
12
|
}
|
|
13
13
|
function ChatProvider({ config, children }) {
|
|
14
|
-
const [client] = react.useState(() => new
|
|
14
|
+
const [client] = react.useState(() => new chunkWYFNZUAL_cjs.ChatClient(config));
|
|
15
15
|
react.useEffect(() => {
|
|
16
16
|
return () => {
|
|
17
17
|
client.destroy();
|
|
@@ -230,13 +230,82 @@ function useTyping(conversationId) {
|
|
|
230
230
|
);
|
|
231
231
|
return { typingUsers, sendTyping };
|
|
232
232
|
}
|
|
233
|
+
function useConversations(options) {
|
|
234
|
+
const { client } = useChatContext();
|
|
235
|
+
const [conversations, setConversations] = react.useState([]);
|
|
236
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
237
|
+
const refreshTimer = react.useRef(null);
|
|
238
|
+
const fetchConversations = react.useCallback(async () => {
|
|
239
|
+
const result = await client.listConversations({
|
|
240
|
+
conversation_type: options?.conversationType
|
|
241
|
+
});
|
|
242
|
+
if (result.data) {
|
|
243
|
+
setConversations(result.data);
|
|
244
|
+
}
|
|
245
|
+
setIsLoading(false);
|
|
246
|
+
}, [client, options?.conversationType]);
|
|
247
|
+
react.useEffect(() => {
|
|
248
|
+
fetchConversations();
|
|
249
|
+
}, [fetchConversations]);
|
|
250
|
+
react.useEffect(() => {
|
|
251
|
+
return client.on("inbox:update", () => {
|
|
252
|
+
if (refreshTimer.current) clearTimeout(refreshTimer.current);
|
|
253
|
+
refreshTimer.current = setTimeout(() => {
|
|
254
|
+
fetchConversations();
|
|
255
|
+
}, 500);
|
|
256
|
+
});
|
|
257
|
+
}, [client, fetchConversations]);
|
|
258
|
+
react.useEffect(() => {
|
|
259
|
+
return client.on("read", ({ conversationId }) => {
|
|
260
|
+
setConversations(
|
|
261
|
+
(prev) => prev.map((c) => c.id === conversationId ? { ...c, unread_count: 0 } : c)
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
}, [client]);
|
|
265
|
+
react.useEffect(() => {
|
|
266
|
+
return () => {
|
|
267
|
+
if (refreshTimer.current) clearTimeout(refreshTimer.current);
|
|
268
|
+
};
|
|
269
|
+
}, []);
|
|
270
|
+
return { conversations, isLoading, refresh: fetchConversations };
|
|
271
|
+
}
|
|
272
|
+
function useUnreadCount() {
|
|
273
|
+
const { client } = useChatContext();
|
|
274
|
+
const [totalUnread, setTotalUnread] = react.useState(0);
|
|
275
|
+
react.useEffect(() => {
|
|
276
|
+
(async () => {
|
|
277
|
+
const result = await client.getUnreadTotal();
|
|
278
|
+
if (result.data) {
|
|
279
|
+
setTotalUnread(result.data.unread_messages);
|
|
280
|
+
}
|
|
281
|
+
})();
|
|
282
|
+
}, [client]);
|
|
283
|
+
react.useEffect(() => {
|
|
284
|
+
return client.on("inbox:update", () => {
|
|
285
|
+
setTotalUnread((prev) => prev + 1);
|
|
286
|
+
});
|
|
287
|
+
}, [client]);
|
|
288
|
+
react.useEffect(() => {
|
|
289
|
+
return client.on("read", () => {
|
|
290
|
+
(async () => {
|
|
291
|
+
const result = await client.getUnreadTotal();
|
|
292
|
+
if (result.data) {
|
|
293
|
+
setTotalUnread(result.data.unread_messages);
|
|
294
|
+
}
|
|
295
|
+
})();
|
|
296
|
+
});
|
|
297
|
+
}, [client]);
|
|
298
|
+
return { totalUnread };
|
|
299
|
+
}
|
|
233
300
|
|
|
234
301
|
Object.defineProperty(exports, "ChatClient", {
|
|
235
302
|
enumerable: true,
|
|
236
|
-
get: function () { return
|
|
303
|
+
get: function () { return chunkWYFNZUAL_cjs.ChatClient; }
|
|
237
304
|
});
|
|
238
305
|
exports.ChatProvider = ChatProvider;
|
|
239
306
|
exports.useChat = useChat;
|
|
240
307
|
exports.useConnection = useConnection;
|
|
308
|
+
exports.useConversations = useConversations;
|
|
241
309
|
exports.usePresence = usePresence;
|
|
242
310
|
exports.useTyping = useTyping;
|
|
311
|
+
exports.useUnreadCount = useUnreadCount;
|
package/dist/react.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
export {
|
|
4
|
-
export { C as ChatClient } from './ChatClient-
|
|
2
|
+
import { d as ChatConfig, f as ChatMessage, S as SendMessageOptions, a as ApiResponse, h as ConnectionStatus, i as Conversation } from './types-FQcFhbJx.cjs';
|
|
3
|
+
export { G as GetMessagesOptions, L as ListConversationsOptions, M as MessagesResponse, R as ReactionSummary, U as UnreadTotalResponse } from './types-FQcFhbJx.cjs';
|
|
4
|
+
export { C as ChatClient } from './ChatClient-lwyQYmNf.cjs';
|
|
5
5
|
|
|
6
6
|
interface ChatProviderProps {
|
|
7
7
|
config: ChatConfig;
|
|
@@ -33,5 +33,15 @@ declare function useTyping(conversationId?: string): {
|
|
|
33
33
|
typingUsers: string[];
|
|
34
34
|
sendTyping: (isTyping?: boolean) => void;
|
|
35
35
|
};
|
|
36
|
+
declare function useConversations(options?: {
|
|
37
|
+
conversationType?: string;
|
|
38
|
+
}): {
|
|
39
|
+
conversations: Conversation[];
|
|
40
|
+
isLoading: boolean;
|
|
41
|
+
refresh: () => Promise<void>;
|
|
42
|
+
};
|
|
43
|
+
declare function useUnreadCount(): {
|
|
44
|
+
totalUnread: number;
|
|
45
|
+
};
|
|
36
46
|
|
|
37
|
-
export { ApiResponse, ChatConfig, ChatMessage, ChatProvider, ConnectionStatus, SendMessageOptions, useChat, useConnection, usePresence, useTyping };
|
|
47
|
+
export { ApiResponse, ChatConfig, ChatMessage, ChatProvider, ConnectionStatus, Conversation, SendMessageOptions, useChat, useConnection, useConversations, usePresence, useTyping, useUnreadCount };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
export {
|
|
4
|
-
export { C as ChatClient } from './ChatClient-
|
|
2
|
+
import { d as ChatConfig, f as ChatMessage, S as SendMessageOptions, a as ApiResponse, h as ConnectionStatus, i as Conversation } from './types-FQcFhbJx.js';
|
|
3
|
+
export { G as GetMessagesOptions, L as ListConversationsOptions, M as MessagesResponse, R as ReactionSummary, U as UnreadTotalResponse } from './types-FQcFhbJx.js';
|
|
4
|
+
export { C as ChatClient } from './ChatClient-BD0sMudg.js';
|
|
5
5
|
|
|
6
6
|
interface ChatProviderProps {
|
|
7
7
|
config: ChatConfig;
|
|
@@ -33,5 +33,15 @@ declare function useTyping(conversationId?: string): {
|
|
|
33
33
|
typingUsers: string[];
|
|
34
34
|
sendTyping: (isTyping?: boolean) => void;
|
|
35
35
|
};
|
|
36
|
+
declare function useConversations(options?: {
|
|
37
|
+
conversationType?: string;
|
|
38
|
+
}): {
|
|
39
|
+
conversations: Conversation[];
|
|
40
|
+
isLoading: boolean;
|
|
41
|
+
refresh: () => Promise<void>;
|
|
42
|
+
};
|
|
43
|
+
declare function useUnreadCount(): {
|
|
44
|
+
totalUnread: number;
|
|
45
|
+
};
|
|
36
46
|
|
|
37
|
-
export { ApiResponse, ChatConfig, ChatMessage, ChatProvider, ConnectionStatus, SendMessageOptions, useChat, useConnection, usePresence, useTyping };
|
|
47
|
+
export { ApiResponse, ChatConfig, ChatMessage, ChatProvider, ConnectionStatus, Conversation, SendMessageOptions, useChat, useConnection, useConversations, usePresence, useTyping, useUnreadCount };
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ChatClient } from './chunk-
|
|
2
|
-
export { ChatClient } from './chunk-
|
|
1
|
+
import { ChatClient } from './chunk-FXO4ZYQB.js';
|
|
2
|
+
export { ChatClient } from './chunk-FXO4ZYQB.js';
|
|
3
3
|
import { createContext, useState, useEffect, useCallback, useRef, useContext } from 'react';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -229,5 +229,72 @@ function useTyping(conversationId) {
|
|
|
229
229
|
);
|
|
230
230
|
return { typingUsers, sendTyping };
|
|
231
231
|
}
|
|
232
|
+
function useConversations(options) {
|
|
233
|
+
const { client } = useChatContext();
|
|
234
|
+
const [conversations, setConversations] = useState([]);
|
|
235
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
236
|
+
const refreshTimer = useRef(null);
|
|
237
|
+
const fetchConversations = useCallback(async () => {
|
|
238
|
+
const result = await client.listConversations({
|
|
239
|
+
conversation_type: options?.conversationType
|
|
240
|
+
});
|
|
241
|
+
if (result.data) {
|
|
242
|
+
setConversations(result.data);
|
|
243
|
+
}
|
|
244
|
+
setIsLoading(false);
|
|
245
|
+
}, [client, options?.conversationType]);
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
fetchConversations();
|
|
248
|
+
}, [fetchConversations]);
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
return client.on("inbox:update", () => {
|
|
251
|
+
if (refreshTimer.current) clearTimeout(refreshTimer.current);
|
|
252
|
+
refreshTimer.current = setTimeout(() => {
|
|
253
|
+
fetchConversations();
|
|
254
|
+
}, 500);
|
|
255
|
+
});
|
|
256
|
+
}, [client, fetchConversations]);
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
return client.on("read", ({ conversationId }) => {
|
|
259
|
+
setConversations(
|
|
260
|
+
(prev) => prev.map((c) => c.id === conversationId ? { ...c, unread_count: 0 } : c)
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
}, [client]);
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
return () => {
|
|
266
|
+
if (refreshTimer.current) clearTimeout(refreshTimer.current);
|
|
267
|
+
};
|
|
268
|
+
}, []);
|
|
269
|
+
return { conversations, isLoading, refresh: fetchConversations };
|
|
270
|
+
}
|
|
271
|
+
function useUnreadCount() {
|
|
272
|
+
const { client } = useChatContext();
|
|
273
|
+
const [totalUnread, setTotalUnread] = useState(0);
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
(async () => {
|
|
276
|
+
const result = await client.getUnreadTotal();
|
|
277
|
+
if (result.data) {
|
|
278
|
+
setTotalUnread(result.data.unread_messages);
|
|
279
|
+
}
|
|
280
|
+
})();
|
|
281
|
+
}, [client]);
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
return client.on("inbox:update", () => {
|
|
284
|
+
setTotalUnread((prev) => prev + 1);
|
|
285
|
+
});
|
|
286
|
+
}, [client]);
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
return client.on("read", () => {
|
|
289
|
+
(async () => {
|
|
290
|
+
const result = await client.getUnreadTotal();
|
|
291
|
+
if (result.data) {
|
|
292
|
+
setTotalUnread(result.data.unread_messages);
|
|
293
|
+
}
|
|
294
|
+
})();
|
|
295
|
+
});
|
|
296
|
+
}, [client]);
|
|
297
|
+
return { totalUnread };
|
|
298
|
+
}
|
|
232
299
|
|
|
233
|
-
export { ChatProvider, useChat, useConnection, usePresence, useTyping };
|
|
300
|
+
export { ChatProvider, useChat, useConnection, useConversations, usePresence, useTyping, useUnreadCount };
|
|
@@ -36,6 +36,9 @@ interface Conversation {
|
|
|
36
36
|
participant_count?: number;
|
|
37
37
|
last_message_at?: string;
|
|
38
38
|
unread_count?: number;
|
|
39
|
+
last_message_preview?: string;
|
|
40
|
+
last_message_sender_id?: string;
|
|
41
|
+
counterparty_user_id?: string;
|
|
39
42
|
created_at: string;
|
|
40
43
|
participants?: Participant[];
|
|
41
44
|
}
|
|
@@ -52,9 +55,15 @@ interface ChatMessage {
|
|
|
52
55
|
sender_type?: string;
|
|
53
56
|
sender_agent_model?: string;
|
|
54
57
|
attachments?: Attachment[];
|
|
58
|
+
reactions?: ReactionSummary[];
|
|
55
59
|
is_edited: boolean;
|
|
56
60
|
created_at: string;
|
|
57
61
|
}
|
|
62
|
+
interface ReactionSummary {
|
|
63
|
+
emoji: string;
|
|
64
|
+
count: number;
|
|
65
|
+
user_ids: string[];
|
|
66
|
+
}
|
|
58
67
|
interface Attachment {
|
|
59
68
|
file_id: string;
|
|
60
69
|
file_name: string;
|
|
@@ -85,6 +94,26 @@ interface ChannelSettings {
|
|
|
85
94
|
max_participants?: number;
|
|
86
95
|
slow_mode_seconds?: number;
|
|
87
96
|
}
|
|
97
|
+
interface ChannelWithSettings {
|
|
98
|
+
id: string;
|
|
99
|
+
channel_type: string;
|
|
100
|
+
name?: string;
|
|
101
|
+
created_at: string;
|
|
102
|
+
linked_session_id?: string;
|
|
103
|
+
expires_at?: string;
|
|
104
|
+
participant_count: number;
|
|
105
|
+
}
|
|
106
|
+
interface CreateEphemeralChannelOptions {
|
|
107
|
+
linked_session_id: string;
|
|
108
|
+
name?: string;
|
|
109
|
+
ttl_minutes?: number;
|
|
110
|
+
}
|
|
111
|
+
interface CreateLargeRoomOptions {
|
|
112
|
+
name: string;
|
|
113
|
+
linked_session_id?: string;
|
|
114
|
+
max_participants?: number;
|
|
115
|
+
slow_mode_seconds?: number;
|
|
116
|
+
}
|
|
88
117
|
interface ChatEventMap {
|
|
89
118
|
connected: void;
|
|
90
119
|
disconnected: void;
|
|
@@ -139,10 +168,20 @@ interface ChatEventMap {
|
|
|
139
168
|
reaction: ChatReaction;
|
|
140
169
|
conversationId: string;
|
|
141
170
|
};
|
|
171
|
+
'room_upgraded': {
|
|
172
|
+
conversationId: string;
|
|
173
|
+
newType: 'large_room';
|
|
174
|
+
};
|
|
142
175
|
'delivery': {
|
|
143
176
|
messageId: string;
|
|
144
177
|
status: 'sent' | 'delivered' | 'read';
|
|
145
178
|
};
|
|
179
|
+
'inbox:update': {
|
|
180
|
+
conversationId: string;
|
|
181
|
+
messageId: string;
|
|
182
|
+
senderId: string;
|
|
183
|
+
preview: string;
|
|
184
|
+
};
|
|
146
185
|
'error': {
|
|
147
186
|
code: string;
|
|
148
187
|
message: string;
|
|
@@ -157,6 +196,11 @@ interface SendMessageOptions {
|
|
|
157
196
|
interface ListConversationsOptions {
|
|
158
197
|
page?: number;
|
|
159
198
|
per_page?: number;
|
|
199
|
+
conversation_type?: 'direct' | 'group' | 'broadcast' | 'ephemeral' | 'large_room';
|
|
200
|
+
}
|
|
201
|
+
interface UnreadTotalResponse {
|
|
202
|
+
unread_conversations: number;
|
|
203
|
+
unread_messages: number;
|
|
160
204
|
}
|
|
161
205
|
interface GetMessagesOptions {
|
|
162
206
|
limit?: number;
|
|
@@ -175,4 +219,4 @@ interface MessagesResponse {
|
|
|
175
219
|
newest_id?: string;
|
|
176
220
|
}
|
|
177
221
|
|
|
178
|
-
export type { ApiError as A, ChannelSettings as C, GetMessagesOptions as G, ListConversationsOptions as L, MessagesResponse as M, Participant as P,
|
|
222
|
+
export type { ApiError as A, ChannelSettings as C, GetMessagesOptions as G, ListConversationsOptions as L, MessagesResponse as M, Participant as P, ReactionSummary as R, SendMessageOptions as S, UnreadTotalResponse as U, ApiResponse as a, Attachment as b, ChannelWithSettings as c, ChatConfig as d, ChatEventMap as e, ChatMessage as f, ChatReaction as g, ConnectionStatus as h, Conversation as i, CreateConversationOptions as j, CreateEphemeralChannelOptions as k, CreateLargeRoomOptions as l, PresenceMember as m, ReadStatus as n };
|
|
@@ -36,6 +36,9 @@ interface Conversation {
|
|
|
36
36
|
participant_count?: number;
|
|
37
37
|
last_message_at?: string;
|
|
38
38
|
unread_count?: number;
|
|
39
|
+
last_message_preview?: string;
|
|
40
|
+
last_message_sender_id?: string;
|
|
41
|
+
counterparty_user_id?: string;
|
|
39
42
|
created_at: string;
|
|
40
43
|
participants?: Participant[];
|
|
41
44
|
}
|
|
@@ -52,9 +55,15 @@ interface ChatMessage {
|
|
|
52
55
|
sender_type?: string;
|
|
53
56
|
sender_agent_model?: string;
|
|
54
57
|
attachments?: Attachment[];
|
|
58
|
+
reactions?: ReactionSummary[];
|
|
55
59
|
is_edited: boolean;
|
|
56
60
|
created_at: string;
|
|
57
61
|
}
|
|
62
|
+
interface ReactionSummary {
|
|
63
|
+
emoji: string;
|
|
64
|
+
count: number;
|
|
65
|
+
user_ids: string[];
|
|
66
|
+
}
|
|
58
67
|
interface Attachment {
|
|
59
68
|
file_id: string;
|
|
60
69
|
file_name: string;
|
|
@@ -85,6 +94,26 @@ interface ChannelSettings {
|
|
|
85
94
|
max_participants?: number;
|
|
86
95
|
slow_mode_seconds?: number;
|
|
87
96
|
}
|
|
97
|
+
interface ChannelWithSettings {
|
|
98
|
+
id: string;
|
|
99
|
+
channel_type: string;
|
|
100
|
+
name?: string;
|
|
101
|
+
created_at: string;
|
|
102
|
+
linked_session_id?: string;
|
|
103
|
+
expires_at?: string;
|
|
104
|
+
participant_count: number;
|
|
105
|
+
}
|
|
106
|
+
interface CreateEphemeralChannelOptions {
|
|
107
|
+
linked_session_id: string;
|
|
108
|
+
name?: string;
|
|
109
|
+
ttl_minutes?: number;
|
|
110
|
+
}
|
|
111
|
+
interface CreateLargeRoomOptions {
|
|
112
|
+
name: string;
|
|
113
|
+
linked_session_id?: string;
|
|
114
|
+
max_participants?: number;
|
|
115
|
+
slow_mode_seconds?: number;
|
|
116
|
+
}
|
|
88
117
|
interface ChatEventMap {
|
|
89
118
|
connected: void;
|
|
90
119
|
disconnected: void;
|
|
@@ -139,10 +168,20 @@ interface ChatEventMap {
|
|
|
139
168
|
reaction: ChatReaction;
|
|
140
169
|
conversationId: string;
|
|
141
170
|
};
|
|
171
|
+
'room_upgraded': {
|
|
172
|
+
conversationId: string;
|
|
173
|
+
newType: 'large_room';
|
|
174
|
+
};
|
|
142
175
|
'delivery': {
|
|
143
176
|
messageId: string;
|
|
144
177
|
status: 'sent' | 'delivered' | 'read';
|
|
145
178
|
};
|
|
179
|
+
'inbox:update': {
|
|
180
|
+
conversationId: string;
|
|
181
|
+
messageId: string;
|
|
182
|
+
senderId: string;
|
|
183
|
+
preview: string;
|
|
184
|
+
};
|
|
146
185
|
'error': {
|
|
147
186
|
code: string;
|
|
148
187
|
message: string;
|
|
@@ -157,6 +196,11 @@ interface SendMessageOptions {
|
|
|
157
196
|
interface ListConversationsOptions {
|
|
158
197
|
page?: number;
|
|
159
198
|
per_page?: number;
|
|
199
|
+
conversation_type?: 'direct' | 'group' | 'broadcast' | 'ephemeral' | 'large_room';
|
|
200
|
+
}
|
|
201
|
+
interface UnreadTotalResponse {
|
|
202
|
+
unread_conversations: number;
|
|
203
|
+
unread_messages: number;
|
|
160
204
|
}
|
|
161
205
|
interface GetMessagesOptions {
|
|
162
206
|
limit?: number;
|
|
@@ -175,4 +219,4 @@ interface MessagesResponse {
|
|
|
175
219
|
newest_id?: string;
|
|
176
220
|
}
|
|
177
221
|
|
|
178
|
-
export type { ApiError as A, ChannelSettings as C, GetMessagesOptions as G, ListConversationsOptions as L, MessagesResponse as M, Participant as P,
|
|
222
|
+
export type { ApiError as A, ChannelSettings as C, GetMessagesOptions as G, ListConversationsOptions as L, MessagesResponse as M, Participant as P, ReactionSummary as R, SendMessageOptions as S, UnreadTotalResponse as U, ApiResponse as a, Attachment as b, ChannelWithSettings as c, ChatConfig as d, ChatEventMap as e, ChatMessage as f, ChatReaction as g, ConnectionStatus as h, Conversation as i, CreateConversationOptions as j, CreateEphemeralChannelOptions as k, CreateLargeRoomOptions as l, PresenceMember as m, ReadStatus as n };
|