Skip to content

[EDU-1691] - Add pub-sub rewind example #2436

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
"pub-sub-occupancy/javascript",
"pub-sub-presence/react",
"pub-sub-presence/javascript",
"pub-sub-rewind/react",
"pub-sub-rewind/javascript",
"spaces-avatar-stack/react",
"spaces-avatar-stack/javascript",
"spaces-component-locking/react",
Expand Down Expand Up @@ -76,6 +78,8 @@
"pub-sub-occupancy-react": "yarn workspace pub-sub-occupancy-react dev",
"pub-sub-presence-javascript": "yarn workspace pub-sub-presence-javascript dev",
"pub-sub-presence-react": "yarn workspace pub-sub-presence-react dev",
"pub-sub-rewind-javascript": "yarn workspace pub-sub-rewind-javascript dev",
"pub-sub-rewind-react": "yarn workspace pub-sub-rewind-react dev",
"spaces-avatar-stack-javascript": "yarn workspace spaces-avatar-stack-javascript dev",
"spaces-avatar-stack-react": "yarn workspace spaces-avatar-stack-react dev",
"spaces-component-locking-javascript": "yarn workspace spaces-component-locking-javascript dev",
Expand Down
1 change: 1 addition & 0 deletions examples/pub-sub-rewind/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/pub-sub-rewind/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
58 changes: 58 additions & 0 deletions examples/pub-sub-rewind/javascript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Rewind channel option with Pub/Sub

Rewind is a Pub/Sub channel option that enables users to retrieve a set number of historical messages published to an application.

Use rewind to retrieve a pre-determined number of messages that have been previously published to a channel when attached. Users can then start working in your application with context of what happened before they went joined, or came online. Uses include retrieving the history of odds on a football game before providing the last 2 minutes worth of contextual data in a realtime dashboard.

Rewind enables users to retrieve a set number of messages that have been previously published within an application. It enables provides users with context as to how the current state has been reached.

Rewind is a channel option which is implemented using [Ably Pub/Sub](https://ably.com/docs/products/channels). The Pub/Sub SDK provides a set of flexible APIs capable of building any realtime application. It is powered by Ably's reliable and scalable platform.
Comment on lines +3 to +9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Rewind is a Pub/Sub channel option that enables users to retrieve a set number of historical messages published to an application.
Use rewind to retrieve a pre-determined number of messages that have been previously published to a channel when attached. Users can then start working in your application with context of what happened before they went joined, or came online. Uses include retrieving the history of odds on a football game before providing the last 2 minutes worth of contextual data in a realtime dashboard.
Rewind enables users to retrieve a set number of messages that have been previously published within an application. It enables provides users with context as to how the current state has been reached.
Rewind is a channel option which is implemented using [Ably Pub/Sub](https://ably.com/docs/products/channels). The Pub/Sub SDK provides a set of flexible APIs capable of building any realtime application. It is powered by Ably's reliable and scalable platform.
Rewind is a channel option that enables users to choose where to begin a channel attachment from.
Use rewind to choose at which point in the past a client attaches to a room. This enables them to receive previously published messages upon attaching and provide context as to what has been happening. Rewind can be by a set number of messages in the past, or within a certain timeframe. Note that [message storage](https://ably.com/docs/storage-history/storage) needs to be enabled to retrieve messages older than 2 minutes.
Rewind can be used to retrieve the history of how odds for a football game changed over the past 5 minutes, or provide the most recent 50 events that occurred in a realtime dashboard.
Rewind is a channel option which is implemented using [Ably Pub/Sub](https://ably.com/docs/basics). The Pub/Sub SDK provides a set of flexible APIs capable of building any realtime application. It is powered by Ably's reliable and scalable platform.


## Resources

Use the following methods to specify where to start an attachment from when attaching to a channel in a pub/sub application:

* [`channel.get()`](https://ably.com/docs/channels#create) - creates a new or retrieves an existing `channel`. Using the `rewind` channel option retrieves the set number of historical messages published to the channel when the channel is attached.
* [`channel.subscribe()`](https://ably.com/docs/channels#subscribe) - subscribes to message events within a specific channel by registering a listener. Message events are emitted when a user publishes a message.

Find out more about [rewind](https://ably.com/docs/channels/options/rewind).

## 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/
```

3. Rename the environment file:

```sh
mv .env.example .env.local
```

4. In `.env.local` update the value of `VITE_ABLY_KEY` to be your Ably API key.

5. Install dependencies:

```sh
yarn install
```

6. Run the server:

```sh
yarn run pub-sub-rewind-javascript
```

7. Try it out by opening a tab to [http://localhost:5173/](http://localhost:5173/) with your browser to see the result.

## Open in CodeSandbox

In CodeSandbox, rename the `.env.example` file to `.env.local` and update the value of your `VITE_PUBLIC_ABLY_KEY` variable to use your Ably API key.
60 changes: 60 additions & 0 deletions examples/pub-sub-rewind/javascript/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!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>Pub/Sub rewind channel</title>
</head>
<body class="font-inter">
<div id="landing-page" class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="bg-white p-8 rounded-lg shadow-lg w-96">
<h2 class="text-2xl mb-6 text-center">Live Football League Odds</h2>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<h2 class="text-2xl mb-6 text-center">Live Football League Odds</h2>
<h2 class="text-2xl mb-6 text-center">Live Odds</h2>

<p class="text-sm text-center">Watch real-time odds movement for today's Football League match.</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p class="text-sm text-center">Watch real-time odds movement for today's Football League match.</p>
<p class="text-sm text-center">Simulate 10 betting odds for a sports game before attaching.</p>

Maybe something like this to show what's happening?

<div class="flex flex-col gap-2 mt-4">
<button
id="pre-load-odds"
class="uk-btn uk-btn-primary uk-border-rounded-right whitespace-nowrap">Load Live Match Odds</button>
</div>
</div>
</div>
<div id="game" class="min-h-screen bg-gray-100 p-4" style="display: none;">
<div class="max-w-sm mx-auto">
<div class="bg-white rounded shadow p-3 mb-4">
<div class="flex justify-between items-center text-sm font-semibold">
<span>
<span class="hidden sm:inline">Royal Knights</span>
<span class="sm:hidden">R K</span>
</span>
<span id="score" class="bg-gray-800 text-white px-2 py-1 rounded">0-0</span>
<span>
<span class="hidden sm:inline">North Rangers</span>
<span class="sm:hidden">N R</span>
</span>
</div>
</div>
<div class="grid grid-cols-3 gap-2 mb-4">
<div class="bg-white rounded shadow p-2">
<h3 class="text-xs font-medium mb-1">Home Win</h3>
<p id="current-home" class="text-lg font-bold text-green-600">2.50</p>
</div>
<div class="bg-white rounded shadow p-2">
<h3 class="text-xs font-medium mb-1">Draw</h3>
<p id="current-draw" class="text-lg font-bold text-blue-600">3.42</p>
</div>
<div class="bg-white rounded shadow p-2">
<h3 class="text-xs font-medium mb-1">Away Win</h3>
<p id="current-away" class="text-lg font-bold text-red-600">2.87</p>
</div>
</div>
<div class="bg-white rounded shadow p-3">
<h2 class="text-sm font-bold mb-2">Odds Movement History</h2>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<h2 class="text-sm font-bold mb-2">Odds Movement History</h2>
<h2 class="text-sm font-bold mb-2">Odds History</h2>

<div id="history" class="space-y-2">
</div>
</div>
</div>
</div>
<script type="module" src="src/script.ts"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions examples/pub-sub-rewind/javascript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "pub-sub-rewind-javascript",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
}
}
168 changes: 168 additions & 0 deletions examples/pub-sub-rewind/javascript/src/script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as Ably from 'ably';
import type { Message } from 'ably';
import minifaker from 'minifaker';
import './styles.css';

import 'minifaker/locales/en';

interface MatchOdds {
match: {
homeTeam: string;
awayTeam: string;
timestamp: number;
score: string;
matchOdds: {
homeWin: string;
draw: string;
awayWin: string;
};
};
}

let matchData: MatchOdds | null = {
match: {
homeTeam: 'Royal Knights',
awayTeam: 'North Rangers',
timestamp: Date.now(),
score: '0-0',
matchOdds: {
homeWin: '2.45',
draw: '3.25',
awayWin: '2.85',
},
},
};

const preloadButton = document.getElementById('pre-load-odds') as HTMLButtonElement;
const urlParams = new URLSearchParams(window.location.search);
const channelName = urlParams.get('name') || 'pub-sub-rewind';
const landingPage = document.getElementById('landing-page');
const game = document.getElementById('game');
let channel: Ably.RealtimeChannel | null = null;

async function enterGame() {
landingPage.style.display = 'none';
game.style.display = 'block';

const client = new Ably.Realtime({
key: import.meta.env.VITE_ABLY_KEY as string,
clientId: minifaker.firstName(),
});

channel = client.channels.get(channelName, {
params: { rewind: '10' },
});

channel.subscribe(async (message) => {
matchData = message.data;
await addHistoryItem(message);
await updateCurrentOdds(message);
});

await updateRandomOdds();
}

preloadButton.addEventListener('click', async () => {
preloadButton.disabled = true;
const client = new Ably.Realtime({
key: import.meta.env.VITE_ABLY_KEY as string,
clientId: minifaker.firstName(),
});

const channel = client.channels.get(channelName);

for (let i = 0; i < 10; i++) {
const markets = ['homeWin', 'draw', 'awayWin'];
const numMarketsToUpdate = Math.floor(Math.random() * 2) + 1;
const marketsToUpdate = markets.sort(() => 0.5 - Math.random()).slice(0, numMarketsToUpdate);

marketsToUpdate.forEach((market) => {
matchData.match.matchOdds[market] = (parseFloat(matchData.match.matchOdds[market]) + (Math.random() * 0.2 - 0.1)).toFixed(2);
});

matchData.match.timestamp = Date.now();
await channel.publish('odds', matchData);

// Show alert for each publish
const alert = document.createElement('div');
alert.className =
'fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded shadow-lg transition-opacity duration-500';
alert.textContent = `Update ${i + 1}/10: New odds published`;
document.body.appendChild(alert);

// Remove alert after 2 seconds
setTimeout(() => {
alert.style.opacity = '0';
setTimeout(() => alert.remove(), 500);
}, 2000);

await new Promise((resolve) => setTimeout(resolve, 1000));
}

await enterGame();
});

async function updateCurrentOdds(message: Message) {
const score = document.getElementById('score');
score.textContent = message.data.match.score;
const currentHome = document.getElementById('current-home');
currentHome.textContent = message.data.match.matchOdds.homeWin;
const currentAway = document.getElementById('current-away');
currentAway.textContent = message.data.match.matchOdds.awayWin;
const currentDraw = document.getElementById('current-draw');
currentDraw.textContent = message.data.match.matchOdds.draw;
}

async function addHistoryItem(message: Message, position = 'prepend') {
const history = document.getElementById('history');
const historyItem = document.createElement('div');
historyItem.id = `history-item-${message.id}`;
historyItem.className = 'border-b pb-2';
const historyDiv = document.createElement('div');
historyDiv.className = 'flex justify-between text-sm text-gray-600';
historyItem.appendChild(historyDiv);

const homeWin = document.createElement('span');
homeWin.textContent = `Home: ${message.data.match.matchOdds.homeWin}`;
const draw = document.createElement('span');
draw.textContent = `Draw: ${message.data.match.matchOdds.draw}`;
const awayWin = document.createElement('span');
awayWin.textContent = `Away: ${message.data.match.matchOdds.awayWin}`;
const time = document.createElement('span');
const timestamp = new Date(message.data.match.timestamp);
time.textContent = `${timestamp.getHours()}:${timestamp.getMinutes().toString().padStart(2, '0')}`;
historyDiv.appendChild(homeWin);
historyDiv.appendChild(draw);
historyDiv.appendChild(awayWin);
historyDiv.appendChild(time);

if (position === 'prepend') {
history.prepend(historyItem);
} else {
history.appendChild(historyItem);
}
}

async function updateRandomOdds() {
if (!matchData) {
return;
}

for (let i = 0; i < 20; i++) {
const delayTime = 5000;
await new Promise((resolve) => setTimeout(resolve, delayTime));

const markets = ['homeWin', 'draw', 'awayWin'];
const numMarketsToUpdate = Math.floor(Math.random() * 3) + 1;
const marketsToUpdate = markets.sort(() => 0.5 - Math.random()).slice(0, numMarketsToUpdate);

const newOdds = { ...matchData };

marketsToUpdate.forEach((market) => {
newOdds.match.matchOdds[market] = (parseFloat(newOdds.match.matchOdds[market]) + (Math.random() * 0.2 - 0.1)).toFixed(2);
});

newOdds.match.timestamp = Date.now();
await channel.publish('odds', newOdds);
}
}
Loading