Skip to content

Commit

Permalink
Merge pull request #17 from ably-labs/ably-v2-migration
Browse files Browse the repository at this point in the history
Ably-js v2 migration
  • Loading branch information
VeskeR authored May 29, 2024
2 parents 2c64a2e + 4f07d04 commit 3b0de24
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 2,117 deletions.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ When you're finished with the changes, create a pull request, also known as a PR
Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information.
- We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
- As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
- If you run into any merge issues, checkout this [git tutorial](https://lab.github.com/githubtraining/managing-merge-conflicts) to help you resolve merge conflicts and other issues.
- If you run into any merge issues, checkout this [git tutorial](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-on-github) to help you resolve merge conflicts and other issues.

### Your PR is merged!

Congratulations :tada::tada: The Ably Labs team thanks you! :sparkles:
Congratulations :tada::tada: The Ably Labs team thanks you! :sparkles:
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
# Build a Social Link-Sharing App with Ably, NextJS, and OpenGraph

This is a demo app using [NextJS](https://nextjs.org/) and [Ably](https://ably.com) with the Ably's [react-hooks](https://github.com/ably-labs/react-hooks) `npm` module.
Live example at: <https://ably-next-headlines.vercel.app/>

Step by step guide at: <https://ably.com/blog/next-js-vercel-link-sharing-serverless-websockets>

This is a demo app using [NextJS](https://nextjs.org/) and [Ably](https://ably.com) with the [Ably](https://www.npmjs.com/package/ably) `npm` module.

## Description

It demonstrates use of:
It demonstrates the use of:

- Pub/sub messaging
- Channel presence
- Channel rewind/History
- OpenGraph

It show how to use Ably's React Hooks in the content of a Next.js application, with server-side rendering.
It shows how to use Ably's React Hooks in the context of a Next.js application, with server-side rendering.

## Tech stack


The project uses the following components:

- [NextJS](https://nextjs.org/), for highly optimized static web sites
- [Vercel](https://vercel.com/), for deployment
- [Ably React Hooks](https://www.npmjs.com/package/@ably-labs/react-hooks), for realtime messaging at scale in React-based applications.
- [Ably](https://www.npmjs.com/package/ably), for realtime messaging at scale in React-based applications.

## Building & running locally

### Prerequisites

1. [Sign up](https://ably.com/signup) or [log in](https://ably.com/login) to ably.com, and [create a new app and copy the root API key](https://faqs.ably.com/setting-up-and-managing-api-keys). This is your server API key
1. [Sign up](https://ably.com/signup) or [log in](https://ably.com/login) to ably.com, and [create a new app and copy the root API key](https://faqs.ably.com/setting-up-and-managing-api-keys). This is your server API key.
2. Create another API key with the Subscribe abd Presence capabilities only. This is your client API key.
2. Run `npm install`
3. Run `npm install`.

### Building the project

To run this project locally, fork this repo and add an `.env` file containing your Ably API keys and local path:

```
```sh
# This will change after deployment
NEXT_PUBLIC_HOSTNAME=http://localhost:3000
# Server key must have Publish and History permissions - use app's Root key
Expand All @@ -45,7 +48,7 @@ ABLY_CLIENT_API_KEY=UGCYxQ.uhSg9Q:A6Ftr7F0HTg....

## Deploying to the cloud

Refer to the tutorial blog post for full instructions on deployment to Vercel.
Refer to the [tutorial blog post](https://ably.com/blog/next-js-vercel-link-sharing-serverless-websockets#step-13-deploy-your-app-to-vercel) for full instructions on deployment to Vercel.

## Contributing

Expand Down
26 changes: 13 additions & 13 deletions components/Articles.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
import React, { useState } from 'react';
import { useChannel } from '@ably-labs/react-hooks';
import { useChannel } from 'ably/react';
import ArticlePreview from './ArticlePreview';
import styles from '../styles/Home.module.css';

/*
clearHistoryState:
- When true, historical messages are retrieved and rendered statically from the History API.
- When false, historical messages are retrieved using channel rewind to prevent a race condition
where new messages are arriving on the channel while history is still being retrieved.
*/
/**
* clearHistoryState:
* - When true, historical messages are retrieved and rendered statically from the History API.
* - When false, historical messages are retrieved using channel rewind to prevent a race condition
* where new messages are arriving on the channel while history is still being retrieved.
*/
let clearHistoryState = true;

export default function Articles(props) {
let inputBox = null;

const [headlineText, setHeadlineText] = useState('');
const [headlines, updateHeadlines] = useState(props.history);
const [_, ably] = useChannel('[?rewind=5]headlines', (headline) => {
const [headlines, setHeadlines] = useState(props.history);
const { ably } = useChannel('headlines', (headline) => {
if (clearHistoryState) {
resetHeadlines();
clearHistoryState = false;
}

updateHeadlines((prev) => [headline, ...prev]);
setHeadlines((prev) => [headline, ...prev]);
});

const resetHeadlines = () => {
updateHeadlines([]);
setHeadlines([]);
};

const headlineTextIsEmpty = headlineText.trim().length === 0;

const processedHeadlines = headlines.map((headline) => processMessage(headline, ably.auth.clientId));

const articles = processedHeadlines.map((headline, index) => <ArticlePreview key={index} headline={headline} />);

const handleFormSubmission = async (event) => {
Expand Down Expand Up @@ -75,8 +77,6 @@ export default function Articles(props) {
}

function processMessage(headline, currentClientId) {
/*headline.data.author =
headline.data.author === currentClientId ? "me" : headline.data.author;*/
headline.data.timestamp = 'timestamp' in headline ? formatDate(headline.timestamp) : 'earlier';
headline.data.url = headline?.data?.url || 'http://example.com';
headline.data.image = headline?.data?.image || 'http://placekitten.com/g/200/300';
Expand Down
7 changes: 4 additions & 3 deletions components/Participants.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import { usePresence, assertConfiguration } from '@ably-labs/react-hooks';
import { usePresence, usePresenceListener, useAbly } from 'ably/react';
import styles from '../styles/Home.module.css';

export default function Participants(props) {
const ably = assertConfiguration();
const [presenceData] = usePresence('headlines');
const ably = useAbly();
usePresence('headlines');
const { presenceData } = usePresenceListener('headlines');

const presenceList = presenceData.map((member, index) => {
const isItMe = member.clientId === ably.auth.clientId ? '(me)' : '';
Expand Down
18 changes: 9 additions & 9 deletions lib/history.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import Ably from 'ably';

const rest = new Ably.Rest.Promise({
key: process.env.ABLY_SERVER_API_KEY,
});

const channel = rest.channels.get('headlines');

export async function getHistoricalMessages() {
const rest = new Ably.Rest({
key: process.env.ABLY_SERVER_API_KEY,
});

const channel = rest.channels.get('headlines');
const resultPage = await channel.history({ limit: 5 });
/*
See issue: https://github.com/vercel/next.js/issues/11993. The JSON must
be re-parsed or certain Message object items cannot be serialized by props later.
/*
See issue: https://github.com/vercel/next.js/issues/11993.
The JSON must be re-parsed or certain Message object items
cannot be serialized by props later.
*/
const historicalMessages = JSON.parse(JSON.stringify(resultPage.items));
return historicalMessages;
Expand Down
74 changes: 42 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
"format:check": "prettier --check --ignore-path .gitignore components lib pages styles next.config.js"
},
"dependencies": {
"@ably-labs/react-hooks": "^2.0.4",
"ably": "^1.2.25",
"ably": "^2.0.4",
"next": "12.1.6",
"open-graph-scraper": "^4.11.0",
"react": "18.1.0",
Expand Down
22 changes: 18 additions & 4 deletions pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import '../styles/globals.css';
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import dynamic from 'next/dynamic';

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
// fixes "Warning: useLayoutEffect does nothing on the server" warning spam from next.js
const ChannelProvider = dynamic(() => import('ably/react').then((module) => module.ChannelProvider), { ssr: false });

export default function App({ Component, pageProps }) {
const client = new Ably.Realtime({
authUrl: `${process.env.NEXT_PUBLIC_HOSTNAME}/api/createTokenRequest`,
});

export default MyApp;
return (
<AblyProvider client={client}>
<ChannelProvider channelName="headlines" options={{ params: { rewind: '5' } }}>
<Component {...pageProps} />;
</ChannelProvider>
</AblyProvider>
);
}
4 changes: 2 additions & 2 deletions pages/api/createTokenRequest.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Ably from 'ably/promises';
import Ably from 'ably';
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';

export default async function handler(req, res) {
const client = new Ably.Realtime(process.env.ABLY_CLIENT_API_KEY);
const client = new Ably.Rest(process.env.ABLY_CLIENT_API_KEY);

const randomName = uniqueNamesGenerator({
dictionaries: [adjectives, animals, colors],
Expand Down
15 changes: 10 additions & 5 deletions pages/api/publish.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import Ably from 'ably/promises';
import Ably from 'ably';
import urlExists from 'url-exists-nodejs';
import ogs from 'open-graph-scraper';

const ably = new Ably.Rest(process.env.ABLY_SERVER_API_KEY);
const channel = ably.channels.get('headlines');

export default async function handler(req, res) {
if (req.method !== 'POST') {
res.status(405).json({});
return;
}

if (typeof req.body?.text !== 'string') {
res.status(400).json({ url: req.body?.text, message: 'not a valid URL' });
return;
}

const validUrl = await urlExists(req.body.text);
if (!validUrl) {
res.status(400).json({ url: req.body.text, message: 'not a valid URL' });
Expand All @@ -24,7 +26,10 @@ export default async function handler(req, res) {
return;
}

channel.publish('new-headline', {
const ably = new Ably.Rest(process.env.ABLY_SERVER_API_KEY);
const channel = ably.channels.get('headlines');

await channel.publish('new-headline', {
author: req.body.author,
site: result?.ogSiteName || 'unknown',
title: result?.ogTitle || 'unknown',
Expand Down
5 changes: 0 additions & 5 deletions pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,9 @@ import Image from 'next/image';
import ablyLogo from '../public/ably-logo.svg';
import styles from '../styles/Home.module.css';
import Participants from '../components/Participants';
import { configureAbly } from '@ably-labs/react-hooks';
import Articles from '../components/Articles';
import { getHistoricalMessages } from '../lib/history';

configureAbly({
authUrl: `${process.env.NEXT_PUBLIC_HOSTNAME}/api/createTokenRequest`,
});

export default function Home(props) {
return (
<div className={styles.container}>
Expand Down
Loading

0 comments on commit 3b0de24

Please sign in to comment.