如何用Vanilla JS、Twilio和Node.js建立一个群聊应用
聊天正在成为商业和社会背景下越来越受欢迎的交流媒介。企业将聊天用于客户和员工的公司内部交流,比如用Slack、Microsoft Teams、Chanty、HubSpot Live Chat、Help Scout等。大多数社交网络和通信应用程序也默认提供聊天选项,如Instagram、Facebook、Reddit和Twitter。其他应用程序如Discord、Whatsapp和Telegram大多是基于聊天的,群聊是其主要功能之一。
虽然存在众多促进聊天的产品,但您可能需要为您的网站定制一个适合您特定交流需求的解决方案。例如,这些产品中有许多是独立的应用程序,可能无法整合到您自己的网站中。让您的用户离开您的网站去聊天可能不是最好的选择,因为这可能会影响用户体验和转换。反过来说,从头开始建立一个聊天应用程序可能是一项艰巨的任务,有时甚至是压倒性的。然而,通过使用Twilio Conversations等API,您可以简化创建过程。这些通信API处理小组创建、添加参与者、发送消息、通知以及其他重要的聊天功能。使用这些API的后端应用程序只需处理认证和对这些API进行调用。然后,前端应用程序显示来自后台的对话、群组和消息。
在本教程中,你将学习如何使用Twilio Conversations API创建一个群组聊天应用程序。这个应用程序的前端将使用HTML、CSS和Vanilla JavaScript构建。它将允许用户创建群聊,发送邀请,登录,以及发送和接收消息。后台将是一个Node.js应用程序。它将为聊天邀请者提供认证令牌并管理聊天创建。
先决条件
在你开始本教程之前,你需要具备以下条件:
- 安装Node.js。
*你可以使用Node.js下载页面上*的预制安装程序来获得它,它主要用于后端应用程序和在前端应用程序中安装依赖。 - 一个Twilio账户。
,你可以通过 这个链接在Twilio网站上创建一个。 http-server来提供前端应用程序。
你可以通过运行npm i -g http-server来安装它。你也可以用npx http-server来一次性运行它。- MongoDB,用于后台应用的会话存储。
它的安装页面*有一个关于如何运行它的详细指南。
后台应用程序
要使用Twilio API发送聊天信息,你需要一个对话。聊天信息是在一个对话中发送和接收的。发送消息的人被称为参与者。参与者只有被添加到对话中,才能在其中发送消息。对话和参与者都是使用Twilio API创建的。后台应用程序将执行这一功能。
参与者需要一个**访问令牌**来发送消息和获取他们订阅的对话。这个项目的前端部分将使用这个访问令牌。后台应用程序创建该令牌并将其发送到前端。在那里它将被用来加载对话和消息。
项目启动器
你将调用后端应用程序twilio-chat-server 。Github上有一个脚手架式的项目启动程序。要克隆该项目并获得启动程序,请运行:
git clone https://github.com/zaracooper/twilio-chat-server.git
cd twilio-chat-server
git checkout starter
后台应用程序采用这种结构:
.
├── app.js
├── config/
├── controllers/
├── package.json
├── routes/
└── utils/
要运行该应用程序,你将使用node index.js 命令。
依赖性
后台应用程序需要8个依赖项。你可以通过运行:来安装它们:
npm i
下面是每个依赖项的列表:
connect-mongo连接到MongoDB,你将使用它作为会话存储。cors处理CORS。dotenv从 文件中加载环境变量,你将在后面的步骤中创建该文件。.envexpress是你将在后端使用的Web框架。express-session提供中间件来处理会话数据。http-errors帮助创建服务器错误。morgan处理日志。twilio创建Twilio客户端,生成令牌,创建对话,并添加参与者。
配置
config 文件夹负责从环境变量加载配置。配置分为三类:CORS、Twilio和MongoDB会话数据库的配置。当环境为development 时,你将使用dotenv 从.env 文件加载config 。
首先在终端上创建.env 文件。这个文件已经被添加到.gitignore 文件中,以防止它包含的敏感值被检入版本库:
touch .env
下面是你的.env 应该是这样的:
# Session DB Config
SESSION_DB_HOST=XXXX
SESSION_DB_USER=XXXX
SESSION_DB_PASS=XXXX
SESSION_DB_PORT=XXXX
SESSION_DB_NAME=XXXX
SESSION_DB_SECRET=XXXX
# Twilio Config
TWILIO_ACCOUNT_SID=XXXX
TWILIO_AUTH_TOKEN=XXXX
TWILIO_API_KEY=XXXX
TWILIO_API_SECRET=XXXX
# CORS Client Config
CORS_CLIENT_DOMAIN=XXXX
你可以从这个MongoDB手册条目中了解如何为你的会话数据库创建一个用户。一旦你创建了一个会话数据库和一个可以写入它的用户,你就可以填写SESSION_DB_USER,SESSION_DB_PASS, 和SESSION_DB_NAME 值。如果你正在运行MongoDB的本地实例,SESSION_DB_HOST 将是localhost ,而SESSION_DB_PORT 通常是27017 。SESSION_DB_SECRET 被 express-session 用来签署会话 ID cookie,它可以是你设置的任何秘密字符串。
在下一步,你将从Twilio控制台获得凭证。这些凭证应该被分配给带有TWILIO_ 前缀的变量。在本地开发期间,前端客户端将运行在http://localhost:3000。所以,你可以使用这个值作为CORS_CLIENT_DOMAIN 环境变量。
添加以下代码到 config/index.js来加载环境变量:
import dotenv from 'dotenv';
if (process.env.NODE_ENV == 'development') {
dotenv.config();
}
const corsClient = {
domain: process.env.CORS_CLIENT_DOMAIN
};
const sessionDB = {
host: process.env.SESSION_DB_HOST,
user: process.env.SESSION_DB_USER,
pass: process.env.SESSION_DB_PASS,
port: process.env.SESSION_DB_PORT,
name: process.env.SESSION_DB_NAME,
secret: process.env.SESSION_DB_SECRET
};
const twilioConfig = {
accountSid: process.env.TWILIO_ACCOUNT_SID,
authToken: process.env.TWILIO_AUTH_TOKEN,
apiKey: process.env.TWILIO_API_KEY,
apiSecret: process.env.TWILIO_API_SECRET
};
const port = process.env.PORT || '8000';
export { corsClient, port, sessionDB, twilioConfig };
环境变量根据它们的作用被分成了不同的类别。每个配置类别都有自己的对象变量,它们都被导出,以便在应用程序的其他部分使用。
从控制台获取Twilio凭证
要建立这个项目,你需要四个不同的Twilio凭证:一个账户SID,一个Auth Token,一个API密钥,和一个API秘密。在控制台中,在*常规设置页面*,向下滚动到API凭证部分。在这里你可以找到你的账户SID和Auth Token。
要获得一个API密钥和秘密,请到 API密钥页面.你可以在下面的截图中看到它。点击"+"按钮,进入 "新API密钥 "页面。
在API密钥列表页面创建API Key 按钮。(大图预览
)
Standard在这个页面上,添加一个密钥名称,把KEY TYPE ,然后点击创建API密钥。复制API密钥和秘密。你将在一个.env 文件中添加所有这些凭证,你将在后续步骤中看到。
实用程序
后台应用程序需要两个工具函数。一个将创建一个令牌,另一个将包裹异步控制器并为其处理错误。
在 utils/token.js中,添加以下代码,创建一个名为createToken 的函数,该函数将生成Twilio访问令牌。
import { twilioConfig } from '../config/index.js';
import twilio from 'twilio';
function createToken(username, serviceSid) {
const AccessToken = twilio.jwt.AccessToken;
const ChatGrant = AccessToken.ChatGrant;
const token = new AccessToken(
twilioConfig.accountSid,
twilioConfig.apiKey,
twilioConfig.apiSecret,
{ identity: username }
);
const chatGrant = new ChatGrant({
serviceSid: serviceSid,
});
token.addGrant(chatGrant);
return token.toJwt();
}
在这个函数中,你使用你的账户SID、API密钥和API秘密生成访问令牌。你可以选择提供一个独特的身份,可以是一个用户名、电子邮件等。创建令牌后,您必须为其添加一个聊天授权。聊天授权可以接受一个对话服务ID和其他可选的值。最后,你要将令牌转换为JWT并返回。
该 utils/controller.js文件中包含一个asyncWrapper 函数,用于包装异步控制器函数并捕获它们抛出的任何错误。将以下代码粘贴到该文件中。
function asyncWrapper(controller) {
return (req, res, next) => Promise.resolve(controller(req, res, next)).catch(next);
}
export { asyncWrapper, createToken };
控制器
后台应用程序有四个控制器:两个用于认证,两个用于处理对话。第一个认证控制器创建一个令牌,第二个控制器删除它。其中一个对话控制器创建新的对话,而另一个则将参与者添加到现有的对话中。
会话控制器
在 controllers/conversations.js文件中,为StartConversation 控制器添加这些导入和代码。
import { twilioConfig } from '../config/index.js';
import { createToken } from '../utils/token.js';
import twilio from 'twilio';
async function StartConversation(req, res, next) {
const client = twilio(twilioConfig.accountSid, twilioConfig.authToken);
const { conversationTitle, username } = req.body;
try {
if (conversationTitle && username) {
const conversation = await client.conversations.conversations
.create({ friendlyName: conversationTitle });
req.session.token = createToken(username, conversation.chatServiceSid);
req.session.username = username;
const participant = await client.conversations.conversations(conversation.sid)
.participants.create({ identity: username })
res.send({ conversation, participant });
} else {
next({ message: 'Missing conversation title or username' });
}
}
catch (error) {
next({ error, message: 'There was a problem creating your conversation' });
}
}
StartConversation 控制器首先创建一个Twilioclient ,使用你的twilioConfig.accountSid 和twilioConfig.authToken ,你从config/index.js 得到。
接下来,它创建一个对话。它需要一个对话的标题,它从请求体中得到这个标题。一个用户必须被添加到一个对话中,然后才能参与其中。如果没有访问令牌,参与者不能发送消息。因此,它使用请求正文中提供的用户名和conversation.chatServiceSid ,生成一个访问令牌。然后由用户名识别的用户被添加到对话中。控制器通过响应新创建的对话和参与者来完成。
接下来,你需要创建AddParticipant 控制器。要做到这一点,在你刚才在上面的controllers/conversations.js 文件中添加以下代码。
async function AddParticipant(req, res, next) {
const client = twilio(twilioConfig.accountSid, twilioConfig.authToken);
const { username } = req.body;
const conversationSid = req.params.id;
try {
const conversation = await client.conversations.conversations
.get(conversationSid).fetch();
if (username && conversationSid) {
req.session.token = createToken(username, conversation.chatServiceSid);
req.session.username = username;
const participant = await client.conversations.conversations(conversationSid)
.participants.create({ identity: username })
res.send({ conversation, participant });
} else {
next({ message: 'Missing username or conversation Sid' });
}
} catch (error) {
next({ error, message: 'There was a problem adding a participant' });
}
}
export { AddParticipant, StartConversation };
AddParticipant 控制器将新的参与者添加到已经存在的对话中。使用作为路由参数提供的conversationSid ,它获取对话。然后,它为用户创建一个令牌,并使用请求正文中的用户名将他们添加到对话中。最后,它将对话和参与者作为一个响应发送出去。
认证控制器
文件中的两个控制器 controllers/auth.js中的两个控制器被称为GetToken 和DeleteToken 。通过复制和粘贴这些代码将它们添加到文件中。
function GetToken(req, res, next) {
if (req.session.token) {
res.send({ token: req.session.token, username: req.session.username });
} else {
next({ status: 404, message: 'Token not set' });
}
}
function DeleteToken(req, res, _next) {
delete req.session.token;
delete req.session.username;
res.send({ message: 'Session destroyed' });
}
export { DeleteToken, GetToken };
GetToken 控制器从会话中检索令牌和用户名(如果它们存在),并将它们作为响应返回。DeleteToken 删除会话。
路由
routes 文件夹有三个文件。index.js,conversations.js, 和auth.js 。
在文件中添加这些授权路由到 routes/auth.js文件中,添加这段代码。
import { Router } from 'express';
import { DeleteToken, GetToken } from '../controllers/auth.js';
var router = Router();
router.get('/', GetToken);
router.delete('/', DeleteToken);
export default router;
GET 路由在/ 路径上返回一个令牌,而DELETE 路由删除一个令牌。
接下来,复制并粘贴以下代码到 routes/conversations.js文件。
import { Router } from 'express';
import { AddParticipant, StartConversation } from '../controllers/conversations.js';
import { asyncWrapper } from '../utils/controller.js';
var router = Router();
router.post('/', asyncWrapper(StartConversation));
router.post('/:id/participants', asyncWrapper(AddParticipant));
export default router;
在这个文件中,创建了对话的路由器。一个用于创建对话的POST 路径/ 和另一个用于添加参与者的POST 路径/:id/participants 被添加到路由器中。
最后,将下面的代码添加到你的新 routes/index.js文件。
import { Router } from 'express';
import authRouter from './auth.js';
import conversationRouter from './conversations.js';
var router = Router();
router.use('/auth/token', authRouter);
router.use('/api/conversations', conversationRouter);
export default router;
通过在这里添加conversation 和auth 路由器,你将它们分别在/api/conversations 和/auth/token 上提供给主路由器。路由器然后被导出。
后台应用程序
现在是时候把后端部分放在一起了。在文本编辑器中打开该 index.js文件,并粘贴以下代码。
import cors from 'cors';
import createError from 'http-errors';
import express, { json, urlencoded } from 'express';
import logger from 'morgan';
import session from 'express-session';
import store from 'connect-mongo';
import { corsClient, port, sessionDB } from './config/index.js';
import router from './routes/index.js';
var app = express();
app.use(logger('dev'));
app.use(json());
app.use(urlencoded({ extended: false }));
app.use(cors({
origin: corsClient.domain,
credentials: true,
methods: ['GET', 'POST', 'DELETE'],
maxAge: 3600 * 1000,
allowedHeaders: ['Content-Type', 'Range'],
exposedHeaders: ['Accept-Ranges', 'Content-Encoding', 'Content-Length', 'Content-Range']
}));
app.options('*', cors());
app.use(session({
store: store.create({
mongoUrl: `mongodb://${sessionDB.user}:${sessionDB.pass}@${sessionDB.host}:${sessionDB.port}/${sessionDB.name}`,
mongoOptions: { useUnifiedTopology: true },
collectionName: 'sessions'
}),
secret: sessionDB.secret,
cookie: {
maxAge: 3600 * 1000,
sameSite: 'strict'
},
name: 'twilio.sid',
resave: false,
saveUninitialized: true
}));
app.use('/', router);
app.use(function (_req, _res, next) {
next(createError(404, 'Route does not exist.'));
});
app.use(function (err, _req, res, _next) {
res.status(err.status || 500).send(err);
});
app.listen(port);
这个文件从创建Express应用程序开始。然后它设置了JSON和URL编码的有效载荷解析,并添加了日志中间件。接下来,它设置了 CORS 和会话处理。如前所述,MongoDB被用来作为会话存储。
所有这些都设置好后,在配置错误处理之前,它又添加了在前面步骤中创建的路由器。最后,它使应用程序在.env 文件中指定的端口监听和接受连接。如果你没有设置端口,该应用程序将监听端口8000 。
一旦你完成了后端应用程序的创建,确保MongoDB正在运行,并通过在终端运行这个命令来启动它。
NODE_ENV=development npm start
你传递NODE_ENV=development 变量,这样配置就会从本地.env 文件中加载。
前端
这个项目的前端部分有几个功能。它允许用户创建对话,查看他们所参加的对话列表,邀请其他人参加他们创建的对话,并在对话中发送消息。这些作用是由四个页面实现的:
- 会话页面
- 一个聊天页面
- 一个错误页面
- 一个登录页面
你会把前端应用程序称为twilio-chat-app 。Github上有一个脚手架式的启动程序。要克隆该项目并获得启动程序,请运行:
git clone https://github.com/zaracooper/twilio-vanilla-js-chat-app.git
cd twilio-vanilla-js-chat-app
git checkout starter
该应用程序采用这种结构:
.
├── index.html
├── pages
│ ├── chat.html
│ ├── conversation.html
│ ├── error.html
│ └── login.html
├── scripts
│ ├── chat.js
│ ├── conversation.js
│ └── login.js
└── styles
├── chat.css
├── main.css
└── simple-page.css
样式和HTML标记已经被添加到启动程序中的每个页面。本节将只涉及你必须添加的脚本。
依赖性
该应用程序有两个依赖项。 axios和 @twilio/conversations.你将使用axios 来向后端应用程序发出请求,使用@twilio/conversations 来发送和获取信息以及脚本中的对话。你可以在终端上通过运行来安装它们:
npm i
索引页
这个页面作为应用程序的登陆页面。你可以在这里找到这个页面的标记(index.html)。它使用两个CSS样式表。 styles/main.css所有的页面都使用,而 styles/simple-page.css小的、不太复杂的页面使用。
你可以在前面的段落中找到这些样式表的链接内容。下面是这个页面的截图,它看起来像什么。
错误页
当一个错误发生时,这个页面会显示出来。 pages/error.html 的内容可以在这里找到。如果发生错误,用户可以点击按钮进入主页。在那里,他们可以再次尝试他们所尝试的内容。
对话页面
在这个页面上,用户向一个表格提供要创建的对话的标题和他们的用户名。
pages/conversation.html 的内容可以在这里找到。在scripts/conversation.js 文件中添加以下代码:
window.twilioChat = window.twilioChat || {};
function createConversation() {
let convoForm = document.getElementById('convoForm');
let formData = new FormData(convoForm);
let body = Object.fromEntries(formData.entries()) || {};
let submitBtn = document.getElementById('submitConvo');
submitBtn.innerText = "Creating..."
submitBtn.disabled = true;
submitBtn.style.cursor = 'wait';
axios.request({
url: '/api/conversations',
baseURL: 'http://localhost:8000',
method: 'post',
withCredentials: true,
data: body
})
.then(() => {
window.twilioChat.username = body.username;
location.href = '/pages/chat.html';
})
.catch(() => {
location.href = '/pages/error.html';
});
}
当用户点击提交按钮时,createConversation 函数被调用。在该函数中,表单的内容被收集起来,并被用于向后台的POST 请求的正文中,http://localhost:8000/api/conversations/ 。
你将使用axios 来发出请求。如果请求成功,将创建一个对话,并将用户添加到其中。然后用户将被重定向到聊天页面,他们可以在对话中发送消息。
下面是一个对话页面的截图:
聊天页面
在这个页面上,用户将查看他们所参与的对话的列表,并向他们发送信息。你可以在这里找到 pages/chat.html 的标记,在这里找到 styles/chat.css 的样式。
scripts/chat.js 文件一开始就定义了一个命名空间twilioDemo:
window.twilioChat = window.twilioChat || {};
添加下面的initClient 函数。它负责初始化Twilio客户端和加载对话:
async function initClient() {
try {
const response = await axios.request({
url: '/auth/token',
baseURL: 'http://localhost:8000',
method: 'GETget',
withCredentials: true
});
window.twilioChat.username = response.data.username;
window.twilioChat.client = await Twilio.Conversations.Client.create(response.data.token);
let conversations = await window.twilioChat.client.getSubscribedConversations();
let conversationCont, conversationName;
const sideNav = document.getElementById('side-nav');
sideNav.removeChild(document.getElementById('loading-msg'));
for (let conv of conversations.items) {
conversationCont = document.createElement('button');
conversationCont.classList.add('conversation');
conversationCont.id = conv.sid;
conversationCont.value = conv.sid;
conversationCont.onclick = async () => {
await setConversation(conv.sid, conv.channelState.friendlyName);
};
conversationName = document.createElement('h3');
conversationName.innerText = `💬 ${conv.channelState.friendlyName}`;
conversationCont.appendChild(conversationName);
sideNav.appendChild(conversationCont);
}
}
catch {
location.href = '/pages/error.html';
}
};
当页面加载时,initClient 从后台获取用户的访问令牌,然后用它来初始化客户端。一旦客户端被初始化,它就被用来获取用户订阅的所有对话。之后,对话被加载到side-nav 。如果发生任何错误,用户将被发送到错误页面。
setConversion 函数加载一个单一的对话。在文件中复制并粘贴下面的代码来添加它:
async function setConversation(sid, name) {
try {
window.twilioChat.selectedConvSid = sid;
document.getElementById('chat-title').innerText = '+ ' + name;
document.getElementById('loading-chat').style.display = 'flex';
document.getElementById('messages').style.display = 'none';
let submitButton = document.getElementById('submitMessage')
submitButton.disabled = true;
let inviteButton = document.getElementById('invite-button')
inviteButton.disabled = true;
window.twilioChat.selectedConversation = await window.twilioChat.client.getConversationBySid(window.twilioChat.selectedConvSid);
const messages = await window.twilioChat.selectedConversation.getMessages();
addMessagesToChatArea(messages.items, true);
window.twilioChat.selectedConversation.on('messageAdded', msg => addMessagesToChatArea([msg], false));
submitButton.disabled = false;
inviteButton.disabled = false;
} catch {
showError('loading the conversation you selected');
}
};
当用户点击一个特定的对话时,setConversation 被调用。这个函数接收对话的SID和名称,并使用SID来获取对话和它的消息。然后,这些消息被添加到聊天区。最后,添加一个监听器,以观察添加到对话中的新消息。当收到这些新消息时,它们被追加到聊天区。如果发生任何错误,会显示错误信息。
这是聊天页面的截图:
接下来,您将添加addMessagedToChatArea ,该函数加载对话消息:
function addMessagesToChatArea(messages, clearMessages) {
let cont, msgCont, msgAuthor, timestamp;
const chatArea = document.getElementById('messages');
if (clearMessages) {
document.getElementById('loading-chat').style.display = 'none';
chatArea.style.display = 'flex';
chatArea.replaceChildren();
}
for (const msg of messages) {
cont = document.createElement('div');
if (msg.state.author == window.twilioChat.username) {
cont.classList.add('right-message');
} else {
cont.classList.add('left-message');
}
msgCont = document.createElement('div');
msgCont.classList.add('message');
msgAuthor = document.createElement('p');
msgAuthor.classList.add('username');
msgAuthor.innerText = msg.state.author;
timestamp = document.createElement('p');
timestamp.classList.add('timestamp');
timestamp.innerText = msg.state.timestamp;
msgCont.appendChild(msgAuthor);
msgCont.innerText += msg.state.body;
cont.appendChild(msgCont);
cont.appendChild(timestamp);
chatArea.appendChild(cont);
}
chatArea.scrollTop = chatArea.scrollHeight;
}
当从侧边导航中选择当前对话时,该函数addMessagesToChatArea 将当前对话的消息添加到聊天区。当有新消息添加到当前对话中时,也会调用它。在获取消息时,通常会显示一个加载消息。在对话信息被添加之前,这个加载信息被删除。来自当前用户的消息被排列在右边,而来自小组参与者的所有其他消息被排列在左边。
这就是加载消息的样子:
添加sendMessage 功能来发送消息。
function sendMessage() {
let submitBtn = document.getElementById('submitMessage');
submitBtn.disabled = true;
let messageForm = document.getElementById('message-input');
let messageData = new FormData(messageForm);
const msg = messageData.get('chat-message');
window.twilioChat.selectedConversation.sendMessage(msg)
.then(() => {
document.getElementById('chat-message').value = '';
submitBtn.disabled = false;
})
.catch(() => {
showError('sending your message');
submitBtn.disabled = false;
});
};
当用户发送消息时,sendMessage 函数被调用。它从文本区获取消息文本并禁用提交按钮。然后使用当前选择的对话,使用其sendMessage 方法发送消息。如果成功,文本区被清除,提交按钮被重新启用。如果不成功,就会显示一个错误信息。
showError 方法在被调用时显示一个错误信息;hideError 则隐藏它:
function showError(msg) {
document.getElementById('error-message').style.display = 'flex';
document.getElementById('error-text').innerText = `There was a problem ${msg ? msg : 'fulfilling your request'}.`;
}
function hideError() {
document.getElementById('error-message').style.display = 'none';
}
这就是这个错误信息的模样:
logout 函数注销了当前用户。它通过向后台发出请求,清除他们的会话来做到这一点。然后用户会被重定向到对话页面,如果他们愿意,可以创建一个新的对话。
function logout(logoutButton) {
logoutButton.disabled = true;
logoutButton.style.cursor = 'wait';
axios.request({
url: '/auth/token',
baseURL: 'http://localhost:8000',
method: 'DELETEdelete',
withCredentials: true
})
.then(() => {
location.href = '/pages/conversation.html';
})
.catch(() => {
location.href = '/pages/error.html';
});
}
添加inviteFriend 功能来发送对话邀请。
async function inviteFriend() {
try {
const link = `http://localhost:3000/pages/login.html?sid=${window.twilioChat.selectedConvSid}`;
await navigator.clipboard.writeText(link);
alert(`The link below has been copied to your clipboard.\n\n${link}\n\nYou can invite a friend to chat by sending it to them.`);
} catch {
showError('preparing your chat invite');
}
}
为了邀请其他人参与对话,当前用户可以向另一个人发送一个链接。这个链接是到登录页面,并包含当前对话的SID作为查询参数。当他们点击邀请按钮时,该链接被添加到他们的剪贴板上。然后会显示一个警告,给出邀请指示。
这里是一个邀请提示的截图:
登录页面
在这个页面上,当用户被邀请参加一个对话时,他们就会登录。你可以在这个链接中找到pages/login.html 的标记。
在scripts/login.js ,login 函数负责登录对话邀请者。复制下面的代码并将其添加到上述文件中。
function login() {
const convParams = new URLSearchParams(window.location.search);
const conv = Object.fromEntries(convParams.entries());
if (conv.sid) {
let submitBtn = document.getElementById('login-button');
submitBtn.innerText = 'Logging in...';
submitBtn.disabled = true;
submitBtn.style.cursor = 'wait';
let loginForm = document.getElementById('loginForm');
let formData = new FormData(loginForm);
let body = Object.fromEntries(formData.entries());
axios.request({
url: `/api/conversations/${conv.sid}/participants`,
baseURL: 'http://localhost:8000',
method: 'POSTpost',
withCredentials: true,
data: body
})
.then(() => {
location.href = '/pages/chat.html';
})
.catch(() => {
location.href = '/pages/error.html';
});
} else {
location.href = '/pages/conversation.html';
}
}
login 函数从URL中获取对话sid 查询参数,从表单中获取用户名。api/conversations/{sid}/participants/ 然后它向后端应用程序的POST 请求。后端应用程序将用户添加到对话中,并生成一个用于消息传递的访问令牌。如果成功,在后端为用户启动一个会话。
然后用户被重定向到聊天页面,但如果请求返回一个错误,他们会被重定向到错误页面。如果URL中没有对话sid 查询参数,用户会被重定向到对话页面。
下面是登录页面的截图:
运行应用程序
在你启动前端应用程序之前,确保后端应用程序正在运行。如前所述,你可以在终端上使用这个命令启动后端应用程序。
NODE_ENV=development npm start
要为前端应用程序提供服务,在另一个终端窗口中运行这个命令。
http-server -p 3000
这样就可以在http://localhost:3000,为应用程序提供服务。一旦运行,就去http://localhost:3000/pages/conversation.html;为你的对话设置一个名称,并添加你的用户名,然后创建它。当你到了聊天页面,点击conversation ,然后点击邀请按钮。
在一个单独的隐身窗口中,粘贴邀请链接,并输入一个不同的用户名。一旦你在隐身窗口的聊天页面上,你就可以开始和自己聊天了。你可以在同一个对话中,在第一个窗口的用户和隐身窗口的第二个用户之间来回发送消息。
总结
在本教程中,你学会了如何使用Twilio Conversations和Vanilla JS创建一个聊天应用。你创建了一个Node.js应用,它可以生成用户访问令牌,为他们维护会话,创建对话,并将用户作为参与者加入其中。你还使用HTML、CSS和Vanilla JS创建了一个前端应用程序。这个应用程序应该允许用户创建对话,发送消息,并邀请其他人来聊天。它应该从后台应用获得访问令牌,并使用它们来执行这些功能。我希望这个教程能让你更好地了解Twilio Conversations是如何工作的,以及如何使用它进行聊天信息传递。










