@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.
- package/LICENSE +21 -0
- package/README.md +451 -0
- package/dist/api.d.ts +43 -0
- package/dist/assets/styles.css +2 -0
- package/dist/chatemi.js +1626 -0
- package/dist/chatemi.umd.cjs +1 -0
- package/dist/components/ChatEmiLauncher.d.ts +3 -0
- package/dist/components/ChatEmiMessenger.d.ts +3 -0
- package/dist/context.d.ts +42 -0
- package/dist/index.d.ts +8 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/mongodb.d.ts +23 -0
- package/dist/server/mongodb.js +58 -0
- package/dist/socket.d.ts +33 -0
- package/dist/types.d.ts +364 -0
- package/docs/BACKEND_CONTRACT.md +305 -0
- package/docs/IMPLEMENTATION_GUIDE.md +306 -0
- package/examples/nextjs-chat-widget/README.md +46 -0
- package/examples/nextjs-chat-widget/app/api/chat/conversations/route.ts +30 -0
- package/examples/nextjs-chat-widget/app/api/chat/socket-events/route.ts +44 -0
- package/examples/nextjs-chat-widget/app/components/ChatWidget.tsx +35 -0
- package/examples/nextjs-chat-widget/app/layout.tsx +20 -0
- package/examples/nextjs-chat-widget/app/lib/chatemi-config.ts +52 -0
- package/examples/nextjs-chat-widget/app/lib/chatemi-mongo.ts +27 -0
- package/examples/nextjs-chat-widget/app/page.tsx +11 -0
- package/package.json +77 -0
|
@@ -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;
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/socket.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|