ساخت ویجت گفتگوی زنده با پشتیبانی در ری اکت (React) — از صفر تا صد
گفتگوی زنده یک روش پشتیبانی از مشتری است که سابقهای طولانی دارد. این روش سریع و کارآمد است چون در آن هر کارمند میتواند همزمان به مشتریان زیادی کمک بکند. بهترین نکته این است که میتوان در طی فرایند خرید به سریعترین روش ممکن به سؤالهای مشتری پاسخ داد و بدین ترتیب احتمال خرید مشتری بالاتر میرود.
در این مقاله به روش یکپارچهسازی یک گفتگوی زنده در اپلیکیشنهای React میپردازیم. قصد ما این است که شیوه یکپارچه قابلیت گفتگوی زنده در اپلیکیشن ریاکت را بدون نگرانی از نگهداری سرور گفتگو و معماری آن پیادهسازی کنیم. در ادامه تصویری از آن چه قصد داریم بسازیم را مشاهده میکنید.
ما برای راهاندازی بخش گفتگوی اپلیکیشن خود از خدمات CometChat Pro استفاده میکنیم. CometChat Pro یک API ارتباطی قدرتمند است که امکان افزودن قابلیتهای گفتگو را به اپلیکیشن فراهم میسازد. این API با قابلیت یکپارچهسازی آسان و مستندات منظم به شما کمک میکند تا ویژگی گفتگوی زنده را با نوشتن چند خط کد به اپلیکیشن خود اضافه کنید. به این منظور ابتدا باید یک حساب رایگان در این وبسایت (+) بسازید.
ما در این راهنما علاوه بر CometChat از فناوریهای زیر نیز استفاده خواهیم کرد:
- Create React App
- react-chat-widget
- Express
- Bootstrap
- Axios
- react-md (spinner component only)
پیشنهاد ما این است که تا انتهای این راهنما همراه باشید تا گامبهگام اپلیکیشن مورد نظر خود را بسازیم، اما اگر بیش از این شتاب دارید، میتوانید کد کامل این مقاله را در این صفحه گیتهاب (+) ملاحظه کنید.
ایجاد اپلیکیشن CometChat
چنان که پیشتر گفتیم برای گنجاندن قابلیت گفتگوی زنده در اپلیکیشن از CometChat استفاده میکنیم. با این وجود، پیش از یکپارچهسازی باید ابتدا اپلیکیشن CometChat را بسازیم.
برای ایجاد یک اپلیکیشن CometChat باید به داشبورد CometChat بروید و روی آیکون + کلیک کنید. ما اپلیکیشن خود را react-chat-widget مینامیم، اما شما میتوانید اپلیکیشن خود را با هر نامی که دوست دارید ایجاد کنید.
ما دو نوع کاربر داریم که به گفتگوی ما اتصال مییابند. یک دسته مشتریهایی هستند که ویجت گفتگو را باز میکنند و دیگری کارمندان بخش پشتیبانی هستند که به گفتگو دسترسی مییابند و از طریق داشبورد به پرسوجوها پاسخ میدهند. به طور کلی «کاربر» مفهومی بنیادی در CometChat محسوب میشود.
از آنجا که ما احتمالاً مشتریان زیادی خواهیم داشت، برای هر مشتری که به گفتگو وصل میشود، باید به صورت دینامیک یک کاربر CometChat ایجاد کنیم. با این وجود از آنجا که تنها یک کارمند وجود خواهد داشت، میتوانیم یک Agent را از پیش در داشبورد ایجاد کنیم.
به این منظور در داشبورد به برگه Users بروید و روی Create User کلیک کنید:
برای ID کاربر مقدار ecommerce-agent را وارد کنید و برای نام کارمند نیز مقدار Demo Agent را وارد کنید. اگر قصد دارید از مراحل این راهنما پیروی کنید، از همین نامها استفاده کنید، چون در غیر این صورت در ادامه با مشکل مواجه خواهید شد. ID کاربر را یادداشت کنید، زیرا در ادامه به آن ارجاع خواهیم داد.
پیش از آن که از داشبورد خارج شوید و به کدنویسی بپردازید، باید یک کلید دسترسی کامل CometChat نیز بسازید. در همین صفحه روی برگه API Keys و سپس روی Create API Key کلیک کنید:
ما کلید خود را react-chat-api نامیدهایم، اما نام آن اهمیت چندانی ندارد. کلید API و ID اپلیکیشن خود را نیز مانند ID کاربر در جایی یادداشت کنید، چون در ادامه لازم خواهد شد.
راهاندازی اکسپرس
در گام قبلی یک کلید دسترسی کامل ساختیم که میتوانیم از آن برای ایجاد دینامیک CometChat استفاده کنیم. با این که میتوانیم این کار را در سمت کلاینت نیز اجرا کنیم، اما معنی آن این خواهد بود که کلید دسترسی کامل خصوصی خود را در معرض دسترس عموم قرار میدهیم که کار نادرستی است. برای جلوگیری از این مشکل یک سرور اکسپرس میسازیم که شرایط زیر را داشته باشد:
- کاربر CometChat را با استفاده از کلید دسترسی کامل بسازد.
- توکنهای احراز هویت را بازگشت دهد (در ادامه بیشتر توضیح خواهیم داد)
- لیستی از کاربران CometChat برای استفاده بعدی در داشبورد بازگشت دهد.
اینک نوبت آغاز کار است. ابتدا یک دایرکتوری خالی جدید برای اپلیکیشن اکسپرس خود ایجاد میکنیم و دستور `npm init -y` را اجرا میکنیم:
mkdir react-express-chat-widgetcd react-express-chat-widgetnpm init –y
سپس اکسپرس و axios را نصب میکنیم:
npm install express axios
سپس در فایلی به نام server.js کد زیر را وارد میکنیم:
1const express = require('express');
2const axios = require('axios');
3const app = express();
4
5const appID = '{appID}';
6const apiKey = '{apiKey}';
7const agentUID = '{agentUID}';
8
9const url = 'https://api.cometchat.com/v1';
10
11const headers = {
12 'Content-Type': 'application/json',
13 appid: appID,
14 apikey: apiKey,
15};
در فایل فوق موارد زیر وجود دارند:
- اطلاعات هویتی اپلیکیشن و ID کاربر پاسخگو که قبلاً ایجاد کردیم ذخیره میشوند.
- UIRL مربوط به API-ی CometChat برای دسترسی راحتتر تعریف میشود.
- یک شیء headers که با استفاده از appID و apiKey ایجاد میشود. ما این هدر را به همراه هر درخواست CometChat ارسال میکنیم.
در همین فایل اکنون یک مسیر تعریف میکنیم تا ایجاد کاربران جدید CometChat را مدیریت کنیم. برای ایجاد یک کاربر جدید باید یک درخواست POST را با UID و نام کاربر ارسال کنیم.
در این راهنما، نام یکسانی را برای همه مشتریان به صورت hard-code مینویسیم، یعنی همه مشتریها را «customer» مینامیم، اما UID آنها باید یکتا باشد. برای UID میتوانیم از تابع POST برای ایجاد ID-های یکتا استفاده کنیم.
کد زیر را به فایل server.js اضافه کنید:
1app.get('/api/create', (req, res) => {
2 const data = {
3 uid: new Date().getTime(),
4 name: 'customer',
5 };
6 axios
7 .post(`${url}/users`, JSON.stringify(data), {
8 headers,
9 })
10 .then(response => {
11 requestAuthToken(response.data.data.uid)
12 .then(token => {
13 console.log('Success:' + JSON.stringify(token));
14 res.json(token);
15 })
16 .catch(error => console.error('Error:', error));
17 })
18 .catch(error => console.error('Error:', error));
19});
20
21app.get('/api/auth', (req, res) => {
22 const uid = req.query.uid;
23 requestAuthToken(uid)
24 .then(token => {
25 console.log('Success:' + JSON.stringify(token));
26 res.json(token);
27 })
28 .catch(error => console.error('Error:', error));
29});
30
31const requestAuthToken = uid => {
32 return new Promise((resolve, reject) => {
33 axios
34 .post(`${url}/users/${uid}/auth_tokens`, null, {
35 headers,
36 })
37 .then(response => {
38 console.log('New Auth Token:', response.data);
39 resolve(response.data.data);
40 })
41 .catch(error => reject(error));
42 });
43};
زمانی که این مسیر فراخوانی شود، اکسپرس کارهای زیر را انجام میدهد:
- یک درخواست POST به آدرس https://api.cometchat.com/v1/users با headers صحیح و اطلاعاتی در مورد کاربر جدید ارسال میکند.
- توکن احراز هویت را برای کاربر جدید واکشی میکند.
- و در نهایت آن را به فراخواننده بازمیگرداند.
ما همچنین یک تابع به نام requestAuthToken میسازیم تا به واکشی کردن توکن احراز هویت کمک کند. سپس در همان فایل یک مسیر احراز هویت میسازیم که آن را برای ایجاد توکن جهت کاربران بازگشتی فراخوانی خواهیم کرد:
1app.get('/api/auth', (req, res) => {
2 const uid = req.query.uid;
3 requestAuthToken(uid)
4 .then(token => {
5 console.log('Success:' + JSON.stringify(token));
6 res.json(token);
7 })
8 .catch(error => console.error('Error:', error));
9});
در نهایت یک تابع برای بازگشت لیستی از کاربران ایجاد کرده و agent یا همان کارمند پشتیبانی را از آن حذف میکنیم. ما این endpoint را متعاقباً از داشبورد فراخوانی میکنیم تا لیستی از کاربران را که agent میتواند با آنها صحبت کند نمایش دهیم. البته کارمند ما نمیتواند با خودش صحبت کند و از این رو خودش را از لیست فیلتر میکنیم:
1app.get('/api/users', (req, res) => {
2 axios
3 .get(`${url}/users`, {
4 headers,
5 })
6 .then(response => {
7 const { data } = response.data;
8 const filterAgentData = data.filter(data => {
9 return data.uid !== agentUID;
10 });
11 res.json(filterAgentData);
12 })
13 .catch(error => console.error('Error:', error));
14});
در انتهای فایل server.js، سرور را اجرا میکنیم:
1const PORT = process.env.PORT || 5000;
2app.listen(PORT, () => {
3 console.log(`Listening on port ${PORT}`);
4});
اگر از ابتدای این مقاله با ما همگام بوده باشید، اینک فایل Server.js باید به صورت زیر در آمده باشد:
1
2const express = require('express');
3const axios = require('axios');
4const app = express();
5
6const appID = '{appID}';
7const apiKey = '{apiKey}';
8const agentUID = '{agentUID}';
9
10const url = 'https://api.cometchat.com/v1';
11
12const headers = {
13 'Content-Type': 'application/json',
14 appid: appID,
15 apikey: apiKey,
16};
17
18app.get('/api/create', (req, res) => {
19 const data = {
20 uid: new Date().getTime(),
21 name: 'customer',
22 };
23 axios
24 .post(`${url}/users`, JSON.stringify(data), {
25 headers,
26 })
27 .then(response => {
28 requestAuthToken(response.data.data.uid)
29 .then(token => {
30 console.log('Success:' + JSON.stringify(token));
31 res.json(token);
32 })
33 .catch(error => console.error('Error:', error));
34 })
35 .catch(error => console.error('Error:', error));
36});
37
38app.get('/api/auth', (req, res) => {
39 const uid = req.query.uid;
40 requestAuthToken(uid)
41 .then(token => {
42 console.log('Success:' + JSON.stringify(token));
43 res.json(token);
44 })
45 .catch(error => console.error('Error:', error));
46});
47
48const requestAuthToken = uid => {
49 return new Promise((resolve, reject) => {
50 axios
51 .post(`${url}/users/${uid}/auth_tokens`, null, {
52 headers,
53 })
54 .then(response => {
55 console.log('New Auth Token:', response.data);
56 resolve(response.data.data);
57 })
58 .catch(error => reject(error));
59 });
60};
61
62app.get('/api/users', (req, res) => {
63 axios
64 .get(`${url}/users`, {
65 headers,
66 })
67 .then(response => {
68 const { data } = response.data;
69 const filterAgentData = data.filter(data => {
70 return data.uid !== agentUID;
71 });
72 res.json(filterAgentData);
73 })
74 .catch(error => console.error('Error:', error));
75});
76
77const PORT = process.env.PORT || 5000;
78app.listen(PORT, () => {
79 console.log(`Listening on port ${PORT}`);
80});
در یک پنجره ترمینال دستور node server.js را اجرا کنید و منتظر باشید تا پیامی به صورت زیر نمایش یابد:
Listening on port 5000
اکنون باید زمان مناسبی برای تست endpoint-ها به همراه curl یا Postman باشد تا مطمئن شویم که کار میکنند و سپس به بخش کدنویسی کلاینت بپردازیم.
راهاندازی اپلیکیشن React
درون دایرکتوری خود دستور npx create-react-app را اجرا کنید تا ساختار اولیه یک اپلیکیشن ریاکت ایجاد شود:
npx create-react-app client
ساختار پوشه شما اینک باید مانند زیر باشد:
|-- express-react-chat-widget |-- package-lock.json |-- package.json |-- server.js |-- client |--.gitignore |-- package-lock.json |-- package.json |-- public |-- src
زمانی که اپلیکیشن ریاکت آماده شد، به دایرکتوری client بروید و ماژولهای زیر را نصب کنید:
cd clientnpm install @cometchat-pro/chat react-chat-widget react-router-dom bootstrap react-md-spinner
اپلیکیشن Create React برای bootstrap کردن یک اپلیکیشن ریاکت کاملاً مفید است، اما فایلهای زیادی تولید میکند که مورد نیاز ما نیستند و اینها شامل فایلهای تست و از این دست هستند. پیش از اقدام به کدنویسی، همه چیز را از دایرکتوری client/src حذف کنید تا از صفر کار خود را شروع کنیم.
برای شروع یک فایل config.js با ID اپلیکیشن و UID کارمند در مسیر client/src/config.js با محتوای زیر بسازید:
1const config = {
2 appID: '{appID}',
3 agentUID: '{agentUID}',
4}
5
6export default config;
این کد مبنایی است که میتوان برای ارجاع به اطلاعات احراز هویت CometChat از هر کجا مورد استفاده قرار داد. با این که ما آن را کد مبنا مینامیم، اما این فرصت را نیز داریم که یک فایل index.css بسازیم:
1body {
2 margin: 0;
3 padding: 0;
4 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 sans-serif;
7 -webkit-font-smoothing: antialiased;
8 -moz-osx-font-smoothing: grayscale;
9}
10
11code {
12 font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 monospace;
14}
15
16
17.message {
18 overflow: hidden;
19}
20
21.balon1 {
22 float: right;
23 background: #35cce6;
24 border-radius: 10px;
25}
26
27.balon2 {
28 float: left;
29 background: #f4f7f9;
30 border-radius: 10px;
31}
ما این فایل را در ادامه از داشبورد مورد ارجاع قرار میدهیم. اکنون در فایل با نام index.js کد زیر را درج کنید:
1import React from 'react';
2import ReactDOM from 'react-dom';
3import 'bootstrap/dist/css/bootstrap.css';
4import './index.css';
5import App from './App';
6import { CometChat } from '@cometchat-pro/chat';
7import config from './config';
8
9CometChat.init(config.appID)
10
11ReactDOM.render(<App />, document.getElementById('root'));
در این کد ما Bootstrap ،CometChat و فایل پیکربندی را که پیش از مقداردهی اولیه CometChat و رندر کردن App ساختهایم ایمپورت میکنیم. اگر در این راهنما با ما همگام بوده باشید، متوجه شدهاید که ما هنوز App را تعریف نکردهایم. بنابراین در این مرحله این کار را انجام میدهیم. در فایل به نام App.js کد زیر را درج کنید:
1import React from 'react';
2import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
3
4import Client from './Client';
5import Agent from './Agent';
6
7const App = () => {
8 return (
9 <Router>
10 <React.Fragment>
11 <ul>
12 <li>
13 <Link to='/'>Client Home</Link>
14 </li>
15 <li>
16 <Link to='/agent'>Agent Dashboard</Link>
17 </li>
18 </ul>
19 <hr />
20 <Route exact path='/' component={Client} />
21 <Route path='/agent' component={Agent} />
22 </React.Fragment>
23 </Router>
24 );
25}
26
27export default App;
در این کد ما دو مسیر را تعریف کردیم:
- مسیر / یا Customer home جهت برقراری اتصال با کارمند پشتیبانی است.
- و مسیر agent/ یا Agent Dashboard برای دسترسی سریع و راحت به داشبورد تعریف شده است.
در ادامه ابتدا کامپوننت در دید مشتری را بررسی میکنیم. ما آن را کامپوننت کلاینت مینامیم.
ایجاد کامپوننت کلاینت
کامپوننت کلاینت ما دو مسئولیت عمده خواهد داشت:
- ایجاد یک کاربر CometChat جدید از طریق سرور Express در زمانی که نخستین مشتری وصل میشود.
- ارسال و دریافت پیامها به صورت آنی.
یک فایل به نام Client.js بسازید و کد زیر را در آن درج کنید:
1import React, {Component} from 'react';
2import {Widget, addResponseMessage, addUserMessage, dropMessages} from 'react-chat-widget';
3import {CometChat} from '@cometchat-pro/chat';
4
5import config from './config';
6import 'react-chat-widget/lib/styles.css';
7
8const agentUID = config.agentUID;
9const CUSTOMER_MESSAGE_LISTENER_KEY = "client-listener";
10const limit = 30;
11
12class Client extends Component {
13 componentDidMount() {
14 addResponseMessage('Welcome to our store!');
15 addResponseMessage('Are you looking for anything in particular?');
16
17 let uid = localStorage.getItem("cc-uid");
18 // check for uid, if exist then get auth token, login, create message listener and fetch previous messages
19 if ( uid !== null) {
20 this.fetchAuthToken(uid).then(
21 result => {
22 console.log('auth token fetched', result);
23 CometChat.login(result.authToken)
24 .then( user => {
25 console.log("Login successfully:", { user });
26 this.createMessageListener();
27 this.fetchPreviousMessages();
28
29 })
30 },
31 error => {
32 console.log('Initialization failed with error:', error);
33 }
34 );
35 }
36 }
37
38 fetchAuthToken = async uid => {
39 const response = await fetch(`/api/auth?uid=${uid}`)
40 const result = await response.json()
41 return result;
42 }
43
44 createUser = async () => {
45 const response = await fetch(`/api/create`)
46 const result = await response.json()
47 return result;
48 }
49
50 createMessageListener = () => {
51 CometChat.addMessageListener(
52 CUSTOMER_MESSAGE_LISTENER_KEY,
53 new CometChat.MessageListener({
54 onTextMessageReceived: message => {
55 console.log("Incoming Message Log", { message });
56 addResponseMessage(message.text);
57 }
58 })
59 );
60 }
61
62 fetchPreviousMessages = () => {
63 var messagesRequest = new CometChat.MessagesRequestBuilder()
64 .setUID(agentUID)
65 .setLimit(limit)
66 .build();
67
68 messagesRequest.fetchPrevious().then(
69 messages => {
70 console.log("Message list fetched:", messages);
71 messages.forEach( message => {
72 if(message.receiver !== agentUID){
73 addResponseMessage(message.text);
74 } else {
75 addUserMessage(message.text)
76 }
77 });
78 },
79 error => {
80 console.log("Message fetching failed with error:", error);
81 }
82 );
83 }
84
85 handleNewUserMessage = newMessage => {
86 console.log(`New message incoming! ${newMessage}`);
87 var textMessage = new CometChat.TextMessage(
88 agentUID,
89 newMessage,
90 CometChat.MESSAGE_TYPE.TEXT,
91 CometChat.RECEIVER_TYPE.USER
92 );
93 let uid = localStorage.getItem("cc-uid");
94
95 if (uid === null) {
96 this.createUser().then(
97 result => {
98 console.log('auth token fetched', result);
99 localStorage.setItem("cc-uid",result.uid)
100 CometChat.login(result.authToken)
101 .then(user => {
102 console.log("Login successfully:", { user });
103 CometChat.sendMessage(textMessage).then(
104 message => {
105 console.log('Message sent successfully:', message);
106 },
107 error => {
108 console.log('Message sending failed with error:', error);
109 }
110 );
111 CometChat.addMessageListener(
112 CUSTOMER_MESSAGE_LISTENER_KEY,
113 new CometChat.MessageListener({
114 onTextMessageReceived: message => {
115 console.log("Incoming Message Log", { message });
116 addResponseMessage(message.text);
117 }
118 })
119 );
120 })
121 },
122 error => {
123 console.log('Initialization failed with error:', error);
124 })
125 } else {
126 // we have uid, do send
127 CometChat.sendMessage(textMessage).then(
128 message => {
129 console.log('Message sent successfully:', message);
130 },
131 error => {
132 console.log('Message sending failed with error:', error);
133 }
134 );
135 }
136 };
137
138 componentWillUnmount() {
139 CometChat.removeMessageListener(CUSTOMER_MESSAGE_LISTENER_KEY);
140 CometChat.logout();
141 dropMessages();
142 }
143
144 render() {
145 return (
146 <div className='App'>
147 <Widget
148 handleNewUserMessage={this.handleNewUserMessage}
149 title='My E-commerce Live Chat'
150 subtitle='Ready to help you'
151 />
152 </div>
153 );
154 }
155}
156
157export default Client;
اگر فکر میکنید این کد حجم بالایی دارد جای نگرانی نیست چون در ادامه آن را جزء به جزء توضیح میدهیم.
تابع render به قدر کافی ساده است، در واقع وظیفه اصلی آن رندر کردن react-chat-widget است. بخش زیادی از کد اختصاص به مدیریت پیام جدید ارسالی از سوی مشتری در تابعی به نام handleNewUserMessage دارد.
به طور خلاصه، ابتدا باید بررسی کنیم که آیا UID مشتری در localStorage وجود دارد یا نه. اگر چنین باشد، از این UID برای لاگین کردن کاربر و ارسال پیام استفاده میکنیم. در غیر این صورت تابع ()createUser را فراخوانی میکنیم و از مقدار بازگشتی برای لاگین کردن کاربر استفاده میکنیم. این تابع createUser یک endpoint را فراخوانی میکند که قبلاً در همین راهنما تعریف کردیم.
در نهایت در یک تابع چرخه عمر ریاکت componentWillUnmount را فرا میخوانیم و به خاطر میسپاریم که شنونده پیام را حذف کنیم. پیش از ادامه باید به یک نکته کوچک اشاره کنیم. در کد فوق، به جای وارد کردن URL سرور و پورت آن (localhost:5000/users) یا چیزی مانند آن در فرانتاند، میتوانیم یک گزینه proxy به فایل package.json اضافه کنیم. بدین ترتیب میتوانیم به جای localhost:5000/users// صرفاً از /users استفاده کنیم:
"browserslist": [">0.2%"، "not dead"، "not ie <= 11"، "not op_mini all"]،"proxy": http://localhost:5000
در این مرحله اپلیکیشن مانند زیر خواهد بود:
چنان که مشاهده میکنید، میتوان پیامها را ارسال یا دریافت کرد، اما اگر صفحه را رفرش کنیم، پیامهای گفتگو ناپدید میشوند و این خوب نیست.
برای حل این مشکل یک متد componentDidMount تنظیم میکنیم که به دنبال UID مشتری در localStorage میگردد، به طوری که وقتی مشتریان صفحه را رفرش بکنند، میتوانند گفتگو را از همان جایی که مانده بود ادامه دهند.
زمانی که UID را پیدا کردیم، میتوانیم از آن برای مقداردهی اولیه یک زنجیره از متدها جهت login ،fetch کردن پیامهای قبلی و ایجاد listener برای پیامهای ورودی استفاده کنیم.
1 componentDidMount() {
2 addResponseMessage('Welcome to our store!');
3 addResponseMessage('Are you looking for anything in particular?');
4
5 let uid = localStorage.getItem("cc-uid");
6 // check for uid, if exist then get auth token, login, create message listener and fetch previous messages
7 if ( uid !== null) {
8 this.fetchAuthToken(uid).then(
9 result => {
10 console.log('auth token fetched', result);
11 CometChat.login(result.authToken)
12 .then( user => {
13 console.log("Login successfully:", { user });
14 this.createMessageListener();
15 this.fetchPreviousMessages();
16
17 })
18 },
19 error => {
20 console.log('Initialization failed with error:', error);
21 }
22 );
23 }
24 }
25
26 fetchAuthToken = async uid => {
27 const response = await fetch(`/api/auth?uid=${uid}`)
28 const result = await response.json()
29 return result;
30 }
31
32 createUser = async () => {
33 const response = await fetch(`/api/create`)
34 const result = await response.json()
35 return result;
36 }
37
38 createMessageListener = () => {
39 CometChat.addMessageListener(
40 CUSTOMER_MESSAGE_LISTENER_KEY,
41 new CometChat.MessageListener({
42 onTextMessageReceived: message => {
43 console.log("Incoming Message Log", { message });
44 addResponseMessage(message.text);
45 }
46 })
47 );
48 }
49
50 fetchPreviousMessages = () => {
51 var messagesRequest = new CometChat.MessagesRequestBuilder()
52 .setUID(agentUID)
53 .setLimit(limit)
54 .build();
55
56 messagesRequest.fetchPrevious().then(
57 messages => {
58 console.log("Message list fetched:", messages);
59 messages.forEach( message => {
60 if(message.receiver !== agentUID){
61 addResponseMessage(message.text);
62 } else {
63 addUserMessage(message.text)
64 }
65 });
66 },
67 error => {
68 console.log("Message fetching failed with error:", error);
69 }
70 );
71 }
اکنون اگر صفحه را رفرش کنیم، اپلیکیشن تلاش خواهد کرد در CometChat لاگین کند و پیامهای قبلی را به صورت خودکار با گشتن به دنبال UID مشتری در localStorage بارگذاری کند و این وضعیت مناسبی محسوب میشود.
اما همچنان مشکل کوچکی وجود دارد. چنان که مشخص شد، هنوز راهی برای کارمند پشتیبانی وجود ندارد که به پیام مشتری پاسخ دهد. ما این مشکل را از طریق ساختن داشبورد کارمند حل میکنیم. در این حالت کارمند میتواند به پیامهای گفتگو که از سوی مشتریان میرسند پاسخ دهد. بدن ترتیب کار ما در فایل Client.js به پایان میرسد و در ادامه به ساخت فایل Agent.js میپردازیم.
ایجاد کامپوننت agent
کارکرد اصلی داشبورد agent دریافت همه مشتریان از CometChat Pro و نمایش تمام پیامهای ورودی از مشتریان جدید در لیست گفتگوی مشتریان کارمند است تا بتواند روی آنها کلیک کرده و پاسخ دهد. در واقع کارکرد اصلی بسیار شبیه به کلاینت است.
هنگام استفاده از CometChat بهراحتی میتوان چندین کارمند ایجاد کرد، اما برای این که همه چیز ساده بماند و درگیر مدیریت کاربر نشویم، در این راهنما از یک کارمند (agent) استفاده میکنیم که قبلاً آن را ایجاد کردهایم.
یک کامپوننت به نام Agent.js ایجاد کنید و حالت اولیه آن را به صورت زیر تعیین کنید:
1import React, {Component} from 'react';
2
3import {CometChat} from '@cometchat-pro/chat';
4import MDSpinner from "react-md-spinner";
5import config from './config';
6
7const agentUID = config.agentUID;
8const AGENT_MESSAGE_LISTENER_KEY = 'agent-listener'
9const limit = 30;
10
11class Agent extends Component {
12
13 state = {
14 customers: [],
15 selectedCustomer: '',
16 chat: [],
17 chatIsLoading: false,
18 customerIsLoading:true
19 }
در همان فایل، یک متد به نام componentDidMount ایجاد کنید:
1componentDidMount() {
2 this.fetchAuthToken(agentUID).then(authToken => {
3 console.log('auth token fetched', authToken);
4 CometChat.login(authToken).then(user => {
5 console.log("Login successfully:", {
6 user
7 }); // after login, fetch all users // put them into customer state
8 this.fetchUsers().then(result => {
9 this.setState({
10 customers: result,
11 customerIsLoading: false
12 })
13 });
14 CometChat.addMessageListener(AGENT_MESSAGE_LISTENER_KEY, new CometChat.MessageListener({
15 onTextMessageReceived: message => {
16 let {
17 customers,
18 selectedCustomer,
19 chat
20 } = this.state;
21 console.log("Incoming Message Log", {
22 message
23 }); // check incoming message // if from the same customer agent is currently chatting // push a new chat item into chat state
24 if (selectedCustomer === message.sender.uid) {
25 chat.push(message);
26 this.setState({
27 chat
28 })
29 } else { // if new customer, push a new customer into customer state
30 let aRegisteredCustomer = customers.filter(customer => {
31 return customer.uid === message.sender.uid
32 });
33 if (!aRegisteredCustomer.length) {
34 customers.push(message.sender) this.setState({
35 customers
36 })
37 }
38 }
39 }
40 }));
41 })
42 }, error => {
43 console.log('Initialization failed with error:', error);
44 });
45}
46fetchUsers = async () => {
47 const response = await fetch(`/api/users`) const result = await response.json() return result;
48}
در کد فوق کارهای زیادی انجام مییابند. در ادامه آنها را به اختصار مرور میکنیم.
- ابتدا به صورت خودکار agent را لاگین کرده و همه کاربران گفتگوی مربوطه را از سرور واکشی میکنیم.
- سپس یک شنونده پیام ورودی میسازیم. هر بار که یک پیام در گفتگوی منتخب دریافت میشود، آن را به «حالت گفتگو» (Chat State) ارسال میکنیم که به نوبه خود موجب بهروزرسانی UI میشود.
- اگر پیام ورودی از گفتگوی انتخاب شده کنونی نباشد، بررسی میکنیم که آیا پیام جدید از مشتری e ثبت نام کرده آمده است یا نه. اگر چنین نبود، مشتری جدید را به «حالت مشتری» ارسال میکنیم.
احتمالاً به خاطر دارید که API اکسپرس را قبلاً برای دریافت لیستی از کاربران ثبت نام کرده ساختیم. از این API برای تشکیل لیستی از کاربران در سمت چپ داشبورد استفاده میکنیم. این لیست را با استفاده از ترکیب کلاسهای بوتاسترپ و فایل index.css که قبلاً تعریف کردیم، در سمت چپ داشبورد قرار میدهیم.
سپس یک تابع رندر میسازیم. این تابع اینترفیس گفتگو را رندر میکند و آن را با استفاده از بوتاسترپ استایلدهی میکند. برای این که خواندن کد آسانتر شود، CustomerList و ChatBox را در کامپوننتهای خاص خود قرار میدهیم که میتوانند در فایل یکسانی تعریف شوند:
1render() {
2 return ( < div className = 'container-fluid' > < div className = 'row' > < div className = 'col-md-2' > < /div> <
3 div className = "col-md-8 h-100pr border rounded" > < div className = 'row' > < div className = 'col-lg-4 col-xs-12 bg-light'
4 style = {
5 {
6 height: 658
7 }
8 } > < div className = 'row p-3' > < h2 > Customer List < /h2 > < /div > < div className = 'row ml-0 mr-0 h-75 bg-white border rounded'
9 style = {
10 {
11 height: '100%',
12 overflow: 'auto'
13 }
14 } > {
15 / * The CustomerList component * /
16 } < CustomerList {
17 ...this.state
18 }
19 selectCustomer = {
20 this.selectCustomer
21 }
22 / > < /div > < /div > < div className = 'col-lg-8 col-xs-12 bg-light'
23 style = {
24 {
25 height: 658
26 }
27 } > < div className = 'row p-3 bg-white' > < h2 > Who you gonna chat with ? < /h2> </div >
28 <
29 div className = 'row pt-5 bg-white'
30 style = {
31 {
32 height: 530,
33 overflow: 'auto'
34 }
35 } > {
36 /* The ChatBox component */
37 } < ChatBox {
38 ...this.state
39 }
40 /> </div > < div className = "row bg-light"
41 style = {
42 {
43 bottom: 0,
44 width: '100%'
45 }
46 } > < form className = "row m-0 p-0 w-100"
47 onSubmit = {
48 this.handleSubmit
49 } > < div className = "col-9 m-0 p-1" > < input id = "text"
50 className = "mw-100 border rounded form-control"
51 type = "text"
52 name = "text"
53 ref = "message"
54 placeholder = "Type a message..." / > < /div> <
55 div className = "col-3 m-0 p-1" > < button className = "btn btn-outline-secondary rounded border w-100"
56 title = "Send"
57 style = {
58 {
59 paddingRight: 16
60 }
61 } > Send < /button > < /div > < /form > < /div > < /div > < /div > < /div > < /div > < /div > )
62 }
کامپوننت Chatbox
1class ChatBox extends Component {
2 render(){
3 const {chat, chatIsLoading} = this.props;
4 if (chatIsLoading) {
5 return (
6 <div className='col-xl-12 my-auto text-center'>
7 <MDSpinner size='72'/>
8 </div>
9 )
10 }
11 else {
12 return (
13 <div className='col-xl-12'>
14 {
15 chat
16 .map(chat =>
17 <div key={chat.id} className="message">
18 <div className={`${chat.receiver !== agentUID ? 'balon1': 'balon2'} p-3 m-1`}>
19 {chat.text}
20 </div>
21 </div>)
22 }
23 </div>
24 )
25 }
26 }
27
28}
کامپوننت CustomerList
1class CustomerList extends Component {
2 render(){
3 const {customers, customerIsLoading, selectedCustomer} = this.props;
4 if (customerIsLoading) {
5 return (
6 <div className='col-xl-12 my-auto text-center'>
7 <MDSpinner size='72'/>
8 </div>
9 )
10 }
11 else {
12 return (
13 <ul className="list-group list-group-flush w-100">
14 {
15 customers
16 .map(customer =>
17 <li
18 key={customer.uid}
19 className={`list-group-item ${customer.uid === selectedCustomer ? 'active':''}`}
20 onClick={() => this.props.selectCustomer(customer.uid)}>{customer.name} </li>)
21 }
22 </ul>
23 )
24 }
25 }
26}
بدین ترتیب مبنایی برای UI ایجاد میشود، اما هنوز امکان ارسال پیام وجود ندارد. برای ارسال پیامها باید یک دستگیره برای مواردی که کارمند یک پیام جدید را تحویل میدهد داشته باشیم. شیوه ارسال پیامها اینک باید برای شما روشن باشد، زیرا از همان فراخوانی sendMessage که در سمت کلاینت استفاده کردیم بهره بخواهیم گرفت.
1 handleSubmit = event => {
2 event.preventDefault();
3 let message = this.refs.message.value;
4
5 var textMessage = new CometChat.TextMessage(
6 this.state.selectedCustomer,
7 message,
8 CometChat.MESSAGE_TYPE.TEXT,
9 CometChat.RECEIVER_TYPE.USER
10 );
11
12 CometChat.sendMessage(textMessage).then(
13 message => {
14 let {chat} = this.state;
15 console.log('Message sent successfully:', message);
16 chat.push(message);
17 this.setState({
18 chat
19 })
20 },
21 error => {
22 console.log('Message sending failed with error:', error);
23 }
24 );
25 this.refs.message.value='';
26 }
همچنین میخواهیم کارمند را قادر سازیم تا سوابق پیامها را مانند حالتی که برای مشتری ایجاد کردیم، ببیند:
1 selectCustomer = uid => {
2 this.setState({
3 selectedCustomer: uid
4 }, ()=> {this.fetchPreviousMessage(uid)})
5 }
6
7 fetchPreviousMessage = uid => {
8 this.setState({
9 chat: [],
10 chatIsLoading: true
11 }, () => {
12 var messagesRequest = new CometChat.MessagesRequestBuilder()
13 .setUID(uid)
14 .setLimit(limit)
15 .build();
16
17 messagesRequest.fetchPrevious().then(
18 messages => {
19 console.log("Message list fetched:", messages);
20 this.setState({
21 chat: messages,
22 chatIsLoading: false
23 })
24 },
25 error => {
26 console.log("Message fetching failed with error:", error);
27 }
28 );
29 });
30 }
به خاطر داشته باشید که شنونده پیام را در زمان حذف کامپوننت پاک کنید:
1 componentWillUnmount(){
2 CometChat.removeMessageListener(AGENT_MESSAGE_LISTENER_KEY);
3 CometChat.logout();
4 }
محصول نهایی به صورت زیر خواهد بود:
اگر کنجکاو هستید که کاربران Superhero از کجا میآیند باید اشاره کنیم که این کاربران به صورت خودکار از سوی CometChat Pro در زمان ایجاد اپلیکیشن جدید ایجاد میشوند. پیش از استفاده عملیاتی از اپلیکیشن حتماً آنها را حذف کنید.
اکنون کارمند پشتیبانی و مشتریان آماده گفتگو با همدیگر هستند. شما میتوانید Client Home و Agent Dashboard را در پنجرههای جداگانهای باز کرده و این موضوع را بررسی کنید.
سخن پایانی
بدین ترتیب ما موفق شدیم ویجت گفتگوی زنده کاملاً اختصاصی خود را برای اپلیکیشن React در زمانی بسیار کوتاه بسازیم. در واقع، CometChat Pro به ما امکان میدهد که پیامها را با نوشتن تنها چند خط کد ارسال و دریافت کنیم. بدین ترتیب دیگر نیاز به راهاندازی سرور گفتگوی شخصی و معماری آن وجود ندارد. البته این سرویس قابلیتهای بسیار بیشتری از ساخت یک ویجت گفتگو دارد که بررسی همه آنها از حیطه این مقاله خارج است.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش React.js در کمتر از ۵ دقیقه — از صفر تا صد
- راهنمای جامع React (بخش اول) — از صفر تا صد
- آموزش ری اکت (React) — مجموعه مقالات مجله فرادرس
==