Skip to content

Commit bbffaea

Browse files
Adding with-ably example for realtime messaging in next apps (vercel#37319)
* update from latest canary * lint-fix
1 parent 4df0778 commit bbffaea

15 files changed

+492
-0
lines changed

examples/with-ably/.gitignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env*.local
30+
.env
31+
32+
# vercel
33+
.vercel

examples/with-ably/README.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Realtime Edge Messaging with [Ably](https://ably.com/)
2+
3+
**Demo:** [https://next-and-ably.vercel.app/](https://next-and-ably.vercel.app/)
4+
5+
Add realtime data and interactive multi-user experiences to your Next.js apps with [Ably](https://ably.com/), without the infrastructure overhead.
6+
7+
Use Ably in your Next.js application using idiomatic, easy to use hooks.
8+
9+
Using this demo you can:
10+
11+
- [Send and receive](https://ably.com/docs/realtime/messages) realtime messages
12+
- Get notifications of [user presence](https://ably.com/docs/realtime/presence) on channels
13+
- Send [presence updates](https://ably.com/docs/api/realtime-sdk/presence#update) when a new client joins or leaves the demo
14+
15+
This demo is uses the [Ably React Hooks package](https://www.npmjs.com/package/@ably-labs/react-hooks), a simplified syntax for interacting with Ably, which manages the lifecycle of the Ably SDK instances for you taking care to subscribe and unsubscribe to channels and events when your components re-render).
16+
17+
## Deploy your own
18+
19+
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) or preview live with [StackBlitz](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-ably)
20+
21+
**You will need an Ably API key to run this demo, [see below for details](#ably-setup)**
22+
23+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-ably&project-name=with-ably&repository-name=with-ably)
24+
25+
## How to use
26+
27+
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
28+
29+
```bash
30+
npx create-next-app --example with-ably with-ably-app
31+
# or
32+
yarn create next-app --example with-ably with-ably-app
33+
# or
34+
pnpm create next-app --example with-ably with-ably-app
35+
```
36+
37+
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
38+
39+
**When deployed, ensure that you set your environment variables (the Ably API key and the deployed Vercel API root) in your Vercel settings**
40+
41+
## Notes
42+
43+
### Ably Setup
44+
45+
In order to send and receive messages you will need an Ably API key.
46+
If you are not already signed up, you can [sign up now for a free Ably account](https://www.ably.com/signup). Once you have an Ably account:
47+
48+
1. Log into your app dashboard.
49+
2. Under **“Your apps”**, click on **“Manage app”** for any app you wish to use for this tutorial, or create a new one with the “Create New App” button.
50+
3. Click on the **“API Keys”** tab.
51+
4. Copy the secret **“API Key”** value from your Root key.
52+
5. Create a .env file in the root of the demo repository
53+
6. Paste the API key into your new env file, along with a env variable for your localhost:
54+
55+
```bash
56+
ABLY_API_KEY=your-ably-api-key:goes-here
57+
API_ROOT=http://localhost:3000
58+
```
59+
60+
### How it Works/Using Ably
61+
62+
#### Configuration
63+
64+
[pages/\_app.js](pages/_app.js) is where the Ably SDK is configured:
65+
66+
```js
67+
import { configureAbly } from '@ably-labs/react-hooks'
68+
69+
const prefix = process.env.API_ROOT || ''
70+
const clientId =
71+
Math.random().toString(36).substring(2, 15) +
72+
Math.random().toString(36).substring(2, 15)
73+
74+
configureAbly({
75+
authUrl: `${prefix}/api/createTokenRequest?clientId=${clientId}`,
76+
clientId: clientId,
77+
})
78+
79+
function MyApp({ Component, pageProps }) {
80+
return <Component {...pageProps} />
81+
}
82+
83+
export default MyApp
84+
```
85+
86+
`configureAbly` matches the method signature of the Ably SDK - and requires either a string or a [AblyClientOptions](https://ably.com/docs/api/realtime-sdk#client-options) object. You can use this configuration object to setup your [tokenAuthentication](https://ably.com/docs/core-features/authentication#token-authentication). If you want to use the usePresence function, you'll need to explicitly provide a `clientId`.
87+
88+
You can do this anywhere in your code before the rest of the library is used.
89+
90+
#### useChannel (Publishing and Subscribing to Messages)
91+
92+
The `useChannel` hook lets you subscribe to a channel and receive messages from it:
93+
94+
```js
95+
import { useState } from 'react'
96+
import { useChannel } from '@ably-labs/react-hooks'
97+
98+
export default function Home() {
99+
const [channel] = useChannel('your-channel', async (message) => {
100+
console.log('Received Ably message', message)
101+
})
102+
}
103+
```
104+
105+
Every time a message is sent to `your-channel` it will be logged to the console. You can do whatever you need to with those messages.
106+
107+
##### Publishing a message
108+
109+
The `channel` instance returned by `useChannel` can be used to send messages to the channel. It is a regular Ably JavaScript SDK `channel` instance.
110+
111+
```javascript
112+
channel.publish('test-message', { text: 'message text' })
113+
```
114+
115+
#### usePresence
116+
117+
The `usePresence` hook lets you subscribe to presence events on a channel - this will allow you to get notified when a user joins or leaves the channel. The presence data is automatically updated and your component re-rendered when presence changes:
118+
119+
```js
120+
import { useState } from 'react'
121+
import { usePresence } from '@ably-labs/react-hooks'
122+
123+
export default function Home() {
124+
const [presenceData, updateStatus] = usePresence('your-channel-name')
125+
126+
const presentClients = presenceData.map((msg, index) => (
127+
<li key={index}>
128+
{msg.clientId}: {msg.data}
129+
</li>
130+
))
131+
132+
return <ul>{presentClients}</ul>
133+
}
134+
```
135+
136+
You can read more about the hooks available with the Ably Hooks package on the [@ably-labs/ably-hooks documentation on npm](https://www.npmjs.com/package/@ably-labs/react-hooks).

examples/with-ably/next-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
4+
// NOTE: This file should not be edited
5+
// see https://nextjs.org/docs/basic-features/typescript for more information.

examples/with-ably/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"private": true,
3+
"scripts": {
4+
"dev": "next dev",
5+
"build": "next build",
6+
"start": "next start"
7+
},
8+
"dependencies": {
9+
"@ably-labs/react-hooks": "^2.0.4",
10+
"ably": "^1.2.22",
11+
"next": "latest",
12+
"react": "18.1.0",
13+
"react-dom": "18.1.0"
14+
},
15+
"devDependencies": {
16+
"@types/node": "^18.0.0",
17+
"@types/react": "^18.0.12",
18+
"eslint-config-next": "12.1.6",
19+
"typescript": "^4.7.3"
20+
}
21+
}

examples/with-ably/pages/_app.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import '../styles/globals.css'
2+
import { configureAbly } from '@ably-labs/react-hooks'
3+
4+
const prefix = process.env.API_ROOT || ''
5+
const clientId =
6+
Math.random().toString(36).substring(2, 15) +
7+
Math.random().toString(36).substring(2, 15)
8+
9+
configureAbly({
10+
authUrl: `${prefix}/api/createTokenRequest?clientId=${clientId}`,
11+
clientId: clientId,
12+
})
13+
14+
function MyApp({ Component, pageProps }) {
15+
return <Component {...pageProps} />
16+
}
17+
18+
export default MyApp
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Ably from 'ably/promises'
2+
import type { NextApiRequest, NextApiResponse } from 'next'
3+
4+
export default async function handler(
5+
req: NextApiRequest,
6+
res: NextApiResponse
7+
) {
8+
const client = new Ably.Realtime(process.env.ABLY_API_KEY)
9+
const tokenRequestData = await client.auth.createTokenRequest({
10+
clientId: req.query.clientId as string,
11+
})
12+
res.status(200).json(tokenRequestData)
13+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Ably from 'ably/promises'
2+
import type { NextApiRequest, NextApiResponse } from 'next/types'
3+
import type { TextMessage } from '../../types'
4+
5+
export default async function handler(
6+
req: NextApiRequest,
7+
res: NextApiResponse
8+
) {
9+
const client = new Ably.Realtime(process.env.ABLY_API_KEY)
10+
11+
const channel = client.channels.get('some-channel-name')
12+
13+
const message: TextMessage = {
14+
text: `Server sent a message on behalf of ${req.body.sender}`,
15+
}
16+
channel.publish('test-message', message)
17+
18+
res.status(200)
19+
}

examples/with-ably/pages/index.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { useState } from 'react'
2+
import { useChannel, usePresence } from '@ably-labs/react-hooks'
3+
import type { Types } from 'ably'
4+
import type { ProxyMessage, TextMessage } from '../types'
5+
6+
import Head from 'next/head'
7+
import Image from 'next/image'
8+
import styles from '../styles/Home.module.css'
9+
10+
export default function Home() {
11+
const [messages, setMessages] = useState<TextMessage[]>([])
12+
13+
const [channel, ably] = useChannel(
14+
'some-channel-name',
15+
async (message: Types.Message) => {
16+
console.log('Received Ably message', message)
17+
setMessages((messages) => [...messages, message.data])
18+
}
19+
)
20+
21+
const [presenceData, updateStatus] = usePresence('your-channel-name')
22+
23+
const messageList = messages.map((message, index) => {
24+
return <li key={index}>{message.text}</li>
25+
})
26+
27+
const presentClients = presenceData.map((msg, index) => (
28+
<li key={index}>
29+
{msg.clientId}: {msg.data}
30+
</li>
31+
))
32+
33+
return (
34+
<div className={styles.container}>
35+
<Head>
36+
<title>Create Next App</title>
37+
<meta name="description" content="Generated by create next app" />
38+
<link rel="icon" href="/favicon.ico" />
39+
</Head>
40+
41+
<h1>Realtime Edge Messaging with Next and Ably</h1>
42+
<p>
43+
Use the buttons below to send and receive messages or to update your
44+
status.
45+
</p>
46+
47+
<main className={styles.main}>
48+
<h2>Present Clients</h2>
49+
<button
50+
onClick={() => {
51+
updateStatus('hello')
52+
}}
53+
>
54+
Update status to hello
55+
</button>
56+
<ul>{presentClients}</ul>
57+
58+
<h2>Ably Message Data</h2>
59+
<button
60+
className={styles.button}
61+
onClick={() => {
62+
const message: TextMessage = {
63+
text: `${ably.auth.clientId} sent a message`,
64+
}
65+
channel.publish('test-message', message)
66+
}}
67+
>
68+
Send A Message
69+
</button>
70+
<button
71+
className={styles.button}
72+
onClick={() => {
73+
const proxyMessage: ProxyMessage = {
74+
sender: `${ably.auth.clientId}`,
75+
}
76+
77+
fetch('/api/send-message', {
78+
method: 'POST',
79+
headers: {
80+
'Content-Type': 'application/json',
81+
},
82+
body: JSON.stringify(proxyMessage),
83+
})
84+
}}
85+
>
86+
Send A Message From the Server
87+
</button>
88+
<ul>{messageList}</ul>
89+
</main>
90+
91+
<footer className={styles.footer}>
92+
Powered by
93+
<a
94+
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
95+
target="_blank"
96+
rel="noopener noreferrer"
97+
>
98+
<span>
99+
<Image
100+
src="/vercel.svg"
101+
alt="Vercel Logo"
102+
width={100}
103+
height={32}
104+
/>
105+
</span>
106+
</a>
107+
and
108+
<a href="https://ably.com/">
109+
<img src="/ably.svg" alt="Ably Realtime" className={styles.logo} />
110+
</a>
111+
</footer>
112+
</div>
113+
)
114+
}

examples/with-ably/public/ably.svg

Lines changed: 27 additions & 0 deletions
Loading

examples/with-ably/public/favicon.ico

25.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)