
现在,我将向您展示如何在没有后端或不想花时间开发后端的情况下为团队/用户/朋友聊天。 我们将进行简单的文本聊天,大约需要一个小时。
要编写没有后端的有效网络聊天几乎是不可能的,它必须采用一种或另一种形式。 我们将使用Chatix及其JavaScript SDK。 Chatix和SDK将从事消息存储和网络任务,我们将处理前端。
完成的项目代码可在GitHub上找到
演示版
项目结构
- App(应用程序的根组件,充当状态守护者,因为在本课程中,我们不会添加Redux或任何其他状态管理器)
- 标头(显示徽标,聊天名称并允许用户输入其名称的应用程序标头)
- LogoHeader
- 房间标题
- 主要的
- MemberList(聊天列表)
- ChatField(包含与聊天消息相关的所有内容的容器)
- 邮件容器
- Message [](消息的演示;在本课程中,我们仅处理文本消息)
- SendMessageForm(用于发送新聊天消息的表单)
- ChatixSDK(无头组件,负责后端)
有关状态存储的重要说明。 当然,在此处添加Redux并通过它处理状态更改会更加方便,但是为了节省时间,我们将状态存储在App的根组件中,并将数据转发给子组件并从子方法中调用它们的父代。
例如,当我们获得聊天的名称时,我们会将其保存为App组件的状态,并将其通过以下App → Header → RoomTitle
传递: App → Header → RoomTitle
。 当用户编写消息时,我们会将其从SendMessageForm传输到App: SendMessageForm → ChatField → Main → App
。
我们的聊天在设计中将如下所示:

组件之间的交互
我们的组件必须相互之间传输数据,并且为了使一切正常工作,现在让我们确定它们之间的交互方式。

正如您在图中所看到的,对我们来说主要的组件是App
,它向子组件提供数据(由于反应性,我们只需分配prop
并且子组件将响应更改),并且子组件相继将方法调用转发给App
。 这不是可以(并且应该)为生产项目完成的最佳架构,但是它将为我们的课程做准备。
项目创建
创建视觉组件
帽子
首先,您需要创建一个新项目,为此,我们将使用create-react-app 。
npx create-react-app chatix-chatroom cd chatix-chatroom
使用命令运行项目
npm start
让我们从创建标题开始。
首先,将徽标添加到标题。 为此,请在src文件夹中创建components文件夹,并在其中创建logo_header文件夹。 我们将徽标上传到此文件夹,并创建2个LogoHeader.js和LogoHeader.css文件
LogoHeader.js
import React from 'react' import logo from './chatix_logo.svg'; import './LogoHeader.css'; function LogoHeader(){ return ( <div className="LogoHeader"> <img src={logo} className="App-logo" alt="Chatix logo" /> </div> ); } export default LogoHeader;
LogoHeader.css
.LogoHeader{ flex-basis: 200px; flex-grow: 0; flex-shrink: 0; }
此处的所有内容都很清楚,只需导入带有徽标和样式的文件即可。
我将不再在此处添加样式表的代码,您可以在完成的项目页面上看到它们
现在显示聊天室的名称。 为此,请创建一个房间标题文件夹,并在其中创建RoomTitle组件。 我们将通过props将名称放入该组件中,因此我们编写了props.chatroomName
,现在将其转移到此处。
房间标题
import React from 'react'; import './RoomTitle.css'; function RoomTitle(props){ return ( <div className="RoomTitle"> <h1>{props.chatroomName}</h1> </div> ); } export default RoomTitle;
然后,我们创建标题组件本身,并将徽标和聊天室的名称放入其中。 通过prop chatroomName
立即将聊天名称放入子组件中。
提醒您,我们同意所有数据(应用程序状态)将由App的根组件存储。 通过它,我们将首先将标头传输到Header ,再从Header传输到RoomTitle 。
组件\标头\ Header.js
Header.js
import React from 'react'; import './Header.css' import LogoHeader from '../logo_header/LogoHeader'; import RoomTitle from '../room-title/RoomTitle'; function Header(props) { return ( <header> <LogoHeader/> <RoomTitle chatroomName={props.chatroomName} /> </header> ); } export default Header;
接下来,打开App.js文件,并将Header.js组件添加到其中 。
然后,将名称添加到状态,然后通过props将其转发到标头 。
同样在标题中,您需要添加当前用户的名称。 为此,将用户对象添加到状态,然后类似地将其转发到标题
import React from 'react'; import './App.css'; import Header from './components/header/Header'; class App extends React.Component { constructor(props){ super(props); chatroomName: '-', me: { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" } } render() { return ( <div className="App"> <Header chatroomName={this.state.chatroomName} me={this.state.me} /> </div> ); }; } export default App;
现在,您需要在标题中添加带有当前用户名的输入,并分配要更改的处理程序,以便我们可以将新的用户名转移到App组件。
为此,我们将名称为名称的handleChangeName
函数props.updateVisitor
添加到输入中,并调用props.updateVisitor
回调函数,在其中我们将使用更新后的名称传递给用户对象。
Header.js
function Header(props) { const [name, setName] = useState(props.me.name ? props.me.name : props.me.uuid.substr(-10)) const handleChangeName = (e) => { setName(e.target.value) let visitor = {...props.me}; visitor.name = e.target.value; props.updateVisitor(visitor) } return ( <header> <LogoHeader/> <RoomTitle chatroomName={props.chatroomName}/> { props.me ? <input className='name-input' value={name} placeholder=' ' onChange={(e) => handleChangeName(e)} /> : null } </header> ); }
现在,将此功能props.updateVisitor
添加到App并将其props.updateVisitor
到props.updateVisitor
。 到目前为止,它只是更新状态下的用户对象,但是通过它,我们将更新服务器上的用户。
onUpdateVisitor = (visitor) => { this.setState({me: visitor}) }
因此,现在我们的应用程序看起来像这样,到目前为止,只知道如何更新名称。 继续前进

侧边栏
现在开始创建侧边栏。
侧栏将位于Main.js页面上的主要组件内。
我们先创建一个components \ main \ Main.js ,然后创建一个包含用户组件列表的组件\ member-list \ MemberList.js,然后立即创建一个组件,该组件将显示用户自己的components \ member-item \ MemberItem.js 。
为了更清楚地说明这三个组件之间的关系,请查看本文开头的项目大纲。
组件已创建,现在让我们按顺序进行。
首先,将用户数组添加到App组件的状态,然后添加Main组件。 然后,我们会将这些用户转发到其中。
该应用程序
class App extends React.Component { constructor(props) { super(props); this.state = { chatroomName: '-', members: [ { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" }, { is_online: true, name: "", uuid: "mnzxcv97zx6chvo" }, { is_online: false, name: "", uuid: "kjuhv987ashdfoua" }, { is_online: false, name: "", uuid: "jdhnf978WEHJSNDL" }, ], me: { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" } }; } render() { return ( <div className="App"> <Header chatroomName={this.state.chatroomName} me={this.state.me} /> <Main members={this.state.members} me={this.state.me} /> </div> ); }; }
在Main组件中,添加MemberList组件并将用户数组转发到其中。
Main.js
function Main(props) { return( <section className="Main"> <MemberList members={props.members} /> </section> ); }
在MemberList组件中,我们遍历所有用户,并为每个用户返回MemberItem组件并将用户对象传递给它。
MemberList.js
function MemberList(props) { const members = props.members.map((member) => <MemberItem key={member.uuid} member={member} /> ); return ( <section className="MemberList"> {members} </section> ); }
MemberItem组件已经在边栏中直接显示用户。 在其中,我们检查用户名(如果未安装),然后显示标识符的前10个字符。 我们还将检查在线/离线状态,并将标识符与当前用户的标识符进行比较,以便在他的对面显示“(您)”标记。
function MemberItem(props) { function getName(){ let name = '' if (props.member.uuid === props.me.uuid) { if(props.me.name) { name = props.me.name } else { name = props.me.uuid.substring(props.me.uuid.length-10, props.me.uuid.length); } } else { if(props.member.name){ name = props.member.name } else { name = props.member.uuid.substring(props.member.uuid.length-10, props.member.uuid.length); } } return name; } return( <div className="MemberItem"> <img src={ icon } alt={ props.member.name }/> <span> { getName() } { props.member.uuid === props.me.uuid && " () " } </span> { props.member.is_online && <span className="online">•</span> } </div> ); }
做完了 现在应用程序已经看起来像这样

现在,我们将处理邮件列表和发送形式。
首先,将包含消息的数组添加到App组件的状态。
该应用程序
this.state = { chatroomName: '-', messages: [ { content: " 1", sender_id: "mnzxcv97zx6chvo", uuid: "dg897sdfg" }, { content: " 2", sender_id: "98s7dfh9a8s7dhf", uuid: "8723hernm" }, { content: " ", sender_id: "mnzxcv97zx6chvo", uuid: "435nbcv98234" } ], members: [ { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" }, { is_online: true, name: "", uuid: "mnzxcv97zx6chvo" }, { is_online: false, name: "", uuid: "kjuhv987ashdfoua" }, { is_online: false, name: "", uuid: "jdhnf978WEHJSNDL" }, ], me: { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" } };
并将它们转发到主要组件
该应用程序
<Main members={this.state.members} messages={this.state.messages} me={this.state.me} />
现在创建组件组件/ chat-field / ChatField.js
将其连接到Main并将消息转发给Main 。
主要的
function Main(props) { return( <section className="Main"> <MemberList me={props.me} members={props.members} /> <ChatField messages={props.messages} /> </section> ); }
接下来,创建组件组件/ message-container / MessageContainer.js
将其连接到ChatField并将消息转发给它。
查特菲尔德
function Main(props) { return( <section className="Main"> <MemberList me={props.me} members={props.members} /> <ChatField messages={props.messages} /> </section> ); }
然后,我们将遍历所有消息,并为每个消息返回显示消息的组件。
让我们创建它conponents / message / Message.js 。 在其中,我们将显示访问者的图标,其名称或未指定名称的标识符以及消息文本本身。
留言内容
function Message(props) { const getSenderName = () => { if (props.sender) { return props.sender.name ? props.sender.name : props.sender.uuid.substr(-10); } return "Unknown sender"; }; return( <div className="Message"> <div className="message-sender-icon"> <img src={icon} alt="visitor icon"/> </div> <div className="message-bubble"> <div className="message-sender-name">{getSenderName()}</div> <div className="message-content">{props.message.content}</div> </div> </div> ); }
现在,在MessageContainer中,我们遍历所有消息,并为每个消息返回将消息对象传递给的Message组件。
邮件容器
function MessageContainer(props) { const messageList = props.messages.map(message => <Message key={message.uuid} sender={props.members.find((member) => member.uuid === message.sender_id)} message={message} /> ); return ( <section className="MessageContainer" ref={messagesContainer}> {messageList} </section> ); }
现在项目看起来像这样:

现在,创建一个带有用于发送消息的表单的组件component / send-message-form / SendMessageForm.js 。 在其中,我们将创建一个输入和一个发送按钮。 更改输入后,会将其文本写入状态,然后单击按钮,我们将调用onSendNewMessage
回调函数并将消息从状态传输到状态。 onSendNewMessage
稍后将在App组件中创建onSendNewMessage
函数,并将其通过props转发。
发送消息形式
class SendMessageForm extends React.Component { constructor(props) { super(props); this.state = { message: '' }; } currentMessageChanged = (e) => { this.setState({message: e.target.value }); } sendMessageClicked = async (e) => { e.preventDefault(); if (this.state.message.length > 0) { await this.props.onSendNewMessage(this.state.message); this.setState({...this.state, ...{message : ''}}); } } render(){ return ( <section className="SendMessageForm"> <form> <input type="text" value={this.state.message} onChange={this.currentMessageChanged} placeholder="Type message to send"/> <button type="submit" onClick={this.sendMessageClicked} > Send </button> </form> </section> ); } }
现在,将SendMessageForm组件放置在ChatField中 。
查特菲尔德
function ChatField(props) { return( <section className="ChatField"> <MessageContainer members={props.members} messages={props.messages} /> <SendMessageForm onSendNewMessage={props.onSendNewMessage}/> </section> ); }
在Main组件中,我们还将onSendNewMessage
中onSendNewMessage
函数。
主要的
<ChatField members={props.members} messages={props.messages} onSendNewMessage={props.onSendNewMessage} />
现在在App中创建此功能,并将其转发给Main 。
该应用程序
onSendNewMessage = async (message) => { console.log(message) }
该应用程序
<Main members={this.state.members} messages={this.state.messages} onSendNewMessage={this.onSendNewMessage} me={this.state.me} />
做完了 现在,当您单击发送消息按钮时,它将被传输到App组件。
现在,应用程序如下所示:

因此,现在所有内容都显示在我们的应用程序中,并且一切正常运行,但是到目前为止,这些数据都是静态数据,为了使我们的聊天更加活跃,您需要将其与后端关联。
后端连接
为此,您需要做的第一件事是安装chatix-core软件包。
npm i chatix-core
然后在chatix上创建一个帐户并创建一个聊天室。 为此,请转到chatix.io并注册。
注册后,您可以在聊天设置页面上的管理界面中看到websiteId网站标识符 。
现在,我们将创建一个新的聊天室,我们将使用该聊天室。

我们返回到项目并创建一个新组件,通过该组件我们可以与服务器一起使用。
组件\ chatix \ ChatixSDK.js
我们在其中导入ChatixCore。
import ChatixCore from 'chatix-core';
在ChatixSDK组件中,创建ChatixCore类的实例,并将websiteId作为参数传递。
const websiteId = "_WEBSITE_ID"; this.sdk = new ChatixCore(websiteId);
现在,您可以在this.sdk中使用用于聊天室的方法。 您可以在chatix-core项目页面上查看方法列表
接下来,我们需要连接到服务器并获取有关先前创建的聊天室的数据。 为此有异步方法start()和getChatroom() 。
收到聊天室对象后,让我们立即获取其名称并将其传输到App 。 为此,在应用程序中添加回调函数updateChatroomTitle(chatroom.title)
并在ChatixSDK中对其进行调用 。
ChatixSDK
class ChatixSDK extends React.Component { constructor(props){ super(props); const websiteId = "_WEBSITE_ID"; this.chatroomId = "_CHATROOM_ID"; this.sdk = new ChatixCore(websiteId); this.sdk.start() .then( async () => { try { // refresh information about chatroom and call passed handler const chatroom = await this.sdk.getChatroom(this.chatroomId); if (props.updateChatroomTitle) { props.updateChatroomTitle(chatroom.title); } } catch (err) { console.error(err); } }) .catch((e) => { console.error(e); }); } render(){ return null; } }
您可以通过打开所需的聊天室在管理员界面中查看this.chatroomId
。

现在,在应用程序中,我们将连接ChatixSDK组件,并将updateChatroomTitle函数放入该组件中,这将更新聊天名称。 我们还向它添加了一个ref
链接,以便我们可以访问此组件。
该应用程序
this.chatixSDK = React.createRef();
setChatroomTitle = (newName) => { const newStateFragment = { chatroomName: newName}; this.setState({...this.state, ...newStateFragment}); };
该应用程序
render() { return ( <div className="App"> <Header chatroomName={this.state.chatroomName} me={this.state.me} updateVisitor={this.onUpdateVisitor} /> <Main members={this.state.members} messages={this.state.messages} onSendNewMessage={this.onSendNewMessage} me={this.state.me} /> <ChatixSDK ref={this.chatixSDK} updateChatroomTitle={this.setChatroomTitle} /> </div> ); };
做完了 现在,在连接到服务器之后,我们立即请求聊天数据,获取其名称并将其写入App组件的状态,由于状态的更改会导致该组件再次呈现,因此标头中的名称将自动更新。 现在,该状态下的默认名称可以替换为空字符串。
该应用程序
chatroomName: ''
现在,让侧边栏填充真实用户。
但是,在获得需要连接到聊天的用户列表之前,为此,在this.sdk.start()
函数内的this.sdk.start()
我们获得了所有用户聊天室的列表,请检查它是否已连接到当前聊天室,如果没有,则将其连接。
ChatixSDK
const myChatrooms = await this.sdk.getMyChatrooms(); if (myChatrooms.filter(x => x.id===this.chatroomId).length === 0) { await this.sdk.connectToChatroom(this.chatroomId); }
确定我们的用户已连接到聊天室之后,我们可以获取此聊天的参与者列表。
ChatixSDK
// lets get all chatroom members using infinite loop with break on empty server response let membersPage = 1; let allChatroomMembers = []; while(true) { let pagedMembers = await this.sdk.getChatroomMembers(this.chatroomId, membersPage++, 10); allChatroomMembers = [...allChatroomMembers, ...pagedMembers]; if (pagedMembers.length === 0) { break; } }
在这里,在一个无限循环中,我们逐页请求用户,直到我们得到所有人为止,一旦我们获得所有人,我们便打破了循环。 之后,就像聊天室的名称一样,我们使用回调函数将其转发给父组件。
ChatixSDK
if (props.setChatroomMembers) { props.setChatroomMembers(allChatroomMembers); }
现在,在App组件中,创建此回调函数setChatroomMembers
,该函数setChatroomMembers
用户的联机/脱机状态并按字母顺序对用户进行排序并记录其状态。
App.js
setChatroomMembers = (members) => { members.sort(this.sortMembers); const newStateFragment = { members: members}; this.setState({...this.state, ...newStateFragment}); }
添加排序功能sortMembers 。 它按状态和字母顺序对用户进行排序。
App.js
sortMembers(a, b) { if (a.is_online === true && b.is_online === false) { return -1; } else if (b.is_online === true && a.is_online === false) { return 1; } else { if (a.name && b.name) { if (a.name.toLocaleUpperCase() > b.name.toLocaleUpperCase()) { return 1; } else if (a.name.toLocaleUpperCase() < b.name.toLocaleUpperCase()) { return -1; } } else if (a.name && !b.name) { return -1; } else if (!a.name && b.name) { return 1; } if (a.uuid > b.uuid) { return -1; } else { return 1; } } }
接下来,我们在ChatixSDK中转发setChatroomMembers函数。
该应用程序
render() { return ( <div className="App"> <Header chatroomName={this.state.chatroomName} me={this.state.me} updateVisitor={this.onUpdateVisitor} /> <Main members={this.state.members} messages={this.state.messages} onSendNewMessage={this.onSendNewMessage} me={this.state.me} /> <ChatixSDK ref={this.chatixSDK} updateChatroomTitle={this.setChatroomTitle} setChatroomMembers={this.setChatroomMembers} /> </div> ); };
现在,在连接到服务器之后,我们以及标头会立即请求所有已连接用户的列表,并将其写入App组件的状态。 并且还可以更改状态中用户列表的默认值。
该应用程序
members: []
现在,按照完全相同的原理,我们获取当前用户的对象和消息数组,并将它们写入App状态
ChatixSDK
// lets load 100 last messages from current chatroom const lastMessages = await this.sdk.getChatroomMessages(this.chatroomId, null, 100); if (props.setChatroomMessages) { props.setChatroomMessages(lastMessages); } if (props.setMe) { const me = this.sdk.getVisitor(); this.props.setMe(me); }
该应用程序
<ChatixSDK ref={this.chatixSDK} setMe={this.setMe} updateChatroomTitle={this.setChatroomTitle} setChatroomMembers={this.setChatroomMembers} setChatroomMessages={this.setChatroomMessages} />
接下来,我们将发送消息。
我们在App中已经具有onSendNewMessage
函数,该函数显示将消息发送到控制台。 相反,我们只需要调用sendChatroomMessage
方法来从ChatixSDK发送消息。
这是一个异步方法,它在响应中返回已发送消息的对象,我们将立即将其添加到状态的消息数组中。 顺便说一句,请注意,我们正在this.chatixSDK
前面创建this.chatixSDK
链接this.chatixSDK
。
该应用程序
onSendNewMessage = async (message) => { let receivedMsg = await this.chatixSDK.current.sendChatroomMessage(message); const currentMessages = this.state.messages; currentMessages.push(receivedMsg); const newStateFragment = {messages: currentMessages}; this.setState({...this.state, ...newStateFragment}); }
由于状态更改会导致其重新呈现,因此消息列表将自动更新。 但是我们需要确保在添加消息时,消息块中的滚动会下降。
为此,请打开MessageContainer组件并使用useEffect钩子,监视消息数组中的更改,并在更改和添加消息后立即获得包含消息的scrollHeight块,并将其滚动相同的数量
function MessageContainer(props) { const messagesContainer = React.createRef(); useEffect(() => { messagesContainer.current.scrollTop = messagesContainer.current.scrollHeight }, [props, messagesContainer]); const messageList = props.messages.map(message => <Message key={message.uuid} sender={props.members.find((member) => member.uuid === message.sender_id)} message={message} /> ); return ( <section className="MessageContainer" ref={messagesContainer}> {messageList} </section> ); }
现在,让我们完成用户名的更新。 我们已经在标题中创建了一个输入,并在更改它时,将更新的用户对象转发到App组件,然后在控制台中显示它。 让我们完成此功能。 为此,向其添加名为this.chatixSDK.current.updateVisitor(user)
的方法,这将更新服务器上的数据。 只需更新本地状态中的数据,为此,我们将更新this.state.me
对象,并在this.state.members
数组中找到当前用户并对其进行更新。 在发送给他们的消息中更新当前用户的名称是必要的。
该应用程序
onUpdateVisitor = (user) => { this.chatixSDK.current.updateVisitor(user) this.setMe(user) let currentUser = this.state.members.find((member) => (member.uuid === user.uuid)) let currentUserIndex = this.state.members.indexOf(currentUser) let newMembers = [...this.state.members] newMembers[currentUserIndex] = user; this.setState({ members: newMembers }) }
现在,我们需要学习如何响应传入消息,连接/断开用户以及更改信息和连接的用户。
为此,在构造函数的ChatixSDK.js文件中,我们需要覆盖回调函数。 您可以在chatix-core项目页面上查看函数和参数的完整列表。
我们目前对onChatroomMessageReceived , onMemberConnectedToChatroom , onMemberDisconnectedFromChatroom和onApplyVisitorInfo感兴趣 。
我们重新定义它们,并为每个函数调用我们将在App中创建的回调。
this.sdk.onChatroomMessageReceived = (chatroomId, message) => { if (chatroomId === this.chatroomId) { this.props.onNewMessageReceived(message); } }; this.sdk.onMemberConnectedToChatroom = (chatroomId, member) => { if (chatroomId === this.chatroomId && props.addChatroomMember) { this.props.addChatroomMember(member); } }; this.sdk.onMemberDisconnectedFromChatroom = (chatroomId, member) => { if (chatroomId === this.chatroomId && props.removeChatroomMember) { this.props.removeChatroomMember(member); } }; this.sdk.onApplyVisitorInfo = (visitor) => { this.props.onMemberUpdated(visitor) }
接下来,转到应用程序并创建这些功能。
onNewMessageReceived(消息)
该函数接受一个消息对象,并简单地将其与其余消息一起添加到状态中。 , .
App
onNewMessageReceived = (message) => { const currentMessages = this.state.messages; currentMessages.push(message); const newStateFragment = {messages: currentMessages}; this.setState({...this.state, ...newStateFragment}); }
App
addChatroomMember(member)
state members. .
App
addChatroomMember = (member) => { const newStateFragment = {}; const currentMembers = this.state.members; currentMembers.push(member); currentMembers.sort(this.sortMembers); newStateFragment.members = currentMembers; this.setState({...this.state, ...newStateFragment}); }
App
removeChatroomMember(memberId)
state members state .
removeChatroomMember = (memberId) => { const currentMembers = this.state.members; const filteredMembers = currentMembers.filter(x=> x.uuid !== memberId); const newStateFragment = {members: filteredMembers}; this.setState({...this.state, ...newStateFragment}); }
onMemberUpdated(updatedMember)
. . state .
App
onMemberUpdated = (updatedMember) => { let oldMember = this.state.members.find(member => member.uuid === updatedMember.uuid); oldMember = this.state.members.indexOf(oldMember); let newStateMembers = this.state.members; newStateMembers[oldMember] = updatedMember; this.setState({ members: newStateMembers }) }
ChatixSDK
ChatixSDK
<ChatixSDK ref={this.chatixSDK} setMe={this.setMe} updateChatroomTitle={this.setChatroomTitle} setChatroomMembers={this.setChatroomMembers} addChatroomMember={this.addChatroomMember} removeChatroomMember={this.removeChatroomMember} setChatroomMessages={this.setChatroomMessages} onNewMessageReceived={this.onNewMessageReceived} onMemberUpdated={this.onMemberUpdated} />
做完了! \ , , / .
alekseyso
其他材料:
SDK Chatix ()
SDK Chatix (npm)
192 -