@mademi_dev/chatemi 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ChatEmi={},e.React,e.jsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r={me:`/me`,users:`/users`,user:e=>`/users/${encodeURIComponent(e)}`,conversations:`/conversations`,conversation:e=>`/conversations/${encodeURIComponent(e)}`,conversationAvatar:e=>`/conversations/${encodeURIComponent(e)}/avatar`,members:e=>`/conversations/${encodeURIComponent(e)}/members`,member:(e,t)=>`/conversations/${encodeURIComponent(e)}/members/${encodeURIComponent(t)}`,messages:e=>`/conversations/${encodeURIComponent(e)}/messages`,message:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}`,markRead:e=>`/conversations/${encodeURIComponent(e)}/read`,markDelivered:e=>`/conversations/${encodeURIComponent(e)}/delivered`,forwardMessage:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}/forward`,reactions:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}/reactions`,upload:`/attachments`,search:`/search/messages`},i=class extends Error{status;payload;constructor(e,t,n){super(e),this.name=`ChatEmiApiError`,this.status=t,this.payload=n}},a=class{config;fetcher;constructor(e){this.config=e,this.fetcher=e.fetchImpl??fetch}async getMe(e){return this.request(this.endpoint(`me`),{signal:e})}async searchUsers(e,t){if(this.config.userDirectory){let n=this.config.userDirectory.searchPath??`/users`;return o(await this.request(n,{query:{q:e.query,cursor:e.cursor,limit:e.limit},signal:t,baseUrl:this.config.userDirectory.baseUrl,headers:await this.resolveUserDirectoryHeaders(),authorize:!1}),this.config.userDirectory.mapUser)}return this.request(this.endpoint(`users`),{query:{q:e.query,cursor:e.cursor,limit:e.limit},signal:t})}async getUser(e,t){if(this.config.userDirectory){let n=this.config.userDirectory.userPath?.(e)??`/users/${encodeURIComponent(e)}`,r=await this.request(n,{signal:t,baseUrl:this.config.userDirectory.baseUrl,headers:await this.resolveUserDirectoryHeaders(),authorize:!1});return this.config.userDirectory.mapUser?this.config.userDirectory.mapUser(r):r}return this.request(this.endpoint(`user`,e),{signal:t})}async listConversations(e={},t){return this.request(this.endpoint(`conversations`),{query:{...e},signal:t})}async getConversation(e,t){return this.request(this.endpoint(`conversation`,e),{signal:t})}async createConversation(e){return this.request(this.endpoint(`conversations`),{method:`POST`,body:e})}async createGroup(e){return this.createConversation({...e,type:`group`})}async createChannel(e){return this.createConversation({...e,type:`channel`,readOnly:e.readOnly??!0})}async updateConversation(e,t){return this.request(this.endpoint(`conversation`,e),{method:`PATCH`,body:t})}async archiveConversation(e){await this.request(this.endpoint(`conversation`,e),{method:`DELETE`})}async updateConversationAvatar(e){if(e.conversationId)return this.request(this.endpoint(`conversationAvatar`,e.conversationId),{method:`PATCH`,body:{attachment:e.attachment}});if(!e.userId)throw Error(`ChatEmi avatar update requires conversationId or userId`);return this.request(this.endpoint(`user`,e.userId),{method:`PATCH`,body:{avatar:e.attachment}})}async addMembers(e,t){return this.request(this.endpoint(`members`,e),{method:`POST`,body:{userIds:t}})}async updateMember(e){return this.request(this.endpoint(`member`,e.conversationId,e.userId),{method:`PATCH`,body:e})}async removeMember(e){await this.request(this.endpoint(`member`,e.conversationId,e.userId),{method:`DELETE`})}async listMessages(e,t={},n){return this.request(this.endpoint(`messages`,e),{query:{...t},signal:n})}async sendMessage(e){return this.request(this.endpoint(`messages`,e.conversationId),{method:`POST`,body:e})}async forwardMessage(e){return this.request(this.endpoint(`forwardMessage`,e.sourceConversationId,e.messageId),{method:`POST`,body:e})}async editMessage(e){return this.request(this.endpoint(`message`,e.conversationId,e.messageId),{method:`PATCH`,body:e})}async deleteMessage(e,t){await this.request(this.endpoint(`message`,e,t),{method:`DELETE`})}async markConversationRead(e,t){await this.request(this.endpoint(`markRead`,e),{method:`POST`,body:{messageIds:t}})}async markConversationDelivered(e,t){await this.request(this.endpoint(`markDelivered`,e),{method:`POST`,body:{messageIds:t}})}async addReaction(e,t,n){return this.request(this.endpoint(`reactions`,e,t),{method:`POST`,body:{emoji:n}})}async removeReaction(e,t,n){return this.request(this.endpoint(`reactions`,e,t),{method:`DELETE`,body:{emoji:n}})}async uploadAttachment(e){let t=new FormData;return t.append(`file`,e.file,e.name),e.conversationId&&t.append(`conversationId`,e.conversationId),e.type&&t.append(`type`,e.type),e.metadata&&t.append(`metadata`,JSON.stringify(e.metadata)),this.request(this.endpoint(`upload`),{method:`POST`,body:t})}async searchMessages(e,t={},n){return this.request(this.endpoint(`search`),{query:{q:e,...t},signal:n})}endpoint(e,t,n){switch(e){case`user`:case`conversation`:case`conversationAvatar`:case`members`:case`messages`:case`markRead`:case`markDelivered`:return(this.config.endpoints?.[e]??r[e])(t??``);case`member`:case`message`:case`forwardMessage`:case`reactions`:return(this.config.endpoints?.[e]??r[e])(t??``,n??``);default:return this.config.endpoints?.[e]??r[e]}}async request(e,t={}){let n=this.buildUrl(e,t.query,t.baseUrl),r=new Headers(await this.resolveHeaders(t.headers,t.authorize??!0)),a=typeof FormData<`u`&&t.body instanceof FormData;!a&&t.body!==void 0&&!r.has(`Content-Type`)&&r.set(`Content-Type`,`application/json`);let o=await this.fetcher(n,{method:t.method??`GET`,headers:r,body:a?t.body:t.body===void 0?void 0:JSON.stringify(t.body),signal:t.signal});if(o.status===204)return;let s=await this.parseResponse(o);if(!o.ok)throw new i(this.errorMessage(s,o.status),o.status,s);return s}buildUrl(e,t,n=this.config.apiBaseUrl){let r=/^https?:\/\//i.test(e),i=n.replace(/\/+$/,``),a=e.startsWith(`/`)?e:`/${e}`,o=new URL(r?e:`${i}${a}`);return Object.entries(t??{}).forEach(([e,t])=>{t!=null&&t!==``&&o.searchParams.set(e,String(t))}),o.toString()}async resolveHeaders(e,t=!0){let n=new Headers,r=typeof this.config.headers==`function`?await this.config.headers():this.config.headers,i=typeof this.config.token==`function`?await this.config.token():this.config.token;return new Headers(r).forEach((e,t)=>n.set(t,e)),new Headers(e).forEach((e,t)=>n.set(t,e)),t&&i&&!n.has(`Authorization`)&&n.set(`Authorization`,`Bearer ${i}`),n}async resolveUserDirectoryHeaders(){return(typeof this.config.userDirectory?.headers==`function`?await this.config.userDirectory.headers():this.config.userDirectory?.headers)??{}}async parseResponse(e){return(e.headers.get(`Content-Type`)??``).includes(`application/json`)?e.json():e.text()}errorMessage(e,t){return e&&typeof e==`object`&&`message`in e&&typeof e.message==`string`?e.message:`ChatEmi API request failed with status ${t}`}};function o(e,t){let n=e=>t?t(e):e;return Array.isArray(e)?{items:e.map(n)}:{...e,items:e.items.map(n)}}var s=500,c=8e3,l=1/0,u=class{config;socket;reconnectAttempts=0;shouldReconnect=!0;reconnectTimer;queue=[];listeners=new Map;constructor(e){this.config=e}get readyState(){return this.socket?.readyState}async connect(){this.config.socketUrl&&(this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING||(this.shouldReconnect=this.config.reconnect?.enabled??!0,this.createSocket(await this.buildSocketUrl())))}disconnect(){this.shouldReconnect=!1,this.clearReconnectTimer(),this.socket?.close(),this.socket=void 0}on(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),()=>{n.delete(t),n.size===0&&this.listeners.delete(e)}}send(e,t,n){let r={type:e,payload:t,requestId:n};if(this.socket?.readyState===WebSocket.OPEN){this.socket.send(JSON.stringify(r));return}this.queue.push(r)}subscribeConversation(e){this.send(`conversation.subscribe`,{conversationId:e})}unsubscribeConversation(e){this.send(`conversation.unsubscribe`,{conversationId:e})}sendTyping(e,t){this.send(`typing`,{conversationId:e,isTyping:t})}sendReadReceipt(e,t){this.send(`message.read`,{conversationId:e,messageIds:t})}sendDeliveredReceipt(e,t){this.send(`message.delivered`,{conversationId:e,messageIds:t})}sendForward(e){this.send(`message.forward`,e)}sendMemberUpdate(e){this.send(`conversation.member.update`,e)}sendAvatarUpdate(e){this.send(`conversation.avatar.update`,{conversationId:e})}sendPresence(e){this.send(`presence`,{status:e})}createSocket(e){let t=(this.config.websocketFactory??(e=>new WebSocket(e)))(e);this.socket=t,t.addEventListener(`open`,()=>{this.reconnectAttempts=0,this.dispatch(`connected`,void 0),this.flushQueue()}),t.addEventListener(`close`,e=>{this.dispatch(`disconnected`,e),this.socket=void 0,this.scheduleReconnect()}),t.addEventListener(`error`,e=>{this.dispatch(`error`,e)}),t.addEventListener(`message`,e=>{this.handleMessage(e.data)})}handleMessage(e){try{let t=typeof e==`string`?JSON.parse(e):e;t&&typeof t.type==`string`&&this.dispatchRaw(t.type,t.payload)}catch(e){this.dispatch(`error`,e instanceof Error?e:Error(`Unable to parse ChatEmi socket message`))}}flushQueue(){for(;this.queue.length>0&&this.socket?.readyState===WebSocket.OPEN;){let e=this.queue.shift();e&&this.socket.send(JSON.stringify(e))}}scheduleReconnect(){if(!this.shouldReconnect||!this.config.socketUrl)return;let e=this.config.reconnect?.maxAttempts??l;if(this.reconnectAttempts>=e)return;this.reconnectAttempts+=1;let t=this.config.reconnect?.initialDelayMs??s,n=this.config.reconnect?.maxDelayMs??c,r=Math.min(t*2**(this.reconnectAttempts-1),n);this.dispatch(`reconnecting`,{attempt:this.reconnectAttempts,delay:r}),this.clearReconnectTimer(),this.reconnectTimer=setTimeout(()=>{this.connect()},r)}async buildSocketUrl(){let e=this.config.socketUrl;if(!e)throw Error(`ChatEmi socketUrl is required before connecting the socket`);let t=new URL(e),n=typeof this.config.token==`function`?await this.config.token():this.config.token;return n&&t.searchParams.set(`token`,n),t.toString()}dispatch(e,t){this.listeners.get(e)?.forEach(e=>e(t))}dispatchRaw(e,t){this.listeners.get(e)?.forEach(e=>e(t))}clearReconnectTimer(){this.reconnectTimer&&=(clearTimeout(this.reconnectTimer),void 0)}},d=(0,t.createContext)(void 0);function f(e,t=[],n,r=[]){return{currentUser:e.currentUser,conversations:t,activeConversationId:n??t[0]?.id,messagesByConversation:{},typingByConversation:{},presenceByUser:{},notifications:r,unreadNotificationCount:r.filter(e=>!e.read).length,connectionStatus:`idle`,theme:e.theme??`light`,loading:!1}}function p(e,t){switch(t.type){case`set-loading`:return{...e,loading:t.loading};case`set-error`:return{...e,error:t.error};case`set-connection-status`:return{...e,connectionStatus:t.status};case`set-current-user`:return{...e,currentUser:t.user};case`set-conversations`:return{...e,conversations:b(t.conversations),activeConversationId:e.activeConversationId??t.conversations[0]?.id};case`upsert-conversation`:return{...e,conversations:b(g(e.conversations,t.conversation))};case`remove-conversation`:return{...e,conversations:e.conversations.filter(e=>e.id!==t.conversationId),activeConversationId:e.activeConversationId===t.conversationId?void 0:e.activeConversationId};case`upsert-member`:return{...e,conversations:e.conversations.map(e=>e.id===t.conversationId?{...e,members:_(e.members??[],t.member),participants:g(e.participants,t.member.user)}:e)};case`remove-member`:return{...e,conversations:e.conversations.map(e=>e.id===t.conversationId?{...e,members:(e.members??[]).filter(e=>e.user.id!==t.userId),participants:e.participants.filter(e=>e.id!==t.userId)}:e)};case`set-active-conversation`:return{...e,activeConversationId:t.conversationId};case`set-messages`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:x(t.messages)}};case`upsert-message`:{let n=e.messagesByConversation[t.message.conversationId]??[],r=S(e.conversations,t.message);return{...e,conversations:r,messagesByConversation:{...e.messagesByConversation,[t.message.conversationId]:x(g(n,t.message))}}}case`remove-message`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:(e.messagesByConversation[t.conversationId]??[]).filter(e=>e.id!==t.messageId)}};case`set-message-reactions`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:(e.messagesByConversation[t.conversationId]??[]).map(e=>e.id===t.messageId?{...e,reactions:t.reactions}:e)}};case`apply-receipt`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.receipt.conversationId]:(e.messagesByConversation[t.receipt.conversationId]??[]).map(e=>t.receipt.messageIds.includes(e.id)?v(e,t.receipt):e)}};case`add-notification`:{let n=(e.notifications.some(e=>e.id===t.notification.id)?e.notifications.map(e=>e.id===t.notification.id?t.notification:e):[t.notification,...e.notifications]).slice(0,t.maxStored??50);return{...e,notifications:n,unreadNotificationCount:n.filter(e=>!e.read).length}}case`dismiss-notification`:{let n=e.notifications.filter(e=>e.id!==t.notificationId);return{...e,notifications:n,unreadNotificationCount:n.filter(e=>!e.read).length}}case`mark-notifications-read`:{let n=t.notificationIds?new Set(t.notificationIds):void 0,r=e.notifications.map(e=>!n||n.has(e.id)?{...e,read:!0}:e);return{...e,notifications:r,unreadNotificationCount:r.filter(e=>!e.read).length}}case`clear-notifications`:return{...e,notifications:[],unreadNotificationCount:0};case`set-typing`:{let n=(e.typingByConversation[t.event.conversationId]??[]).filter(e=>e.user.id!==t.event.user.id);return{...e,typingByConversation:{...e.typingByConversation,[t.event.conversationId]:t.event.isTyping?[...n,t.event]:n}}}case`set-presence`:return{...e,presenceByUser:{...e.presenceByUser,[t.userId]:{userId:t.userId,status:t.status,lastSeenAt:t.lastSeenAt}}};default:return e}}function m({children:e,config:r,autoConnect:i=!0,initialConversations:o,initialActiveConversationId:s,initialNotifications:c}){let l=(0,t.useMemo)(()=>new a(r),[r]),m=(0,t.useMemo)(()=>new u(r),[r]),h=(0,t.useRef)({activeConversationId:s,currentUserId:r.currentUser?.id}),g=(0,t.useRef)(new Set),[_,v]=(0,t.useReducer)(p,f(r,o,s,c));(0,t.useEffect)(()=>{h.current={activeConversationId:_.activeConversationId,currentUserId:_.currentUser?.id}},[_.activeConversationId,_.currentUser?.id]),(0,t.useEffect)(()=>{let e=!0,t=new AbortController;v({type:`set-current-user`,user:r.currentUser});async function n(){v({type:`set-loading`,loading:!0}),v({type:`set-error`,error:void 0});try{let[n,i]=await Promise.all([r.currentUser?Promise.resolve(r.currentUser):l.getMe(t.signal).catch(()=>void 0),l.listConversations({},t.signal)]);if(!e)return;v({type:`set-current-user`,user:n}),v({type:`set-conversations`,conversations:i.items})}catch(t){e&&v({type:`set-error`,error:w(t)})}finally{e&&v({type:`set-loading`,loading:!1})}}return n(),()=>{e=!1,t.abort()}},[l,r.currentUser]),(0,t.useEffect)(()=>{let e=[m.on(`connected`,()=>v({type:`set-connection-status`,status:`connected`})),m.on(`disconnected`,()=>v({type:`set-connection-status`,status:`disconnected`})),m.on(`reconnecting`,()=>v({type:`set-connection-status`,status:`reconnecting`})),m.on(`error`,e=>{v({type:`set-connection-status`,status:`error`}),v({type:`set-error`,error:w(e)})}),m.on(`conversation.created`,e=>v({type:`upsert-conversation`,conversation:e})),m.on(`conversation.updated`,e=>v({type:`upsert-conversation`,conversation:e})),m.on(`conversation.deleted`,({conversationId:e})=>v({type:`remove-conversation`,conversationId:e})),m.on(`conversation.member.added`,({conversationId:e,member:t})=>v({type:`upsert-member`,conversationId:e,member:t})),m.on(`conversation.member.updated`,({conversationId:e,member:t})=>v({type:`upsert-member`,conversationId:e,member:t})),m.on(`conversation.member.removed`,({conversationId:e,userId:t})=>v({type:`remove-member`,conversationId:e,userId:t})),m.on(`message.created`,e=>{v({type:`upsert-message`,message:e}),e.sender.id!==h.current.currentUserId&&v({type:`add-notification`,notification:C(e),maxStored:r.notifications?.maxStored})}),m.on(`message.updated`,e=>v({type:`upsert-message`,message:e})),m.on(`message.deleted`,({conversationId:e,messageId:t})=>v({type:`remove-message`,conversationId:e,messageId:t})),m.on(`message.receipt`,e=>v({type:`apply-receipt`,receipt:e})),m.on(`message.reaction`,({conversationId:e,messageId:t,reactions:n})=>v({type:`set-message-reactions`,conversationId:e,messageId:t,reactions:n})),m.on(`typing`,e=>v({type:`set-typing`,event:e})),m.on(`presence`,({userId:e,status:t,lastSeenAt:n})=>v({type:`set-presence`,userId:e,status:t,lastSeenAt:n})),m.on(`notification`,e=>v({type:`add-notification`,notification:e,maxStored:r.notifications?.maxStored}))];return i&&r.socketUrl&&(v({type:`set-connection-status`,status:`connecting`}),m.connect()),()=>{e.forEach(e=>e()),m.disconnect()}},[i,r.notifications?.maxStored,r.socketUrl,m]),(0,t.useEffect)(()=>{!r.notifications?.enabled||!r.notifications.browser||typeof window>`u`||!(`Notification`in window)||window.Notification.permission!==`granted`||(r.notifications.showWhenOpen||typeof document>`u`||document.hidden)&&_.notifications.forEach(e=>{e.read||g.current.has(e.id)||(g.current.add(e.id),new window.Notification(e.title||r.notifications?.title||`New message`,{body:e.body,icon:e.avatarUrl}))})},[r.notifications,_.notifications]);let y=(0,t.useCallback)(async(e={})=>{v({type:`set-loading`,loading:!0});try{let t=await l.listConversations(e);return v({type:`set-conversations`,conversations:t.items}),t.items}finally{v({type:`set-loading`,loading:!1})}},[l]),b=(0,t.useCallback)(async(e,t={})=>{v({type:`set-active-conversation`,conversationId:e}),m.subscribeConversation(e);let n=await l.listMessages(e,t);return v({type:`set-messages`,conversationId:e,messages:n.items}),l.markConversationDelivered(e,n.items.filter(e=>e.sender.id!==_.currentUser?.id).map(e=>e.id)),n.items},[l,m,_.currentUser?.id]),x=(0,t.useCallback)(async e=>{let t=await l.createConversation(e);return v({type:`upsert-conversation`,conversation:t}),v({type:`set-active-conversation`,conversationId:t.id}),t},[l]),S=(0,t.useCallback)(async e=>{let t=`temp-${Date.now()}`,n=_.currentUser?{id:t,conversationId:e.conversationId,sender:_.currentUser,text:e.text,html:e.html,attachments:e.attachments,kind:e.kind,replyToId:e.replyToId,status:`sending`,createdAt:new Date().toISOString(),metadata:e.metadata}:void 0;n&&v({type:`upsert-message`,message:n});try{let r=await l.sendMessage(e);return n&&v({type:`remove-message`,conversationId:e.conversationId,messageId:t}),v({type:`upsert-message`,message:r}),r}catch(e){throw n&&v({type:`upsert-message`,message:{...n,status:`failed`}}),e}},[l,_.currentUser]),T=(0,t.useCallback)(async e=>{let t=await l.editMessage(e);return v({type:`upsert-message`,message:t}),t},[l]),E=(0,t.useCallback)(async(e,t)=>{await l.deleteMessage(e,t),v({type:`remove-message`,conversationId:e,messageId:t})},[l]),D=(0,t.useCallback)(async(e,t)=>{await l.markConversationRead(e,t),m.sendReadReceipt(e,t??[])},[l,m]),O=(0,t.useCallback)(async(e,t)=>{await l.markConversationDelivered(e,t),m.sendDeliveredReceipt(e,t??[])},[l,m]),k=(0,t.useCallback)(async e=>{let t=await l.forwardMessage(e);return m.sendForward(e),v({type:`upsert-message`,message:t}),t},[l,m]),A=(0,t.useCallback)(async(e,t,n)=>{let r=await l.addReaction(e,t,n);return v({type:`upsert-message`,message:r}),r},[l]),j=(0,t.useCallback)(async(e,t,n)=>{let r=await l.removeReaction(e,t,n);return v({type:`upsert-message`,message:r}),r},[l]),M=(0,t.useCallback)(e=>l.uploadAttachment(e),[l]),N=(0,t.useCallback)(async e=>{let t=await l.updateConversationAvatar(e);return`participants`in t?(v({type:`upsert-conversation`,conversation:t}),m.sendAvatarUpdate(t.id)):v({type:`set-current-user`,user:_.currentUser?.id===t.id?t:_.currentUser}),t},[l,m,_.currentUser]),P=(0,t.useCallback)(async(e,t)=>{let n=await l.addMembers(e,t);return n.forEach(t=>v({type:`upsert-member`,conversationId:e,member:t})),n},[l]),F=(0,t.useCallback)(async e=>{let t=await l.updateMember(e);return v({type:`upsert-member`,conversationId:e.conversationId,member:t}),m.sendMemberUpdate(e),t},[l,m]),I=(0,t.useCallback)(async e=>{await l.removeMember(e),v({type:`remove-member`,conversationId:e.conversationId,userId:e.userId})},[l]),L=(0,t.useCallback)(async e=>(await l.searchUsers(e)).items,[l]),R=(0,t.useCallback)(async(e,t={})=>(await l.searchMessages(e,t)).items,[l]),z=(0,t.useCallback)(e=>m.sendTyping(e,!0),[m]),B=(0,t.useCallback)(e=>m.sendTyping(e,!1),[m]),V=(0,t.useCallback)(e=>m.sendPresence(e),[m]),H=(0,t.useCallback)(e=>{v({type:`dismiss-notification`,notificationId:e})},[]),U=(0,t.useCallback)(e=>{v({type:`mark-notifications-read`,notificationIds:e})},[]),W=(0,t.useCallback)(()=>{v({type:`clear-notifications`})},[]),G=(0,t.useCallback)(async()=>typeof window>`u`||!(`Notification`in window)?`unsupported`:window.Notification.permission===`granted`||window.Notification.permission===`denied`?window.Notification.permission:window.Notification.requestPermission(),[]),K=(0,t.useCallback)(()=>m.connect(),[m]),q=(0,t.useCallback)(()=>m.disconnect(),[m]),J=(0,t.useMemo)(()=>({refreshConversations:y,openConversation:b,createConversation:x,sendMessage:S,editMessage:T,deleteMessage:E,markRead:D,markDelivered:O,forwardMessage:k,addReaction:A,removeReaction:j,uploadAttachment:M,updateAvatar:N,addMembers:P,updateMember:F,removeMember:I,searchUsers:L,searchMessages:R,startTyping:z,stopTyping:B,setPresence:V,dismissNotification:H,markNotificationsRead:U,clearNotifications:W,requestNotificationPermission:G,connect:K,disconnect:q}),[y,b,x,S,T,E,D,O,k,A,j,M,N,P,F,I,L,R,z,B,V,H,U,W,G,K,q]),Y=_.conversations.find(e=>e.id===_.activeConversationId),X=_.activeConversationId?_.messagesByConversation[_.activeConversationId]??[]:[],Z=(0,t.useMemo)(()=>({..._,api:l,socket:m,activeConversation:Y,activeMessages:X,actions:J}),[J,Y,X,l,m,_]);return(0,n.jsx)(d.Provider,{value:Z,children:e})}function h(){let e=(0,t.useContext)(d);if(!e)throw Error(`useChatEmi must be used inside a ChatEmiProvider`);return e}function g(e,t){let n=e.findIndex(e=>e.id===t.id);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function _(e,t){let n=e.findIndex(e=>e.user.id===t.user.id);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function v(e,t){let n=t.status===`read`?`readBy`:`deliveredTo`,r=y(e[n]??[],t);return{...e,[n]:r,status:t.status===`read`||e.status===`read`?`read`:`delivered`}}function y(e,t){let n=e.findIndex(e=>e.userId===t.userId&&e.status===t.status);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function b(e){return[...e].sort((e,t)=>{let n=e.lastMessage?.createdAt??e.updatedAt??e.createdAt,r=t.lastMessage?.createdAt??t.updatedAt??t.createdAt;return Date.parse(r)-Date.parse(n)})}function x(e){return[...e].sort((e,t)=>Date.parse(e.createdAt)-Date.parse(t.createdAt))}function S(e,t){return b(e.map(e=>e.id===t.conversationId?{...e,lastMessage:t,updatedAt:t.createdAt}:e))}function C(e){return{id:`message:${e.id}`,kind:`message`,title:e.sender.name,body:e.text??e.attachments?.[0]?.name??`Sent a message`,conversationId:e.conversationId,messageId:e.id,actor:e.sender,avatarUrl:e.sender.avatarUrl,read:!1,createdAt:e.createdAt,metadata:e.metadata}}function w(e){return e instanceof Error?e.message:`Unexpected ChatEmi error`}function T({className:e,emptyState:r,composerPlaceholder:i=`Write a message...`,showSidebar:a=!0,theme:o,enableAdminControls:s=!0,enableMessageActions:c=!0,enableMediaPreview:l=!0,renderConversation:u,renderMessage:d}){let{actions:f,activeConversation:p,activeMessages:m,connectionStatus:g,conversations:_,currentUser:v,error:y,loading:b,theme:x,typingByConversation:S}=h(),[C,w]=(0,t.useState)(``),[T,O]=(0,t.useState)(``),[N,P]=(0,t.useState)(``),[F,z]=(0,t.useState)([]),[B,V]=(0,t.useState)(!1),[H,U]=(0,t.useState)(),[W,G]=(0,t.useState)([]),[K,q]=(0,t.useState)(!1),J=(0,t.useRef)(void 0),Y=o??x,X=(0,t.useMemo)(()=>{let e=T.trim().toLowerCase();return e?_.filter(t=>A(t,v?.id).toLowerCase().includes(e)):_},[_,v?.id,T]),Z=p?S[p.id]??[]:[],Q=(0,t.useMemo)(()=>m.map(e=>e.id).join(`,`),[m]),$=p?.members?.find(e=>e.user.id===v?.id),ee=s&&!!($&&[`owner`,`admin`,`moderator`].includes($.role));(0,t.useEffect)(()=>{if(!p||!Q)return;let e=m.filter(e=>e.sender.id!==v?.id).map(e=>e.id);e.length>0&&f.markRead(p.id,e)},[f,p,m,v?.id,Q]),(0,t.useEffect)(()=>{let e=!0;async function t(){if(N.trim().length<2){z([]);return}let t=await f.searchUsers({query:N.trim(),limit:8});e&&z(t)}return t(),()=>{e=!1}},[f,N]);async function te(e){if(e.preventDefault(),!p||!C.trim()&&W.length===0)return;let t=C.trim(),n=W;w(``),G([]),f.stopTyping(p.id);try{await f.sendMessage({conversationId:p.id,text:t||void 0,attachments:n.length>0?n:void 0,kind:L(n),replyToId:H?.id}),U(void 0)}catch{w(t),G(n)}}async function ne(e){if(e?.length){q(!0);try{let t=await Promise.all(Array.from(e).map(e=>f.uploadAttachment({file:e,name:e.name,type:I(e)})));G(e=>[...e,...t])}finally{q(!1)}}}function re(e){w(e),p&&(f.startTyping(p.id),J.current&&clearTimeout(J.current),J.current=setTimeout(()=>f.stopTyping(p.id),1400))}return(0,n.jsxs)(`section`,{className:[`chatemi`,e].filter(Boolean).join(` `),"data-status":g,"data-theme":Y,children:[a?(0,n.jsxs)(`aside`,{className:`chatemi__sidebar`,"aria-label":`Conversations`,children:[(0,n.jsxs)(`div`,{className:`chatemi__brand`,children:[(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:`ChatEmi`}),(0,n.jsx)(`span`,{children:M(g)})]}),(0,n.jsx)(`span`,{className:`chatemi__status chatemi__status--${g}`})]}),(0,n.jsxs)(`label`,{className:`chatemi__search`,children:[(0,n.jsx)(`span`,{className:`chatemi__sr-only`,children:`Search conversations`}),(0,n.jsx)(`input`,{value:T,onChange:e=>O(e.target.value),placeholder:`Search chats`})]}),(0,n.jsx)(`div`,{className:`chatemi__conversation-list`,children:X.map(e=>{let t=e.id===p?.id;return(0,n.jsx)(`button`,{className:`chatemi__conversation ${t?`chatemi__conversation--active`:``}`,onClick:()=>void f.openConversation(e.id),type:`button`,children:u?u(e,t):(0,n.jsx)(E,{conversation:e,currentUserId:v?.id})},e.id)})})]}):null,(0,n.jsxs)(`main`,{className:`chatemi__main`,children:[p?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(`header`,{className:`chatemi__header`,children:[(0,n.jsx)(k,{conversation:p,currentUserId:v?.id}),(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:A(p,v?.id)}),(0,n.jsx)(`span`,{children:Z.length>0?`${Z.map(e=>e.user.name).join(`, `)} typing...`:j(p,v?.id)})]}),ee?(0,n.jsx)(`button`,{className:`chatemi__header-action`,onClick:()=>V(e=>!e),type:`button`,children:`Members`}):null]}),B&&p?(0,n.jsxs)(`section`,{className:`chatemi__members`,"aria-label":`Member management`,children:[(0,n.jsx)(`div`,{className:`chatemi__members-list`,children:(p.members??p.participants.map(e=>R(e))).map(e=>(0,n.jsxs)(`div`,{className:`chatemi__member`,children:[e.user.avatarUrl?(0,n.jsx)(`img`,{alt:``,className:`chatemi__member-avatar`,src:e.user.avatarUrl}):(0,n.jsx)(`span`,{className:`chatemi__member-avatar`,children:e.user.name.slice(0,2).toUpperCase()}),(0,n.jsxs)(`span`,{children:[(0,n.jsx)(`strong`,{children:e.user.name}),(0,n.jsx)(`small`,{children:e.role})]}),e.role!==`owner`&&e.user.id!==v?.id?(0,n.jsxs)(`span`,{className:`chatemi__member-actions`,children:[(0,n.jsx)(`button`,{onClick:()=>void f.updateMember({conversationId:p.id,userId:e.user.id,role:`admin`}),type:`button`,children:`Admin`}),(0,n.jsx)(`button`,{onClick:()=>void f.updateMember({conversationId:p.id,userId:e.user.id,role:`member`}),type:`button`,children:`Member`}),(0,n.jsx)(`button`,{onClick:()=>void f.removeMember({conversationId:p.id,userId:e.user.id}),type:`button`,children:`Remove`})]}):null]},e.user.id))}),(0,n.jsxs)(`label`,{className:`chatemi__member-search`,children:[(0,n.jsx)(`span`,{children:`Find users from external directory`}),(0,n.jsx)(`input`,{onChange:e=>P(e.target.value),placeholder:`Search users`,value:N})]}),F.length>0?(0,n.jsx)(`div`,{className:`chatemi__user-results`,children:F.map(e=>(0,n.jsxs)(`button`,{onClick:()=>void f.addMembers(p.id,[e.id]),type:`button`,children:[e.avatarUrl?(0,n.jsx)(`img`,{alt:``,src:e.avatarUrl}):null,`Add `,e.name]},e.id))}):null]}):null,(0,n.jsxs)(`div`,{className:`chatemi__messages`,role:`log`,"aria-live":`polite`,children:[m.map(e=>{let t=e.sender.id===v?.id;return d?(0,n.jsx)(`div`,{className:`chatemi__message-shell`,children:d(e,t)},e.id):(0,n.jsx)(D,{enableActions:c,enableMediaPreview:l,isMine:t,message:e,onForward:e=>{let t=window.prompt(`Forward to conversation id`);t&&f.forwardMessage({sourceConversationId:e.conversationId,targetConversationId:t,messageId:e.id})},onReply:U},e.id)}),m.length===0&&!b?(0,n.jsx)(`div`,{className:`chatemi__empty`,children:`No messages yet. Start the conversation.`}):null]}),W.length>0?(0,n.jsx)(`div`,{className:`chatemi__attachments`,children:W.map(e=>(0,n.jsx)(`button`,{className:`chatemi__attachment-pill`,onClick:()=>G(t=>t.filter(t=>t.id!==e.id)),type:`button`,children:e.name??e.type},e.id))}):null,H?(0,n.jsxs)(`div`,{className:`chatemi__replying`,children:[(0,n.jsxs)(`span`,{children:[`Replying to `,(0,n.jsx)(`strong`,{children:H.sender.name}),`: `,H.text??H.attachments?.[0]?.name??`media`]}),(0,n.jsx)(`button`,{onClick:()=>U(void 0),type:`button`,children:`Cancel`})]}):null,(0,n.jsxs)(`form`,{className:`chatemi__composer`,onSubmit:e=>void te(e),children:[(0,n.jsxs)(`label`,{className:`chatemi__upload`,children:[(0,n.jsx)(`span`,{children:K?`Uploading`:`Attach`}),(0,n.jsx)(`input`,{disabled:K,multiple:!0,onChange:e=>void ne(e.target.files),type:`file`})]}),(0,n.jsx)(`textarea`,{onBlur:()=>f.stopTyping(p.id),onChange:e=>re(e.target.value),placeholder:i,rows:1,value:C}),(0,n.jsx)(`button`,{disabled:K||!C.trim()&&W.length===0,type:`submit`,children:`Send`})]})]}):r??(0,n.jsx)(`div`,{className:`chatemi__empty chatemi__empty--screen`,children:`Select a conversation to start messaging.`}),y?(0,n.jsx)(`div`,{className:`chatemi__error`,children:y}):null]})]})}function E({conversation:e,currentUserId:t}){return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(k,{conversation:e,currentUserId:t}),(0,n.jsxs)(`span`,{className:`chatemi__conversation-body`,children:[(0,n.jsx)(`strong`,{children:A(e,t)}),(0,n.jsx)(`small`,{children:e.lastMessage?.text??e.description??`No messages yet`})]}),e.unreadCount?(0,n.jsx)(`span`,{className:`chatemi__badge`,children:e.unreadCount}):null]})}function D({message:e,isMine:t,enableActions:r,enableMediaPreview:i,onReply:a,onForward:o}){return(0,n.jsxs)(`article`,{className:`chatemi__message ${t?`chatemi__message--mine`:``}`,children:[t?null:(0,n.jsx)(`strong`,{className:`chatemi__message-sender`,children:e.sender.name}),e.forwardedFrom?(0,n.jsxs)(`div`,{className:`chatemi__forwarded`,children:[`Forwarded from `,e.forwardedFrom.name]}):null,e.replyTo?(0,n.jsxs)(`blockquote`,{className:`chatemi__reply-preview`,children:[(0,n.jsx)(`strong`,{children:e.replyTo.sender.name}),(0,n.jsx)(`span`,{children:e.replyTo.text??e.replyTo.attachments?.[0]?.name??`media`})]}):null,e.text?(0,n.jsx)(`p`,{children:e.text}):null,e.html?(0,n.jsx)(`div`,{className:`chatemi__message-html`,dangerouslySetInnerHTML:{__html:e.html}}):null,e.attachments?.length?(0,n.jsx)(`div`,{className:`chatemi__message-attachments`,children:e.attachments.map(e=>O(e,i))}):null,e.reactions?.length?(0,n.jsx)(`div`,{className:`chatemi__reactions`,children:e.reactions.map(e=>(0,n.jsxs)(`span`,{children:[e.emoji,` `,e.count]},e.emoji))}):null,(0,n.jsxs)(`footer`,{children:[(0,n.jsx)(`time`,{dateTime:e.createdAt,children:N(e.createdAt)}),t&&e.status?(0,n.jsx)(`span`,{children:F(e)}):null]}),r?(0,n.jsxs)(`div`,{className:`chatemi__message-actions`,children:[(0,n.jsx)(`button`,{onClick:()=>a(e),type:`button`,children:`Reply`}),(0,n.jsx)(`button`,{onClick:()=>o(e),type:`button`,children:`Forward`})]}):null]})}function O(e,t){return t&&e.type===`image`?(0,n.jsxs)(`a`,{className:`chatemi__media chatemi__media--image`,href:e.url,rel:`noreferrer`,target:`_blank`,children:[(0,n.jsx)(`img`,{alt:e.caption??e.name??`image`,src:e.url}),e.caption?(0,n.jsx)(`span`,{children:e.caption}):null]},e.id):t&&e.type===`video`?(0,n.jsxs)(`figure`,{className:`chatemi__media chatemi__media--video`,children:[(0,n.jsx)(`video`,{controls:!0,poster:e.thumbnailUrl,src:e.url}),(0,n.jsx)(`figcaption`,{children:e.caption??e.name??`Video`})]},e.id):e.type===`voice`||e.type===`audio`?(0,n.jsxs)(`div`,{className:`chatemi__media chatemi__media--audio`,children:[(0,n.jsx)(`span`,{children:e.type===`voice`?`Voice message`:e.name??`Audio`}),(0,n.jsx)(`audio`,{controls:!0,src:e.url})]},e.id):(0,n.jsxs)(`a`,{className:`chatemi__media chatemi__media--file`,href:e.url,rel:`noreferrer`,target:`_blank`,children:[e.thumbnailUrl?(0,n.jsx)(`img`,{alt:``,src:e.thumbnailUrl}):null,(0,n.jsx)(`span`,{children:e.name??e.type}),e.size?(0,n.jsx)(`small`,{children:z(e.size)}):null]},e.id)}function k({conversation:e,currentUserId:t}){let r=A(e,t),i=e.participants.find(e=>e.id!==t)??e.participants[0],a=e.avatarUrl??i?.avatarUrl;return a?(0,n.jsx)(`img`,{alt:``,className:`chatemi__avatar`,src:a}):(0,n.jsx)(`span`,{className:`chatemi__avatar chatemi__avatar--fallback`,children:r.slice(0,2).toUpperCase()})}function A(e,t){return e.title?e.title:e.participants.filter(e=>e.id!==t).map(e=>e.name).join(`, `)||`Untitled chat`}function j(e,t){if(e.type===`channel`)return`${e.participants.length} subscribers`;if(e.type===`group`)return`${e.participants.length} members`;let n=e.participants.find(e=>e.id!==t);return n?n.presence===`online`?`online`:n.lastSeenAt?`last seen ${P(n.lastSeenAt)}`:`last seen recently`:`Saved messages`}function M(e){switch(e){case`connected`:return`Realtime online`;case`connecting`:case`reconnecting`:return`Connecting`;case`error`:return`Connection issue`;default:return`Realtime idle`}}function N(e){return new Intl.DateTimeFormat(void 0,{hour:`2-digit`,minute:`2-digit`}).format(new Date(e))}function P(e){let t=Date.now()-Date.parse(e),n=Math.max(1,Math.round(t/6e4));if(n<60)return`${n}m ago`;let r=Math.round(n/60);return r<24?`${r}h ago`:`${Math.round(r/24)}d ago`}function F(e){return e.status===`read`||e.readBy?.length?`read`:e.status===`delivered`||e.deliveredTo?.length?`delivered`:e.status??`sent`}function I(e){return e.type.startsWith(`image/`)?`image`:e.type.startsWith(`video/`)?`video`:e.type.startsWith(`audio/`)?e.name.toLowerCase().includes(`voice`)?`voice`:`audio`:`file`}function L(e){return e.some(e=>e.type===`voice`)?`voice`:e.length>0?`media`:`text`}function R(e){return{user:e,role:`member`,joinedAt:new Date().toISOString()}}function z(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/1024/1024).toFixed(1)} MB`}var B={width:420,height:680},V={width:340,height:480},H={width:920,height:860};function U({className:e,title:r=`Messages`,subtitle:i=`ChatEmi`,placement:a=`bottom-right`,defaultOpen:o=!1,showNotificationList:s=!0,badgeCount:c,initialSize:l=B,minSize:u=V,maxSize:d=H,markNotificationsReadOnOpen:f=!0,launcherIcon:p,...m}){let{actions:g,conversations:_,connectionStatus:v,notifications:y,theme:b,unreadNotificationCount:x}=h(),[S,C]=(0,t.useState)(o),[w,E]=(0,t.useState)({x:0,y:0}),D=(0,t.useRef)(void 0),O=(0,t.useMemo)(()=>_.reduce((e,t)=>e+(t.unreadCount??0),0),[_]),k=c??(x>0?x:O);function A(){C(e=>{let t=!e;return t&&f&&g.markNotificationsRead(),t&&g.requestNotificationPermission(),t})}function j(e){e.target.closest(`button`)||(e.currentTarget.setPointerCapture(e.pointerId),D.current={pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,originX:w.x,originY:w.y})}function M(e){let t=D.current;!t||t.pointerId!==e.pointerId||E({x:t.originX+e.clientX-t.startX,y:t.originY+e.clientY-t.startY})}function N(e){D.current?.pointerId===e.pointerId&&(D.current=void 0)}return(0,n.jsxs)(`div`,{className:[`chatemi-launcher`,`chatemi-launcher--${a}`,e].filter(Boolean).join(` `),"data-theme":m.theme??b,children:[S?(0,n.jsxs)(`section`,{"aria-label":r,className:`chatemi-launcher__modal`,style:{width:l.width,height:l.height,minWidth:u.width,minHeight:u.height,maxWidth:d.width,maxHeight:d.height,transform:`translate(${w.x}px, ${w.y}px)`},children:[(0,n.jsxs)(`div`,{className:`chatemi-launcher__modal-header`,onPointerDown:j,onPointerMove:M,onPointerUp:N,onPointerCancel:N,children:[(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:r}),(0,n.jsxs)(`span`,{children:[i,` · `,v]})]}),(0,n.jsx)(`button`,{"aria-label":`Close messages`,onClick:A,type:`button`,children:`Close`})]}),s&&y.length>0?(0,n.jsxs)(`div`,{className:`chatemi-launcher__notifications`,"aria-label":`Notifications`,children:[y.slice(0,3).map(e=>(0,n.jsxs)(`button`,{className:e.read?`chatemi-launcher__notification`:`chatemi-launcher__notification chatemi-launcher__notification--unread`,onClick:()=>{e.conversationId&&g.openConversation(e.conversationId),g.markNotificationsRead([e.id])},type:`button`,children:[e.avatarUrl?(0,n.jsx)(`img`,{alt:``,src:e.avatarUrl}):(0,n.jsx)(`span`,{children:e.title.slice(0,2).toUpperCase()}),(0,n.jsxs)(`span`,{children:[(0,n.jsx)(`strong`,{children:e.title}),e.body?(0,n.jsx)(`small`,{children:e.body}):null]})]},e.id)),(0,n.jsx)(`button`,{className:`chatemi-launcher__clear`,onClick:g.clearNotifications,type:`button`,children:`Clear`})]}):null,(0,n.jsx)(T,{...m})]}):null,(0,n.jsxs)(`button`,{"aria-expanded":S,"aria-label":S?`Close messages`:`Open messages`,className:`chatemi-launcher__toggle`,onClick:A,type:`button`,children:[(0,n.jsx)(`span`,{className:`chatemi-launcher__icon`,children:p??(0,n.jsx)(W,{})}),k>0?(0,n.jsx)(`span`,{className:`chatemi-launcher__badge`,children:k>99?`99+`:k}):null]})]})}function W(){return(0,n.jsxs)(`svg`,{"aria-hidden":`true`,fill:`none`,height:`28`,viewBox:`0 0 28 28`,width:`28`,children:[(0,n.jsx)(`path`,{d:`M5 13.4C5 8.76 8.98 5 13.9 5h.2C19.02 5 23 8.76 23 13.4c0 4.62-3.98 8.38-8.9 8.38h-.64c-.42 0-.83.12-1.18.34l-3.54 2.2a.75.75 0 0 1-1.14-.64l.18-3.2a1.8 1.8 0 0 0-.5-1.36A8.06 8.06 0 0 1 5 13.4Z`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:`2`}),(0,n.jsx)(`path`,{d:`M10 12.5h8M10 16h5`,stroke:`currentColor`,strokeLinecap:`round`,strokeWidth:`2`})]})}e.ChatEmiApi=a,e.ChatEmiApiError=i,e.ChatEmiLauncher=U,e.ChatEmiMessenger=T,e.ChatEmiProvider=m,e.ChatEmiSocket=u,e.useChatEmi=h});
@@ -0,0 +1,3 @@
1
+ import { ReactElement } from "react";
2
+ import type { ChatEmiLauncherProps } from "../types";
3
+ export declare function ChatEmiLauncher({ className, title, subtitle, placement, defaultOpen, showNotificationList, badgeCount, initialSize, minSize, maxSize, markNotificationsReadOnOpen, launcherIcon, ...messengerProps }: ChatEmiLauncherProps): ReactElement;
@@ -0,0 +1,3 @@
1
+ import { ReactElement } from "react";
2
+ import type { ChatEmiMessengerProps } from "../types";
3
+ export declare function ChatEmiMessenger({ className, emptyState, composerPlaceholder, showSidebar, theme, enableAdminControls, enableMessageActions, enableMediaPreview, renderConversation, renderMessage }: ChatEmiMessengerProps): ReactElement;
@@ -0,0 +1,42 @@
1
+ import type { ReactElement } from "react";
2
+ import { ChatEmiApi } from "./api";
3
+ import { ChatEmiSocket } from "./socket";
4
+ import type { ChatEmiAttachment, ChatEmiConversation, ChatEmiCreateConversationInput, ChatEmiEditMessageInput, ChatEmiForwardMessageInput, ChatEmiID, ChatEmiListOptions, ChatEmiManageMemberInput, ChatEmiMember, ChatEmiMessage, ChatEmiMessageListOptions, ChatEmiPresenceStatus, ChatEmiProviderProps, ChatEmiSendMessageInput, ChatEmiState, ChatEmiUpdateAvatarInput, ChatEmiUpdateMemberInput, ChatEmiUploadAttachmentInput, ChatEmiUser, ChatEmiUserSearchOptions } from "./types";
5
+ export interface ChatEmiActions {
6
+ refreshConversations: (options?: ChatEmiListOptions) => Promise<ChatEmiConversation[]>;
7
+ openConversation: (conversationId: ChatEmiID, options?: ChatEmiMessageListOptions) => Promise<ChatEmiMessage[]>;
8
+ createConversation: (input: ChatEmiCreateConversationInput) => Promise<ChatEmiConversation>;
9
+ sendMessage: (input: ChatEmiSendMessageInput) => Promise<ChatEmiMessage>;
10
+ editMessage: (input: ChatEmiEditMessageInput) => Promise<ChatEmiMessage>;
11
+ deleteMessage: (conversationId: ChatEmiID, messageId: ChatEmiID) => Promise<void>;
12
+ markRead: (conversationId: ChatEmiID, messageIds?: ChatEmiID[]) => Promise<void>;
13
+ markDelivered: (conversationId: ChatEmiID, messageIds?: ChatEmiID[]) => Promise<void>;
14
+ forwardMessage: (input: ChatEmiForwardMessageInput) => Promise<ChatEmiMessage>;
15
+ addReaction: (conversationId: ChatEmiID, messageId: ChatEmiID, emoji: string) => Promise<ChatEmiMessage>;
16
+ removeReaction: (conversationId: ChatEmiID, messageId: ChatEmiID, emoji: string) => Promise<ChatEmiMessage>;
17
+ uploadAttachment: (input: ChatEmiUploadAttachmentInput) => Promise<ChatEmiAttachment>;
18
+ updateAvatar: (input: ChatEmiUpdateAvatarInput) => Promise<ChatEmiConversation | ChatEmiUser>;
19
+ addMembers: (conversationId: ChatEmiID, userIds: ChatEmiID[]) => Promise<ChatEmiMember[]>;
20
+ updateMember: (input: ChatEmiUpdateMemberInput) => Promise<ChatEmiMember>;
21
+ removeMember: (input: ChatEmiManageMemberInput) => Promise<void>;
22
+ searchUsers: (options: ChatEmiUserSearchOptions) => Promise<ChatEmiUser[]>;
23
+ searchMessages: (query: string, options?: ChatEmiListOptions) => Promise<ChatEmiMessage[]>;
24
+ startTyping: (conversationId: ChatEmiID) => void;
25
+ stopTyping: (conversationId: ChatEmiID) => void;
26
+ setPresence: (status: ChatEmiPresenceStatus) => void;
27
+ dismissNotification: (notificationId: ChatEmiID) => void;
28
+ markNotificationsRead: (notificationIds?: ChatEmiID[]) => void;
29
+ clearNotifications: () => void;
30
+ requestNotificationPermission: () => Promise<NotificationPermission | "unsupported">;
31
+ connect: () => Promise<void>;
32
+ disconnect: () => void;
33
+ }
34
+ export interface ChatEmiContextValue extends ChatEmiState {
35
+ api: ChatEmiApi;
36
+ socket: ChatEmiSocket;
37
+ activeConversation?: ChatEmiConversation;
38
+ activeMessages: ChatEmiMessage[];
39
+ actions: ChatEmiActions;
40
+ }
41
+ export declare function ChatEmiProvider({ children, config, autoConnect, initialConversations, initialActiveConversationId, initialNotifications }: ChatEmiProviderProps): ReactElement;
42
+ export declare function useChatEmi(): ChatEmiContextValue;
@@ -0,0 +1,8 @@
1
+ import "./styles.css";
2
+ export { ChatEmiApi, ChatEmiApiError } from "./api";
3
+ export { ChatEmiProvider, useChatEmi } from "./context";
4
+ export type { ChatEmiActions, ChatEmiContextValue } from "./context";
5
+ export { ChatEmiSocket } from "./socket";
6
+ export { ChatEmiLauncher } from "./components/ChatEmiLauncher";
7
+ export { ChatEmiMessenger } from "./components/ChatEmiMessenger";
8
+ export type { ChatEmiAttachment, ChatEmiAttachmentKind, ChatEmiConfig, ChatEmiConnectionStatus, ChatEmiConversation, ChatEmiConversationType, ChatEmiCreateConversationInput, ChatEmiEditMessageInput, ChatEmiEndpointOverrides, ChatEmiForwardMessageInput, ChatEmiID, ChatEmiLauncherPlacement, ChatEmiLauncherProps, ChatEmiListOptions, ChatEmiManageMemberInput, ChatEmiMember, ChatEmiMemberPermission, ChatEmiMemberRole, ChatEmiMessage, ChatEmiMessageKind, ChatEmiMessageListOptions, ChatEmiMessageStatus, ChatEmiMessengerProps, ChatEmiNotification, ChatEmiNotificationConfig, ChatEmiNotificationKind, ChatEmiPage, ChatEmiPresenceEvent, ChatEmiPresenceStatus, ChatEmiProviderProps, ChatEmiReaction, ChatEmiReceiptEvent, ChatEmiSendMessageInput, ChatEmiSocketEnvelope, ChatEmiSocketEventMap, ChatEmiSocketEventName, ChatEmiSocketHandler, ChatEmiState, ChatEmiTheme, ChatEmiTypingEvent, ChatEmiUpdateAvatarInput, ChatEmiUpdateMemberInput, ChatEmiUploadAttachmentInput, ChatEmiUser, ChatEmiUserDirectoryConfig, ChatEmiUserSearchOptions } from "./types";
@@ -0,0 +1 @@
1
+ export { createChatEmiMongoConnection, ensureChatEmiIndexes, type ChatEmiMongoCollections, type ChatEmiMongoConnection, type ChatEmiMongoConnectionOptions } from "./mongodb.js";
@@ -0,0 +1 @@
1
+ export { createChatEmiMongoConnection, ensureChatEmiIndexes } from "./mongodb.js";
@@ -0,0 +1,23 @@
1
+ import { MongoClient, type Collection, type Db, type Document, type MongoClientOptions } from "mongodb";
2
+ export interface ChatEmiMongoCollections {
3
+ conversations: Collection<Document>;
4
+ messages: Collection<Document>;
5
+ members: Collection<Document>;
6
+ receipts: Collection<Document>;
7
+ attachments: Collection<Document>;
8
+ }
9
+ export interface ChatEmiMongoConnectionOptions {
10
+ uri: string;
11
+ databaseName: string;
12
+ clientOptions?: MongoClientOptions;
13
+ collectionNames?: Partial<Record<keyof ChatEmiMongoCollections, string>>;
14
+ }
15
+ export interface ChatEmiMongoConnection {
16
+ client: MongoClient;
17
+ db: Db;
18
+ collections: ChatEmiMongoCollections;
19
+ ensureIndexes: () => Promise<void>;
20
+ close: () => Promise<void>;
21
+ }
22
+ export declare function createChatEmiMongoConnection(options: ChatEmiMongoConnectionOptions): Promise<ChatEmiMongoConnection>;
23
+ export declare function ensureChatEmiIndexes(collections: ChatEmiMongoCollections): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import { MongoClient } from "mongodb";
2
+ const clients = new Map();
3
+ export async function createChatEmiMongoConnection(options) {
4
+ const client = await getMongoClient(options.uri, options.clientOptions);
5
+ const db = client.db(options.databaseName);
6
+ const collectionNames = {
7
+ conversations: "chatemi_conversations",
8
+ messages: "chatemi_messages",
9
+ members: "chatemi_members",
10
+ receipts: "chatemi_receipts",
11
+ attachments: "chatemi_attachments",
12
+ ...options.collectionNames
13
+ };
14
+ const collections = {
15
+ conversations: db.collection(collectionNames.conversations),
16
+ messages: db.collection(collectionNames.messages),
17
+ members: db.collection(collectionNames.members),
18
+ receipts: db.collection(collectionNames.receipts),
19
+ attachments: db.collection(collectionNames.attachments)
20
+ };
21
+ return {
22
+ client,
23
+ db,
24
+ collections,
25
+ ensureIndexes: () => ensureChatEmiIndexes(collections),
26
+ close: async () => {
27
+ clients.delete(cacheKey(options.uri, options.clientOptions));
28
+ await client.close();
29
+ }
30
+ };
31
+ }
32
+ export async function ensureChatEmiIndexes(collections) {
33
+ await Promise.all([
34
+ collections.conversations.createIndex({ updatedAt: -1 }),
35
+ collections.conversations.createIndex({ type: 1, publicUsername: 1 }, { sparse: true }),
36
+ collections.messages.createIndex({ conversationId: 1, createdAt: -1 }),
37
+ collections.messages.createIndex({ conversationId: 1, senderId: 1, createdAt: -1 }),
38
+ collections.members.createIndex({ conversationId: 1, userId: 1 }, { unique: true }),
39
+ collections.members.createIndex({ userId: 1, role: 1 }),
40
+ collections.receipts.createIndex({ conversationId: 1, messageId: 1, userId: 1, status: 1 }, { unique: true }),
41
+ collections.attachments.createIndex({ conversationId: 1, createdAt: -1 })
42
+ ]);
43
+ }
44
+ async function getMongoClient(uri, clientOptions) {
45
+ const key = cacheKey(uri, clientOptions);
46
+ const existingClient = clients.get(key);
47
+ if (existingClient) {
48
+ return existingClient;
49
+ }
50
+ // The package does not guess pool sizes. Pass clientOptions based on your
51
+ // serverless or long-running server workload and monitor MongoDB connection usage.
52
+ const clientPromise = new MongoClient(uri, clientOptions).connect();
53
+ clients.set(key, clientPromise);
54
+ return clientPromise;
55
+ }
56
+ function cacheKey(uri, clientOptions) {
57
+ return `${uri}:${JSON.stringify(clientOptions ?? {})}`;
58
+ }
@@ -0,0 +1,33 @@
1
+ import type { ChatEmiConfig, ChatEmiForwardMessageInput, ChatEmiID, ChatEmiPresenceStatus, ChatEmiSocketEventName, ChatEmiSocketHandler, ChatEmiUpdateMemberInput } from "./types";
2
+ export declare class ChatEmiSocket {
3
+ private readonly config;
4
+ private socket?;
5
+ private reconnectAttempts;
6
+ private shouldReconnect;
7
+ private reconnectTimer?;
8
+ private readonly queue;
9
+ private readonly listeners;
10
+ constructor(config: ChatEmiConfig);
11
+ get readyState(): number | undefined;
12
+ connect(): Promise<void>;
13
+ disconnect(): void;
14
+ on<TName extends ChatEmiSocketEventName>(eventName: TName, handler: ChatEmiSocketHandler<TName>): () => void;
15
+ send<TPayload>(type: string, payload: TPayload, requestId?: string): void;
16
+ subscribeConversation(conversationId: ChatEmiID): void;
17
+ unsubscribeConversation(conversationId: ChatEmiID): void;
18
+ sendTyping(conversationId: ChatEmiID, isTyping: boolean): void;
19
+ sendReadReceipt(conversationId: ChatEmiID, messageIds: ChatEmiID[]): void;
20
+ sendDeliveredReceipt(conversationId: ChatEmiID, messageIds: ChatEmiID[]): void;
21
+ sendForward(input: ChatEmiForwardMessageInput): void;
22
+ sendMemberUpdate(input: ChatEmiUpdateMemberInput): void;
23
+ sendAvatarUpdate(conversationId: ChatEmiID): void;
24
+ sendPresence(status: ChatEmiPresenceStatus): void;
25
+ private createSocket;
26
+ private handleMessage;
27
+ private flushQueue;
28
+ private scheduleReconnect;
29
+ private buildSocketUrl;
30
+ private dispatch;
31
+ private dispatchRaw;
32
+ private clearReconnectTimer;
33
+ }
@@ -0,0 +1,364 @@
1
+ import type { ReactNode } from "react";
2
+ export type ChatEmiID = string;
3
+ export type ChatEmiConnectionStatus = "idle" | "connecting" | "connected" | "reconnecting" | "disconnected" | "error";
4
+ export type ChatEmiConversationType = "direct" | "group" | "channel" | "bot";
5
+ export type ChatEmiMessageStatus = "queued" | "sending" | "sent" | "delivered" | "read" | "failed";
6
+ export type ChatEmiPresenceStatus = "online" | "offline" | "away" | "busy" | "invisible";
7
+ export type ChatEmiAttachmentKind = "image" | "video" | "audio" | "voice" | "file" | "location" | "contact";
8
+ export type ChatEmiMessageKind = "text" | "media" | "voice" | "system";
9
+ export type ChatEmiMemberRole = "owner" | "admin" | "moderator" | "member" | "guest";
10
+ export type ChatEmiMemberPermission = "send_messages" | "send_media" | "pin_messages" | "manage_messages" | "manage_members" | "manage_roles" | "manage_conversation" | "invite_members";
11
+ export type ChatEmiTheme = "light" | "dark" | "system" | "midnight" | "glass" | "emerald" | "violet";
12
+ export type ChatEmiLauncherPlacement = "bottom-right" | "bottom-left" | "top-right" | "top-left";
13
+ export type ChatEmiNotificationKind = "message" | "mention" | "reaction" | "system" | "custom";
14
+ export interface ChatEmiUser {
15
+ id: ChatEmiID;
16
+ name: string;
17
+ username?: string;
18
+ avatarUrl?: string;
19
+ statusText?: string;
20
+ presence?: ChatEmiPresenceStatus;
21
+ lastSeenAt?: string;
22
+ phoneNumber?: string;
23
+ email?: string;
24
+ verified?: boolean;
25
+ bot?: boolean;
26
+ metadata?: Record<string, unknown>;
27
+ }
28
+ export interface ChatEmiMember {
29
+ user: ChatEmiUser;
30
+ role: ChatEmiMemberRole;
31
+ permissions?: ChatEmiMemberPermission[];
32
+ joinedAt: string;
33
+ invitedBy?: ChatEmiUser;
34
+ mutedUntil?: string | null;
35
+ bannedUntil?: string | null;
36
+ title?: string;
37
+ }
38
+ export interface ChatEmiAttachment {
39
+ id: ChatEmiID;
40
+ type: ChatEmiAttachmentKind;
41
+ url: string;
42
+ name?: string;
43
+ mimeType?: string;
44
+ size?: number;
45
+ width?: number;
46
+ height?: number;
47
+ duration?: number;
48
+ waveform?: number[];
49
+ thumbnailUrl?: string;
50
+ caption?: string;
51
+ metadata?: Record<string, unknown>;
52
+ }
53
+ export interface ChatEmiReaction {
54
+ emoji: string;
55
+ count: number;
56
+ reactedByMe?: boolean;
57
+ users?: ChatEmiUser[];
58
+ }
59
+ export interface ChatEmiMessage {
60
+ id: ChatEmiID;
61
+ conversationId: ChatEmiID;
62
+ sender: ChatEmiUser;
63
+ kind?: ChatEmiMessageKind;
64
+ text?: string;
65
+ html?: string;
66
+ attachments?: ChatEmiAttachment[];
67
+ replyTo?: ChatEmiMessage;
68
+ replyToId?: ChatEmiID;
69
+ forwardedFrom?: ChatEmiUser;
70
+ forwardedFromConversationId?: ChatEmiID;
71
+ forwardedFromMessageId?: ChatEmiID;
72
+ forwardedAt?: string;
73
+ forwardCount?: number;
74
+ reactions?: ChatEmiReaction[];
75
+ status?: ChatEmiMessageStatus;
76
+ deliveredTo?: ChatEmiReceiptEvent[];
77
+ readBy?: ChatEmiReceiptEvent[];
78
+ viewCount?: number;
79
+ createdAt: string;
80
+ updatedAt?: string;
81
+ editedAt?: string;
82
+ deletedAt?: string;
83
+ metadata?: Record<string, unknown>;
84
+ }
85
+ export interface ChatEmiConversation {
86
+ id: ChatEmiID;
87
+ type: ChatEmiConversationType;
88
+ title?: string;
89
+ description?: string;
90
+ avatarUrl?: string;
91
+ participants: ChatEmiUser[];
92
+ members?: ChatEmiMember[];
93
+ ownerId?: ChatEmiID;
94
+ adminIds?: ChatEmiID[];
95
+ inviteLink?: string;
96
+ publicUsername?: string;
97
+ lastMessage?: ChatEmiMessage;
98
+ unreadCount?: number;
99
+ mutedUntil?: string | null;
100
+ pinned?: boolean;
101
+ archived?: boolean;
102
+ readOnly?: boolean;
103
+ createdAt: string;
104
+ updatedAt?: string;
105
+ metadata?: Record<string, unknown>;
106
+ }
107
+ export interface ChatEmiPage<T> {
108
+ items: T[];
109
+ nextCursor?: string | null;
110
+ }
111
+ export interface ChatEmiListOptions {
112
+ cursor?: string;
113
+ limit?: number;
114
+ }
115
+ export interface ChatEmiMessageListOptions extends ChatEmiListOptions {
116
+ beforeId?: ChatEmiID;
117
+ afterId?: ChatEmiID;
118
+ }
119
+ export interface ChatEmiSendMessageInput {
120
+ conversationId: ChatEmiID;
121
+ text?: string;
122
+ html?: string;
123
+ attachments?: ChatEmiAttachment[];
124
+ replyToId?: ChatEmiID;
125
+ kind?: ChatEmiMessageKind;
126
+ metadata?: Record<string, unknown>;
127
+ }
128
+ export interface ChatEmiEditMessageInput {
129
+ conversationId: ChatEmiID;
130
+ messageId: ChatEmiID;
131
+ text?: string;
132
+ html?: string;
133
+ metadata?: Record<string, unknown>;
134
+ }
135
+ export interface ChatEmiCreateConversationInput {
136
+ type: ChatEmiConversationType;
137
+ participantIds: ChatEmiID[];
138
+ title?: string;
139
+ description?: string;
140
+ avatarUrl?: string;
141
+ publicUsername?: string;
142
+ readOnly?: boolean;
143
+ metadata?: Record<string, unknown>;
144
+ }
145
+ export interface ChatEmiForwardMessageInput {
146
+ sourceConversationId: ChatEmiID;
147
+ targetConversationId: ChatEmiID;
148
+ messageId: ChatEmiID;
149
+ comment?: string;
150
+ metadata?: Record<string, unknown>;
151
+ }
152
+ export interface ChatEmiManageMemberInput {
153
+ conversationId: ChatEmiID;
154
+ userId: ChatEmiID;
155
+ }
156
+ export interface ChatEmiUpdateMemberInput extends ChatEmiManageMemberInput {
157
+ role?: ChatEmiMemberRole;
158
+ permissions?: ChatEmiMemberPermission[];
159
+ mutedUntil?: string | null;
160
+ bannedUntil?: string | null;
161
+ title?: string;
162
+ }
163
+ export interface ChatEmiUpdateAvatarInput {
164
+ conversationId?: ChatEmiID;
165
+ userId?: ChatEmiID;
166
+ attachment: ChatEmiAttachment;
167
+ }
168
+ export interface ChatEmiUserSearchOptions extends ChatEmiListOptions {
169
+ query: string;
170
+ }
171
+ export interface ChatEmiUploadAttachmentInput {
172
+ file: File | Blob;
173
+ conversationId?: ChatEmiID;
174
+ name?: string;
175
+ type?: ChatEmiAttachmentKind;
176
+ metadata?: Record<string, unknown>;
177
+ }
178
+ export interface ChatEmiTypingEvent {
179
+ conversationId: ChatEmiID;
180
+ user: ChatEmiUser;
181
+ isTyping: boolean;
182
+ expiresAt?: string;
183
+ }
184
+ export interface ChatEmiReceiptEvent {
185
+ conversationId: ChatEmiID;
186
+ messageIds: ChatEmiID[];
187
+ userId: ChatEmiID;
188
+ status: Extract<ChatEmiMessageStatus, "delivered" | "read">;
189
+ at: string;
190
+ }
191
+ export interface ChatEmiPresenceEvent {
192
+ userId: ChatEmiID;
193
+ status: ChatEmiPresenceStatus;
194
+ lastSeenAt?: string;
195
+ }
196
+ export interface ChatEmiNotification {
197
+ id: ChatEmiID;
198
+ kind: ChatEmiNotificationKind;
199
+ title: string;
200
+ body?: string;
201
+ conversationId?: ChatEmiID;
202
+ messageId?: ChatEmiID;
203
+ actor?: ChatEmiUser;
204
+ avatarUrl?: string;
205
+ read?: boolean;
206
+ createdAt: string;
207
+ metadata?: Record<string, unknown>;
208
+ }
209
+ export interface ChatEmiSocketEnvelope<TType extends string = string, TPayload = unknown> {
210
+ type: TType;
211
+ payload: TPayload;
212
+ requestId?: string;
213
+ }
214
+ export interface ChatEmiSocketEventMap {
215
+ connected: undefined;
216
+ disconnected: CloseEvent | undefined;
217
+ error: Event | Error;
218
+ reconnecting: {
219
+ attempt: number;
220
+ delay: number;
221
+ };
222
+ "conversation.created": ChatEmiConversation;
223
+ "conversation.updated": ChatEmiConversation;
224
+ "conversation.deleted": {
225
+ conversationId: ChatEmiID;
226
+ };
227
+ "conversation.member.added": {
228
+ conversationId: ChatEmiID;
229
+ member: ChatEmiMember;
230
+ };
231
+ "conversation.member.updated": {
232
+ conversationId: ChatEmiID;
233
+ member: ChatEmiMember;
234
+ };
235
+ "conversation.member.removed": {
236
+ conversationId: ChatEmiID;
237
+ userId: ChatEmiID;
238
+ };
239
+ "message.created": ChatEmiMessage;
240
+ "message.updated": ChatEmiMessage;
241
+ "message.deleted": {
242
+ conversationId: ChatEmiID;
243
+ messageId: ChatEmiID;
244
+ };
245
+ "message.receipt": ChatEmiReceiptEvent;
246
+ "message.reaction": {
247
+ conversationId: ChatEmiID;
248
+ messageId: ChatEmiID;
249
+ reactions: ChatEmiReaction[];
250
+ };
251
+ typing: ChatEmiTypingEvent;
252
+ presence: ChatEmiPresenceEvent;
253
+ notification: ChatEmiNotification;
254
+ }
255
+ export type ChatEmiSocketEventName = keyof ChatEmiSocketEventMap;
256
+ export type ChatEmiSocketHandler<TName extends ChatEmiSocketEventName> = (payload: ChatEmiSocketEventMap[TName]) => void;
257
+ export interface ChatEmiEndpointOverrides {
258
+ me?: string;
259
+ users?: string;
260
+ user?: (userId: ChatEmiID) => string;
261
+ conversations?: string;
262
+ conversation?: (conversationId: ChatEmiID) => string;
263
+ conversationAvatar?: (conversationId: ChatEmiID) => string;
264
+ members?: (conversationId: ChatEmiID) => string;
265
+ member?: (conversationId: ChatEmiID, userId: ChatEmiID) => string;
266
+ messages?: (conversationId: ChatEmiID) => string;
267
+ message?: (conversationId: ChatEmiID, messageId: ChatEmiID) => string;
268
+ markRead?: (conversationId: ChatEmiID) => string;
269
+ markDelivered?: (conversationId: ChatEmiID) => string;
270
+ forwardMessage?: (conversationId: ChatEmiID, messageId: ChatEmiID) => string;
271
+ reactions?: (conversationId: ChatEmiID, messageId: ChatEmiID) => string;
272
+ upload?: string;
273
+ search?: string;
274
+ }
275
+ export interface ChatEmiUserDirectoryConfig {
276
+ baseUrl: string;
277
+ searchPath?: string;
278
+ userPath?: (userId: ChatEmiID) => string;
279
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
280
+ mapUser?: (rawUser: unknown) => ChatEmiUser;
281
+ }
282
+ export interface ChatEmiNotificationConfig {
283
+ enabled?: boolean;
284
+ browser?: boolean;
285
+ showWhenOpen?: boolean;
286
+ maxStored?: number;
287
+ title?: string;
288
+ }
289
+ export interface ChatEmiConfig {
290
+ apiBaseUrl: string;
291
+ socketUrl?: string;
292
+ token?: string | (() => string | Promise<string | undefined> | undefined);
293
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
294
+ currentUser?: ChatEmiUser;
295
+ fetchImpl?: typeof fetch;
296
+ websocketFactory?: (url: string) => WebSocket;
297
+ endpoints?: ChatEmiEndpointOverrides;
298
+ userDirectory?: ChatEmiUserDirectoryConfig;
299
+ theme?: ChatEmiTheme;
300
+ notifications?: ChatEmiNotificationConfig;
301
+ reconnect?: {
302
+ enabled?: boolean;
303
+ maxAttempts?: number;
304
+ initialDelayMs?: number;
305
+ maxDelayMs?: number;
306
+ };
307
+ }
308
+ export interface ChatEmiProviderProps {
309
+ children: ReactNode;
310
+ config: ChatEmiConfig;
311
+ autoConnect?: boolean;
312
+ initialConversations?: ChatEmiConversation[];
313
+ initialActiveConversationId?: ChatEmiID;
314
+ initialNotifications?: ChatEmiNotification[];
315
+ }
316
+ export interface ChatEmiState {
317
+ currentUser?: ChatEmiUser;
318
+ conversations: ChatEmiConversation[];
319
+ activeConversationId?: ChatEmiID;
320
+ messagesByConversation: Record<ChatEmiID, ChatEmiMessage[]>;
321
+ typingByConversation: Record<ChatEmiID, ChatEmiTypingEvent[]>;
322
+ presenceByUser: Record<ChatEmiID, ChatEmiPresenceEvent>;
323
+ notifications: ChatEmiNotification[];
324
+ unreadNotificationCount: number;
325
+ connectionStatus: ChatEmiConnectionStatus;
326
+ theme: ChatEmiTheme;
327
+ loading: boolean;
328
+ error?: string;
329
+ }
330
+ export interface ChatEmiMessengerProps {
331
+ className?: string;
332
+ emptyState?: ReactNode;
333
+ composerPlaceholder?: string;
334
+ showSidebar?: boolean;
335
+ theme?: ChatEmiTheme;
336
+ enableAdminControls?: boolean;
337
+ enableMessageActions?: boolean;
338
+ enableMediaPreview?: boolean;
339
+ renderConversation?: (conversation: ChatEmiConversation, isActive: boolean) => ReactNode;
340
+ renderMessage?: (message: ChatEmiMessage, isMine: boolean) => ReactNode;
341
+ }
342
+ export interface ChatEmiLauncherProps extends Omit<ChatEmiMessengerProps, "className"> {
343
+ className?: string;
344
+ title?: string;
345
+ subtitle?: string;
346
+ placement?: ChatEmiLauncherPlacement;
347
+ defaultOpen?: boolean;
348
+ showNotificationList?: boolean;
349
+ badgeCount?: number;
350
+ initialSize?: {
351
+ width: number;
352
+ height: number;
353
+ };
354
+ minSize?: {
355
+ width: number;
356
+ height: number;
357
+ };
358
+ maxSize?: {
359
+ width: number;
360
+ height: number;
361
+ };
362
+ markNotificationsReadOnOpen?: boolean;
363
+ launcherIcon?: ReactNode;
364
+ }