Skip to content

Commit

Permalink
Merge pull request #2316 from ably/EDU-1666-Example-Chat-Message-history
Browse files Browse the repository at this point in the history


[EDU-1666] - Chat message history example
  • Loading branch information
GregHolmes authored Dec 18, 2024
2 parents dca5a6e + 7998e54 commit d20ce4e
Show file tree
Hide file tree
Showing 28 changed files with 5,312 additions and 1 deletion.
1 change: 1 addition & 0 deletions examples/chat-room-history/javascript/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_PUBLIC_ABLY_KEY=
36 changes: 36 additions & 0 deletions examples/chat-room-history/javascript/.gitignore
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
39 changes: 39 additions & 0 deletions examples/chat-room-history/javascript/README.md
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.
71 changes: 71 additions & 0 deletions examples/chat-room-history/javascript/index.html
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>
25 changes: 25 additions & 0 deletions examples/chat-room-history/javascript/package.json
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"
}
}
150 changes: 150 additions & 0 deletions examples/chat-room-history/javascript/src/script.ts
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;
23 changes: 23 additions & 0 deletions examples/chat-room-history/javascript/src/styles.css
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;
}
12 changes: 12 additions & 0 deletions examples/chat-room-history/javascript/tailwind.config.js
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: [],
}

15 changes: 15 additions & 0 deletions examples/chat-room-history/javascript/tsconfig.json
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"]
}
8 changes: 8 additions & 0 deletions examples/chat-room-history/javascript/vite-env.d.ts
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;
}
Loading

0 comments on commit d20ce4e

Please sign in to comment.