-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2316 from ably/EDU-1666-Example-Chat-Message-history
[EDU-1666] - Chat message history example
- Loading branch information
Showing
28 changed files
with
5,312 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
VITE_PUBLIC_ABLY_KEY= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
.yarn/install-state.gz | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Message history for chat applications | ||
|
||
This folder contains the code for message history (Typescript) - a demo of how you can leverage [Ably Chat](https://ably.com/docs/products/chat?lang=javascript) to retrieve the historical messages when joining a room. | ||
|
||
## Getting started | ||
|
||
1. Clone the [Ably docs](https://github.com/ably/docs) repository where this example can be found: | ||
|
||
```sh | ||
git clone [email protected]:ably/docs.git | ||
``` | ||
|
||
2. Change directory: | ||
|
||
```sh | ||
cd /examples/chat-room-history/javascript/ | ||
``` | ||
|
||
3. Rename the environment file: | ||
|
||
```sh | ||
mv .env.example .env.local | ||
``` | ||
|
||
4. In `.env.local` update the value of `VITE_PUBLIC_ABLY_KEY` to be your Ably API key. | ||
|
||
5. Install dependencies: | ||
|
||
```sh | ||
yarn install | ||
``` | ||
|
||
6. Run the server: | ||
|
||
```sh | ||
yarn run dev | ||
``` | ||
|
||
7. Try it out by opening two tabs, one to [http://localhost:5173?loadChat=true](http://localhost:5173?loadChat=true) and one to [http://localhost:5173/](http://localhost:5173/) with your browser to see the result. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<link href='https://fonts.googleapis.com/css?family=Inter' rel='stylesheet'> | ||
<link rel="stylesheet" href="src/styles.css" /> | ||
<title>Chat room history</title> | ||
</head> | ||
<body class="font-inter"> | ||
<!-- Landing Page View --> | ||
<div id="landing-page" class="min-h-screen flex items-center justify-center bg-gray-100"> | ||
<div class="bg-gray-300 p-8 rounded-lg shadow-lg w-96"> | ||
<h2 class="text-xl mb-4 text-center">Use the 'Send message' button to send 5 messages before you enter and subscribe to the chat room.</h2> | ||
<div class="flex flex-col gap-4"> | ||
<button id="send-message" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"> | ||
Send message | ||
</button> | ||
<button id="enter-chat" class="bg-gray-500 text-white px-4 py-2 rounded" disabled> | ||
Enter chat | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div id="chat-room-messages" class="container" style="display: none;"> | ||
<div class="flex-1 p:2 sm:p-12 justify-between flex flex-col h-screen"> | ||
<div | ||
id="messages" | ||
class="w-96 flex flex-auto flex-col space-y-2 p-3 overflow-y-auto scrollbar-thumb-blue scrollbar-thumb-rounded scrollbar-track-blue-lighter scrollbar-w-2 scrolling-touch" | ||
> | ||
<button | ||
id="load-past-messages" | ||
class="absolute top-2 left-1/2 transform -translate-x-1/2 bg-blue-500 hover:bg-blue-600 text-white rounded-full p-2" | ||
onclick="event.preventDefault(); getPastMessages();" | ||
> | ||
↑ Load previous messages | ||
</button> | ||
</div> | ||
<div class="border-t-2 border-gray-200 px-4 pt-4 mb-2 sm:mb-0"> | ||
<form class="flex" onsubmit="event.preventDefault(); handleSubmit();"> | ||
<input | ||
type="text" | ||
name="message" | ||
placeholder="Type something..." | ||
class="w-full focus:outline-none focus:placeholder-gray-400 text-gray-600 placeholder-gray-600 pl-2 pr-2 bg-gray-200 rounded-l-md py-1 italic" | ||
autoFocus | ||
/> | ||
<div class="items-center inset-y-0 flex"> | ||
<button | ||
type="submit" | ||
class="inline-flex items-center justify-center rounded-r-md px-3 py-1 transition duration-500 ease-in-out text-white bg-blue-500 hover:bg-blue-400 focus:outline-none" | ||
> | ||
Send | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 20 20" | ||
fill="currentColor" | ||
class="h-6 w-6 ml-2 transform rotate-90" | ||
> | ||
<path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"></path> | ||
</svg> | ||
</button> | ||
</div> | ||
</form> | ||
</div> | ||
</div> | ||
</div> | ||
<script type="module" src="src/script.ts"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "js-chat-room-messages", | ||
"version": "1.0.0", | ||
"main": "index.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "tsc && vite build", | ||
"preview": "vite preview" | ||
}, | ||
"devDependencies": { | ||
"autoprefixer": "^10.4.20", | ||
"dotenv": "^16.4.5", | ||
"postcss": "^8.4.47", | ||
"tailwindcss": "^3.4.13", | ||
"typescript": "^5.6.3", | ||
"@faker-js/faker": "^9.2.0" | ||
}, | ||
"dependencies": { | ||
"@ably/chat": "^0.2.1", | ||
"ably": "^2.3.1", | ||
"nanoid": "^5.0.7", | ||
"vite": "^5.4.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import * as Ably from 'ably'; | ||
import { ChatClient, Message, RoomOptionsDefaults } from '@ably/chat'; | ||
import { faker } from '@faker-js/faker'; | ||
import './styles.css'; | ||
|
||
const realtimeClient = new Ably.Realtime({ | ||
clientId: faker.person.firstName(), | ||
key: import.meta.env.VITE_PUBLIC_ABLY_KEY as string, | ||
}); | ||
// Number of times messages are sent to the chat room before the user enters the room. | ||
let sendCount = 0; | ||
const chatClient = new ChatClient(realtimeClient); | ||
const room = chatClient.rooms.get('chat-room-messages', RoomOptionsDefaults); | ||
|
||
const landingPage = document.getElementById('landing-page'); | ||
const chatRoom = document.getElementById('chat-room-messages'); | ||
|
||
/** Check if url param `loadChat` exists and is set to true to render the chat window instead of the menu */ | ||
const urlParams = new URLSearchParams(window.location.search); | ||
if (urlParams.get('loadChat') === 'true') { | ||
landingPage.style.display = 'none'; | ||
chatRoom.style.display = 'block'; | ||
enterChat(); | ||
} | ||
|
||
function generateRandomMessage() { | ||
const messages = [ | ||
'Hey everyone! Just joined this amazing chat.', | ||
'What a beautiful day for chatting!', | ||
'Hello from the other side of the screen!', | ||
'Anyone here interested in web development?', | ||
'I love how this chat app works!', | ||
'Greetings from a random message generator!', | ||
]; | ||
return messages[Math.floor(Math.random() * messages.length)]; | ||
} | ||
|
||
/** Publish a new message to the chat room. */ | ||
document.getElementById('send-message').addEventListener('click', () => { | ||
sendCount++; | ||
|
||
const randomMessage = generateRandomMessage(); | ||
room.messages.send({ text: randomMessage }); | ||
|
||
const alert = document.createElement('div'); | ||
alert.className = 'fixed left-1/2 top-4 -translate-x-1/2 bg-green-500 text-white px-6 py-3 rounded shadow-lg'; | ||
alert.textContent = `Message: "${randomMessage}" sent to chat room!`; | ||
document.body.appendChild(alert); | ||
|
||
if (sendCount === 5) { | ||
const enterChatButton = document.getElementById('enter-chat') as HTMLButtonElement; | ||
if (enterChatButton) { | ||
enterChatButton.disabled = false; | ||
enterChatButton.className = 'text-white px-4 py-2 rounded bg-green-500 hover:bg-green-600'; | ||
} | ||
} | ||
|
||
setTimeout(() => { | ||
alert.remove(); | ||
}, 3000); | ||
}); | ||
|
||
/** Action to render chat window and subscribe to any new messages received. */ | ||
document.getElementById('enter-chat').addEventListener('click', async () => { | ||
landingPage.style.display = 'none'; | ||
chatRoom.style.display = 'block'; | ||
await enterChat(); | ||
}); | ||
|
||
const getPastMessages = async () => { | ||
/** 💡 Add past 10 messages published to the room 💡 */ | ||
const pastMessages = await room.messages.get({ limit: 10 }); | ||
|
||
pastMessages.items.forEach((message) => { | ||
const messageExists = document.getElementById(message.timeserial); | ||
|
||
if (messageExists) { | ||
return null; | ||
} | ||
|
||
addMessage(message, 'before'); | ||
const messagesContainer = document.getElementById('messages'); | ||
messagesContainer.scrollTop = messagesContainer.scrollHeight; | ||
}); | ||
|
||
const loadButton = document.getElementById('load-past-messages'); | ||
if (loadButton instanceof HTMLButtonElement) { | ||
loadButton.disabled = true; | ||
loadButton.className = 'absolute top-2 left-1/2 transform -translate-x-1/2 bg-gray-300 text-white rounded-full p-2'; | ||
} | ||
}; | ||
|
||
async function enterChat() { | ||
room.messages.subscribe((message) => { | ||
addMessage(message.message); | ||
|
||
const messagesContainer = document.getElementById('messages'); | ||
messagesContainer.scrollTop = messagesContainer.scrollHeight; | ||
}); | ||
|
||
/** 💡 Attach to the room to subscribe to messages 💡 */ | ||
await room.attach(); | ||
} | ||
|
||
function addMessage(message: Message, position = 'after') { | ||
const messages = document.getElementById('messages'); | ||
|
||
/** Create message div with timeserial as the unique identifier */ | ||
const messageDiv = document.createElement('div'); | ||
messageDiv.id = message.timeserial; | ||
messageDiv.className = 'flex items-start'; | ||
|
||
if (position === 'after') { | ||
messages.appendChild(messageDiv); | ||
} else { | ||
messages.insertBefore(messageDiv, messages.firstChild); | ||
} | ||
|
||
/** Add author name to the message div */ | ||
const authorSpan = document.createElement('span'); | ||
authorSpan.className = `font-bold ${message.clientId === chatClient.clientId ? 'text-grey-500' : 'text-blue-500'} mr-2`; | ||
authorSpan.textContent = `${message.clientId === chatClient.clientId ? 'You' : message.clientId}:`; | ||
messageDiv.appendChild(authorSpan); | ||
|
||
/** Add message text to message div */ | ||
const messageSpan = document.createElement('span'); | ||
messageSpan.className = 'text-gray-700'; | ||
messageSpan.textContent = message.text; | ||
messageDiv.appendChild(messageSpan); | ||
} | ||
|
||
/** 💡 Send message to room 💡 */ | ||
function handleSubmit() { | ||
const form = document.querySelector('form') as HTMLFormElement; | ||
const formData = new FormData(form); | ||
|
||
const message = formData.get('message') as string; | ||
|
||
if (message.trim() !== '') { | ||
room.messages.send({ text: message }); | ||
|
||
const messageInput = form.querySelector('input[name="message"]') as HTMLInputElement; | ||
messageInput.value = ''; | ||
} else { | ||
alert('Please enter a message before sending.'); | ||
} | ||
} | ||
|
||
(window as any).handleSubmit = handleSubmit; | ||
(window as any).getPastMessages = getPastMessages; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
|
||
.container { | ||
width: 100%; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
position: relative; | ||
background-color: #f4f8fb; | ||
height: 100vh; | ||
} | ||
|
||
input { | ||
padding: 0.5rem; | ||
width: 100%; | ||
height: 2.5rem; | ||
font-size: 0.875rem; | ||
border-radius: 0.375rem; | ||
outline: none; | ||
transition: all 0.2s ease-in-out; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** @type {import('tailwindcss').Config} */ | ||
module.exports = { | ||
content: [ | ||
"./index.html", | ||
"./src/**/*.{js,jsx,ts,tsx}", | ||
], | ||
theme: { | ||
extend: {}, | ||
}, | ||
plugins: [], | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"compilerOptions": { | ||
"lib": ["dom", "es2015"], | ||
"outDir": "./lib/cjs/", | ||
"sourceMap": true, | ||
"declaration": true, | ||
"noImplicitAny": true, | ||
"module": "commonjs", | ||
"target": "es2017", | ||
"allowJs": true, | ||
"moduleResolution": "node" | ||
}, | ||
"include": ["src/**/*.ts*"], | ||
"exclude": ["node_modules", "dist", "lib"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
interface ImportMetaEnv { | ||
readonly VITE_PUBLIC_ABLY_KEY: string; | ||
// Add other environment variables here if needed | ||
} | ||
|
||
interface ImportMeta { | ||
readonly env: ImportMetaEnv; | ||
} |
Oops, something went wrong.