即時數據是當今現代應用程式的核心支柱之一。擁有能夠發送雙向訊息的系統使我們能夠及時了解各種資訊。此類範例可以包括訊息傳遞應用程式、用於財務資料的儀表板的資料分析、用於增強和虛擬應用程式的平視顯示器 (HUD)。就像駕駛飛機的戰鬥機飛行員或使用 Apple Vision Pro 耳機的休閒消費者。這項技術有無數的用例。
在談論一般 IM 應用程式時,能夠與某人即時交談為許多獨特的可能性打開了大門。由於這些新發現的功能,我們的世界變得更加互聯。在今天的文章中,我們將在建立即時訊息應用程式時了解有關訊息傳遞的所有知識。該應用程式將能夠連接到兩個不同的即時應用程式平台:Pusher 和 PubNub。
我們的應用程式將與 WhatsApp 和 Telegram 應用程式的基本準系統版本非常相似。可以開啟多個網頁瀏覽器或瀏覽器標籤,這將使我們能夠與多個使用者一起登入。這樣我們就可以測試多個用戶之間的群聊,就像它是一個真正的聊天應用程式一樣。
下面您可以看到我們的主儀表板是什麼樣子,您可以看到有一些按鈕可以導航到我們的訊息應用程式的兩個版本:
在這裡您可以在螢幕截圖中看到我們應用程式的 PubNub 版本的樣子。它由藍色標題表示:
Pusher 版本有一個綠色標題,讓您可以直觀地分辨出兩者之間的差異:
好的,我們的快速介紹完成後,我們現在可以更徹底地了解它們之間的對比。訊息應用程式的程式碼庫可以在我的 GitHub 上找到:https://github.com/andrewbaisden/realtime-chat-app。
在我們開始之前,請先了解這些先決條件,並確保在我們開始之前您已完成所有設定。
兩個平台有很多共同點,但也有一些明顯的差異,這使得它們很容易區分。主要問題之一是它們的架構不一樣。
就 PubNub 而言,它是一個可以處理資料和訊息流的雲端平台。延遲非常低,服務可用性良好,因為它在全球範圍內可用。該平台很好地處理了擴展和基礎設施,因此開發人員可以自由地處理重要的專案。
現在,透過 Pusher,他們提供了自架和雲端託管的不同部署選項。當使用自架時,Pusher 能夠在您自己的自訂硬體或軟體上運行,這給您很大的自由。當談到雲端託管解決方案時,您可以期待與 PubNub 類似的服務。
當我們比較這些功能時,我們可以看到它們都提供了多種程式語言支援的 SDK 和函式庫。其中包括 JavaScript、Python、Java、Swift、Ruby 等。兩個平台上都提供頻道,這使我們能夠發布和訂閱各種數據流。狀態是另一種選擇,透過它,我們可以在我們設定的不同管道上即時查看所有用戶的線上和離線狀態。關於訊息歷史記錄,這是一個類似的故事,這也適用於推播通知。
另一個偉大的方面是它們功能豐富,而且文件非常詳盡,使得學習和掌握所提供的不同怪癖變得非常容易。您可以在下面找到它們各自的文件:
PubNub 文件:PubNub
Pusher 文件:Pusher
是時候開發我們的應用程式了!首先確保您在 PubNub 和 Pusher 上有一個帳戶,我們現在將快速完成在兩個平台上建立帳戶的過程,所以如果您還沒有建立帳戶,請繼續操作。
從PubNub開始,進入網站首頁,點選右上角免費試用按鈕,如下圖。
您現在應該看到註冊頁面,因此請繼續使用表格建立您的帳戶。
好的,現在您建立的帳戶應該可以存取儀表板了。使用選單轉到“鍵集”部分,然後找到您的應用程式及其鍵集。如果還沒有應用程序,這也是您應該創建應用程式的部分。
您的 API 金鑰現在應該可見,因此請確保存在性和持久性選項處於開啟狀態,因為我們需要它來追蹤使用者和資料。
是的,我們已經完成了 PubNub 帳戶的操作,現在讓我們開始處理 Pusher 帳戶吧。像以前一樣,前往網站並點擊頁面右上角的註冊按鈕。
現在在下面的畫面上,使用表單建立帳戶,如下所示。
必須建立一個頻道,因此請在儀表板頁面上選擇管理按鈕來執行此操作。
在下一頁(應該是頻道螢幕)上,使用“創建應用程式”按鈕創建一個應用程序,如此螢幕截圖所示。
現在我們可以進入表單,正如您在本範例中看到的,配置應該根據您的用例量身定制。
因此,一旦該階段完成,頻道頁面應該像這裡一樣顯示。
點擊側邊欄選單中的應用程式金鑰,然後您將可以存取稍後需要的 API 金鑰。
太棒了,帳戶設定階段現已完成,在下一節中,我們將開始編寫應用程式的程式碼。第一步將是使用資料夾等建立專案架構...我繼續為命令列建立了一個複製和貼上腳本,這樣我們就不必手動寫出所有命令。首先在電腦上建立一個名為 realtime-chat-app 的項目,然後使用命令列轉到該位置。
使用 Next.js 建立項目,然後使用 shell 腳本設定項目。
我們首先執行此命令來建立 Next.js 專案:
npx create-next-app client
現在在設定畫面上選擇 Tailwind CSS 和 App Router 很重要,因為我們在這個專案中需要這些選項。
這裡是用於建立檔案和資料夾並安裝依賴項的 shell 腳本,只需在同一資料夾中執行它,它就會自動 cd 到客戶端資料夾。如果您已經位於用戶端資料夾中,則可以省略腳本中的第一行。
cd client npm install @pubnub/react-chat-components axios pubnub pubnub-react pusher pusher-js touch .env.local cd src/app mkdir components pubnub pusher touch components/ChatInterface.js components/ChatMessage.js components/ChatPubNub.js components/ChatPusher.js components/DashboardButton.js components/Header.js components/UserLogin.js pubnub/page.js pusher/page.js cd ../../.. mkdir server cd server npm init -y npm install express cors pusher dotenv touch index.js .env cd ..
這裡發生了很多事情,所以讓我們看看這個腳本在做什麼:
看看這個螢幕截圖,這就是我們的專案在 IDE 中的外觀:
The hard work is done that build script did most of the work we just have to add the code to the files.
Right our first file is the globals.css file so clear all the code in that file and replace it with what is shown here:
@tailwind base; @tailwind components; @tailwind utilities; body { background-color: rgb(15 23 42); }
Code cleanup has been done in this file and now we have a background colour for our application.
Onto the layout.css file. Continuing from what we just did replace all the code with this new code:
import { Ubuntu } from 'next/font/google'; import './globals.css'; const ubuntu = Ubuntu({ subsets: ['latin'], weight: ['300', '400', '500', '700'], }); export const metadata = { title: 'Realtime Chat App', description: 'Generated by create next app', }; export default function RootLayout({ children }) { return ( <html lang="en"> <body className={ubuntu.className}>{children}</body> </html> ); }
Ubuntu is now the default font in our application.
All thats left in this section is to replace all of the code in our next.config.mjs file with this code:
/** @type {import('next').NextConfig} */ const nextConfig = { images: { remotePatterns: [ { hostname: 'res.cloudinary.com', }, ], }, }; export default nextConfig;
Cloudinary was added as an image host so now we can use it throughout our app.
And with that the project setup phase is done so in the next section we will work on the main files for our codebase. First we will do PubNub and then we will do Pusher.
The initilisation and configuration part will be the first one to tackle. You must sign into your PubNub account, and find the application you made at the start. Locate your Keysets as well as the Publish and Subscribe Keys. Now put them inside of the .env.local file in the project.
Here is an example of where they should be put:
NEXT_PUBLIC_PUBNUB_PUBLISH_KEY=your-publish-key NEXT_PUBLIC_PUBNUB_SUBSCRIBE_KEY=your-subscribe-key
Time to add some code to our components/ChatPubNub.js file, and this is the file where we can find the main code for subscribing to channels, handling the presence and publishing our message.
Put the code you see here into the components/ChatPubNub.js file:
import { useState, useEffect, useRef } from 'react'; import PubNub from 'pubnub'; import ChatInterface from './ChatInterface'; export default function ChatPubNub({ activeUser }) { const [chats, setChats] = useState([]); const [count, setCount] = useState(1); const bottomRef = useRef(null); let pubnub; const channelName = 'presence-chatroom'; useEffect(() => { pubnub = new PubNub({ publishKey: process.env.NEXT_PUBLIC_PUBNUB_PUBLISH_KEY, subscribeKey: process.env.NEXT_PUBLIC_PUBNUB_SUBSCRIBE_KEY, uuid: activeUser, }); pubnub.addListener({ message: (messageEvent) => { const chat = messageEvent.message; setChats((prevChats) => [...prevChats, chat]); }, }); const presenceChannelName = `${channelName}-pnpres`; pubnub.subscribe({ channels: [channelName], withPresence: true, presenceChannels: [presenceChannelName], }); const scrollToBottom = () => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }; scrollToBottom(); return () => { pubnub.unsubscribeAll(); }; }, [chats, activeUser, count]); const handleKeyUp = (evt) => { const value = evt.target.value; if (evt.keyCode === 13 && !evt.shiftKey) { const chat = { user: activeUser, message: value, timestamp: +new Date() }; evt.target.value = ''; pubnub.publish({ channel: channelName, message: chat, }); } }; return activeUser ? ( <> <ChatInterface activeUser={activeUser} count={count} chats={chats} handleKeyUp={handleKeyUp} bottomRef={bottomRef} /> </> ) : null; }
Here is a quick explanation of what our code does in this file. We can do subscriptions to channels, publish messages to different channels as well as handle the overall presence information. With these capabilities our app will function a lot like a group chat and with presence switched on we can see how many users are online in real-time.
We can work on our next file now which is the components/ChatInterface.js so add this code to it:
import ChatMessage from './ChatMessage'; export default function ChatInterface({ activeUser, count, chats, handleKeyUp, bottomRef, }) { return ( <div className="flex"> <aside className="w-64 min-w-64 bg-slate-800 p-2"> <h1 className="text-lg text-white">Chats</h1> <div className="flex justify-between mt-2"> <div className="flex"> <div className="rounded-full bg-slate-100 h-10 w-10 mr-2"></div> <div className="text-sm"> <p className="text-white">John</p> <p className="text-slate-400">Hello world</p> </div> </div> <div className="text-sm text-slate-400">Friday</div> </div> </aside> <section className="grow"> <div className="bg-zinc-800 p-2"> <div className="flex"> <div className="rounded-full bg-slate-100 h-10 w-10 mr-2"></div> <div> <h1 className="text-2xl text-white">{activeUser}</h1> <span className="text-gray-300">users online: {count}</span> </div> </div> </div> <div className="bg-zinc-900 p-2 overflow-y-auto h-80 max-h-80 text-white"> {chats.map((chat, index) => { const previous = Math.max(0, index - 1); const previousChat = chats[previous]; const position = chat.user === activeUser ? 'right' : 'left'; const isFirst = previous === index; const inSequence = chat.user === previousChat.user; const hasDelay = Math.ceil( (chat.timestamp - previousChat.timestamp) / (1000 * 60) ) > 1; return ( <div key={index}> {(isFirst || !inSequence || hasDelay) && ( <div> <span>{chat.user || 'Anonymous'}</span> </div> )} <ChatMessage message={chat.message} position={position} /> </div> ); })} <div ref={bottomRef} />{' '} </div> <div className="w-full bg-zinc-800 p-2"> <textarea onKeyUp={handleKeyUp} placeholder="Enter a message" className="w-full block rounded mt-2 mb-2 p-2 text-white bg-zinc-600" ></textarea> </div> </section> </div> ); }
Our component displays the UI for our messaging chat interface. There is a section for messaging, sending messages and a sidebar, which could hold users when they are in the group.
The next component is for the components/ChatMessage.js file and this has our chat message interface.
Add this code to the file:
export default function ChatMessage({ message }) { return ( <div> <div className="mt-4 mb-4"> <span className="bg-zinc-600 p-2 rounded">{message}</span> </div> </div> ); }
Chat bubbles should become possible thanks to this component whenever we use the chat to send messages to users.
Dashboard buttons is what we require next so add this code to our components/DashboardButton.js file:
import Link from 'next/link'; import Image from 'next/image'; export default function DashboardButton({ url, img, alt }) { return ( <> <Link href={url}> <div className="rounded mr-4 bg-slate-50 hover:bg-slate-200 h-96 w-96 text-center flex items-center justify-center drop-shadow-lg uppercase"> <Image src={img} height={200} width={200} alt={alt} /> </div> </Link> </> ); }
We can now easily navigate between the PubNub and Pusher versions of our real-time messaging chat app using these reusable buttons.
Ok the navigation component is next and this is for our main header. Put this code in our file at components/Header.js:
import Link from 'next/link'; export default function Header() { return ( <> <nav className="bg-white flex justify-around p-8 mb-4 font-bold"> <Link href={'/'}>Dashboard</Link> <Link href={'/pusher'}>Pusher Chat App</Link> <Link href={'/pubnub'}>PubNub Chat App</Link> </nav> </> ); }
All our page routes are easily able to be navigated using this header component which has page links.
The login screen is next and this is the code our file at components/UserLogin.js desires:
import { useState } from 'react'; import ChatPubNub from '../components/ChatPubNub'; import ChatPusher from '../components/ChatPusher'; export default function UserLogin({ bgColor, appName }) { const [user, setUser] = useState(null); const handleKeyUp = (evt) => { if (evt.keyCode === 13) { const newUser = evt.target.value; setUser(newUser); } }; return ( <> <main> <div> <section> <div className={`p-4 ${bgColor} text-slate-100`}> <span> {user ? ( <span className="flex justify-between text-white"> <span> {user} <span>is online</span> </span> <span>{appName}</span> </span> ) : ( <span className="text-2xl text-white"> What is your name? </span> )} </span> {!user && ( <input type="text" onKeyUp={handleKeyUp} autoComplete="off" className="w-full block rounded mt-2 mb-2 p-2 text-black" /> )} </div> </section> {appName === 'PubNub Chat' ? ( <section>{user && <ChatPubNub activeUser={user} />}</section> ) : ( <section>{user && <ChatPusher activeUser={user} />}</section> )} </div> </main> </> ); }
Its a pretty straightforward login screen component whereby a user can choose a name and then they get redirected to the messaging app. There is logic to check which app a user is using and it automatically sends the user to the right chat interface for that version of the app.
Lets do the pubnub/page.js route file now and add this code to it:
'use client'; import Header from '../components/Header'; import UserLogin from '../components/UserLogin'; export default function PubNub() { return ( <div> <Header /> <UserLogin bgColor={'bg-sky-800'} appName={'PubNub Chat'} /> </div> ); }
We can find the main page route for the PubNub version messaging app.
Lastly we must add code to our page.js file in the root folder to complete our application so like before just replace the code with what we have written here:
'use client'; import DashboardButton from './components/DashboardButton'; export default function Home() { return ( <> <div className="h-screen flex justify-center items-center"> <div className="text-center"> <h1 className="mb-4 text-white text-4xl">Choose a messaging app</h1> <div className="grid gap-2 lg:grid-cols-2 md:grid-cols-1 sm:grid-cols-1"> <DashboardButton url={'/pusher'} img={ 'https://res.cloudinary.com/d74fh3kw/image/upload/v1715187329/pusher-logo_u0gljx.svg' } alt="Pusher Logo" /> <DashboardButton url={'/pubnub'} img={ 'https://res.cloudinary.com/d74fh3kw/image/upload/v1715189173/pubnub-logo-vector_olhbio.png' } alt="PubNub Logo" /> </div> </div> </div> </> ); }
Our main dashboard link can be found here which has the buttons for our PubNub and Pusher version of our application.
The PubNub messaging part of our application should be good to go now! Just cd into the client folder if you have not done so already and start the application with the usual Next.js run command:
npm run dev
Its worth mentioning that the Pusher part of our application is not going to work yet as we must complete the integrations in the upcoming section. To use the PubNub app go to the login screen, enter a name and hit the enter button and then you will see the messenger chat application screen. You can see your online status and the sidebar has a hard-coded user which is just an example.
To make the application more interactive open more browser tabs or browser windows and sign in with more users. Having a real-time group chat is now possible just like any other messaging app you are familiar with.
In the next section we shall get Pusher up and working.
This section will take less time because we get to reuse a lot of the components we used in the earlier sections. The difference this time around is that Pusher will need to connect to our backend server to work.
Like before we are going to start with the configuration files for our .env.local and .env files in the server and client folders. We need to add the same secrets to the files. Find your application on the Pusher platform and then find the App keys.
The App keys must be added to the env files with the right variables. Take a note of this key difference. Our client .env.local env file must have NEXT_PUBLIC at the start, and the .env file in the server folder does not require it and you can see that in the examples below.
Here is our .env.local file which is in the client folder:
NEXT_PUBLIC_PUBNUB_PUBLISH_KEY=your-publish-key NEXT_PUBLIC_PUBNUB_SUBSCRIBE_KEY=your-subscribe-key NEXT_PUBLIC_PUSHER_APP_ID=your-app-id NEXT_PUBLIC_PUSHER_APP_KEY=your-app-key NEXT_PUBLIC_PUSHER_APP_SECRET=your-app-secret NEXT_PUBLIC_PUSHER_APP_CLUSTER=your-cluster
And this is the .env file which can be found in the server folder:
PUSHER_APP_ID=your-app-id PUSHER_APP_KEY=your-app-key PUSHER_APP_SECRET=your-app-secret PUSHER_APP_CLUSTER=your-cluster
Time to work on the frontend so you know the drill add this code to the components/ChatPusher.js file:
import { useState, useEffect, useRef } from 'react'; import axios from 'axios'; import Pusher from 'pusher-js'; import ChatInterface from './ChatInterface'; export default function ChatPusher({ activeUser }) { const [chats, setChats] = useState([]); const [count, setCount] = useState(0); const bottomRef = useRef(null); let channel; let pusher; useEffect(() => { pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_APP_KEY, { cluster: process.env.NEXT_PUBLIC_PUSHER_APP_CLUSTER, useTLS: true, channelAuthorization: { endpoint: 'http://localhost:8000/auth', }, }); channel = pusher.subscribe('presence-chatroom'); channel.bind('new-message', ({ chat = null }) => { if (chat) { setChats((prevChats) => [...prevChats, chat]); } }); channel.bind('pusher:subscription_succeeded', () => { updateMemberCount(channel); }); channel.bind('pusher:member_added', () => { updateMemberCount(channel); }); channel.bind('pusher:member_removed', () => { updateMemberCount(channel); }); const scrollToBottom = () => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }; scrollToBottom(); return () => { pusher.disconnect(); }; }, [chats]); const updateMemberCount = (presenceChannel) => { const memberCount = Object.keys(presenceChannel.members.members).length; console.log('Count people online', memberCount); setCount(memberCount); }; const handleKeyUp = (evt) => { const value = evt.target.value; if (evt.keyCode === 13 && !evt.shiftKey) { const chat = { user: activeUser, message: value, timestamp: +new Date() }; evt.target.value = ''; axios.post('http://localhost:8000/message', chat); } }; return activeUser ? ( <> <ChatInterface activeUser={activeUser} count={count} chats={chats} handleKeyUp={handleKeyUp} bottomRef={bottomRef} /> </> ) : null; }
There are similarities here to our PubNub component which matches this. We can subscribe to the channels, publish different messages and handle the presence. Although all of this is now done via the backend which now also works with basic authentication. Unlike the PubNub version however this version can accurately see how many users are online as well as when users are active, join and leave the group chat.
An authentication route is present for authenticating users and we incorporated a message route for posting all messages to our server.
Our frontend is almost completed just one file remains so add this code to our pusher.page.js file now:
'use client'; import Header from '../components/Header'; import UserLogin from '../components/UserLogin'; export default function Pusher() { return ( <div> <Header /> <UserLogin bgColor={'bg-emerald-800 '} appName={'Pusher Chat'} /> </div> ); }
This file ensures that our Pusher version has a working route. All thats left is to get the messaging app up and running when we do the server file next.
Before we do that we should setup our run script in our package.json file so add this script to it:
"scripts": { "start": "node index.js" },
Alright, last file! Add this code to our index.js file in the server folder so we can complete the backend:
const cors = require('cors'); const Pusher = require('pusher'); const express = require('express'); require('dotenv').config(); const crypto = require('crypto'); const dev = process.env.NODE_ENV !== 'production'; const port = process.env.PORT || 8000; const pusher = new Pusher({ appId: process.env.PUSHER_APP_ID, key: process.env.PUSHER_APP_KEY, secret: process.env.PUSHER_APP_SECRET, cluster: process.env.PUSHER_APP_CLUSTER, useTLS: true, }); const server = express(); server.use(cors()); server.use(express.json()); server.use(express.urlencoded({ extended: false })); const chatHistory = { messages: [] }; server.post('/message', (req, res) => { const { user = null, message = '', timestamp = +new Date() } = req.body; const chat = { user, message, timestamp }; chatHistory.messages.push(chat); pusher.trigger('presence-chatroom', 'new-message', { chat }); res.status(200).send('Message sent successfully.'); }); server.post('/messages', (req, res) => { res.json({ ...chatHistory, status: 'success' }); }); server.post('/auth', (req, res) => { const socketId = req.body.socket_id; const channel = req.body.channel_name; console.log('Socket and channel', socketId, channel); const userId = crypto.randomBytes(8).toString('hex'); const presenceData = { user_id: userId, user_info: { name: 'Anonymous User', }, }; const auth = pusher.authorizeChannel(socketId, channel, presenceData); res.send(auth); }); server.listen(port, (err) => { if (err) throw err; console.log(`Server is running on port: ${port} http://localhost:${port}`); });
Just a quick run through of this file so we can understand how it works. If you don't know its an Express server file which can connect to the Pusher API and it has routes for the authentication, message posting, in addition to getting chat history for all messages.
For connectivity, cors is implemented, so we don't get any of those annoying errors when trying to connect to different servers. The crypto module is used to doing various tasks like hash generation and encrypting and decrypting data.
With our codebase at MVP status, all you have to do is run the backend server in a different terminal window with the following command as shown below:
npm run start
So our server will run on port 8000 and you can change this in the server code if need be. Of course our Next.js application runs on port 3000, they need to be on different ports for obvious reasons. You already know how to use the PubNub version, the Pusher version works much the same.
Congratulations you have reached the end of this tutorial and created a working real-time messaging application!
Thats it we have completed the tutorial, learned about both real-time messaging applications and built a working demo application. As you have learned both platforms offer a similar feature set although Pusher has self-hosted and cloud options whereas PubNub offers only the later.
Ultimately your choice of platform will come down to what you make of their pros and cons. They have a free plan, so testing them is pretty easy to do. Pusher has flexible pricing in contrast to PubNubs strict pricing that offers much cheaper starter options priced at $98 compared to $49 for the Pusher startup option.
以上是PubNub 與 Pusher 在 React 中創建即時訊息應用程式的詳細內容。更多資訊請關注PHP中文網其他相關文章!