Skip to content
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

feat: read/unread status #1376

Merged
merged 26 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
84ce9ad
feat: read/unread status
chris13524 Jan 31, 2024
f1b598f
feat: add read state changes
Elyniss Feb 6, 2024
39183e0
chore: move features
Elyniss Feb 6, 2024
629c142
chore: refactor
Elyniss Feb 6, 2024
05b460e
Apply suggestions from code review
Elyniss Feb 6, 2024
13130ae
Merge pull request #1381 from WalletConnect/fea/web3inbox-read-unred/…
Elyniss Feb 7, 2024
f465b0a
chore: rename
Elyniss Feb 7, 2024
619a2e8
Merge pull request #1396 from WalletConnect/fea/web3inbox-read-unred/…
Elyniss Feb 7, 2024
f39a87f
chore: add api for read unread (#1383)
devceline Feb 7, 2024
be82275
fix: read unread in usage, and backwards-compatible parameters
chris13524 Feb 8, 2024
5286a03
Max sizes
chris13524 Feb 8, 2024
e96aa08
Merge pull request #1405 from WalletConnect/fix/web3inbox-read-unread…
chris13524 Feb 8, 2024
c1a4910
fix: add unreadCount
chris13524 Feb 8, 2024
442f9d3
feat: unread count
chris13524 Feb 8, 2024
a2a18a0
Merge branch 'main' of https://github.com/WalletConnect/walletconnect…
chris13524 Mar 18, 2024
f693a7d
fix: mark all notifications as read, remove mark as unread
chris13524 Mar 18, 2024
1187c9a
fix: remove "returned"
chris13524 Mar 19, 2024
910905f
feat: mark all as read endpoint
chris13524 Mar 20, 2024
1efc3d2
fix: syntax
chris13524 Mar 20, 2024
2d8c80f
fix: add rate limit for mark-all-as-read endpoint
chris13524 Mar 21, 2024
220a082
Update docs/web3inbox/frontend-integration/api.mdx
devceline Apr 16, 2024
8f53bbc
Update docs/web3inbox/frontend-integration/api.mdx
devceline Apr 16, 2024
b097f62
Apply suggestions from code review
devceline Apr 16, 2024
2a432d6
Update docs/web3inbox/frontend-integration/api.mdx
devceline Apr 16, 2024
ed951e7
chore: remove Web3Wallet changes
chris13524 Apr 16, 2024
23a99d7
fix: fmt
chris13524 Apr 16, 2024
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
13 changes: 8 additions & 5 deletions docs/api/notify/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,14 @@ const accountSubscriptions = notifyClient.getActiveSubscriptions({

```javascript
// Will return all past Notify messages for the provided subscription topic, keyed by messageId.
const messageHistory = notifyClient.getNotificationHistory({
topic,
limit: 10,
startingAfter: 'notification-id'
})
const messageHistory = notifyClient.getNotificationHistory({ topic, limit: 10, startingAfter: 'notification-id', unreadFirst: true })
```

#### Marking notification as read

```javascript
// Will mark the 2 specific notifications as read
const messageHistory = notifyClient.markNotificationsAsRead({ topic, notificationIds: ["notification-1", "notification-2" ] })
```

</PlatformTabItem>
Expand Down
1 change: 1 addition & 0 deletions docs/web3inbox/about.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Some of the key features of the Web3Inbox SDK include:
- **Device push notifications:** Push notifications to the user's wallet (if it supports Notify API) or the [Web3Inbox.com app](https://app.web3inbox.com).
- **Notification history:** Notifications are stored and can be accessed from any device.
- **Spam protection/subscription control.** Using notification types, subscribers can opt-out of certain notification types they do not want to receive.
- **Tracking read status across devices.**

## How do users receive notifications?

Expand Down
42 changes: 42 additions & 0 deletions docs/web3inbox/backend-integration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,46 @@ curl 'https://notify.walletconnect.com/<PROJECT_ID>/subscribers' \

</Tabs>

## Mark all notifications as read

Unless marked as read by an app frontend, notifications will always be "unread". Because of this, when you
initially add support for displaying unread status or unread count to your frontend, users that have received
notifications in the past will have notifications display as "unread" even if they already have seen them.
This can potentially be an undesireable user experience.

To mitigate this problem, you can make a one-time call to the `/v1/<project-id>/mark-all-as-read` API endpoint
which will mark all existing notifications as read. Notifications marked as read in this way will not contribute
to read rate analytics. After you deploy your integration of unread states, you can call this endpoint to
reset the unread state for all of your existing notifications.

<Tabs queryString={'api-client'}>

<TabItem value="javascript" label="JavaScript">

```typescript
const PROJECT_ID = '<PROJECT_ID>'
const NOTIFY_API_SECRET = '<NOTIFY_API_SECRET>'
const response = await fetch(`https://notify.walletconnect.com/v1/${PROJECT_ID}/mark-all-as-read`, {
method: 'POST',
headers: {
Authorization: `Bearer ${NOTIFY_API_SECRET}`
}
})
```

</TabItem>

<TabItem value="curl" label="cURL">

```bash
curl -X POST 'https://notify.walletconnect.com/v1/<PROJECT_ID>/mark-all-as-read' \
--header 'Authorization: Bearer <NOTIFY_API_SECRET>'
```

</TabItem>

</Tabs>

## Rate limits

To protect our system and subscribers, various limits and rate limits are in-place.
Expand All @@ -221,3 +261,5 @@ Rate limits are implemented as [token bucket](https://en.wikipedia.org/wiki/Toke
- Each app can call this endpoint 100 times per second with a burst up to 100. Rate limited requests will return a 429 status code.
- `GET /<project-id>/subscribers`
- Each app can call this endpoint 1 time every 5 minutes with a burst up to 2. Rate limited requests will return a 429 status code.
- `POST /v1/<project-id>/mark-all-as-read`
- Each app can call this endpoint 1 time per hour with a burst up to 5. Rate limited requests will return a 429 status code.
100 changes: 94 additions & 6 deletions docs/web3inbox/frontend-integration/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ const isSubscribed = Boolean(subscription)
scope: ScopeMap
expiry: number
symkey: string
unreadCount: number
}
```

Expand Down Expand Up @@ -304,6 +305,7 @@ client.watchSubscriptions(subscriptions => console.log({ subscriptions }))
scope: ScopeMap
expiry: number
symkey: string
unreadCount: number
}
```

Expand All @@ -327,24 +329,75 @@ Get notifications
// watch notifications of current account's subscription to current dapp
const notificationsPerPage = 5
const isInfiniteScroll = true
const unreadFirst = true

const {
data: notifications,
nextPage,
markNotificationsAsRead,
markAllNotificationsAsRead
} = useNotifications(
notificationsPerPage,
isInfiniteScroll,
account,
domain,
unreadFirst,
onRead // optional function to run whenever messages are read
)

// marking a single notification as read
await notifications[0].markAsRead();

// mark specific notifications as read for default account and under default domain
await markNotificationsAsRead(notifications.slice(2).map(n => n.id));

// mark specific notifications as read for specified account under default domain
await markNotificationsAsRead(notifications.slice(2).map(n => n.id), differentAccount);

// mark specific notifications as read for default account under specified domain
await markNotificationsAsRead(notifications.slice(2).map(n => n.id), undefined, differentDomain);

// mark specific notifications as read for specified account under specified domain
await markNotificationsAsRead(notifications.slice(2).map(n => n.id), differentAccount, differentDomain);

// mark all notifications as read for default account under default domain
await markAllNotificationsAsRead();

// mark all notifications as read for specified account under default domain
await markAllNotificationsAsRead(differentAccount);

// mark all notifications as read for default account under specified domain
await markAllNotificationsAsRead(undefined, differentDomain);

// mark all notifications as read for specified account under specified domain
await markAllNotificationsAsRead(differentAccount, differentDomain);


const { data: notifications, nextPage } = useNotifications(notificationsPerPage, isInfiniteScroll)
```

#### References

- **notificationsPerPage:** Number representing how many notifications to get per fetch
- **isInfiniteScroll:** Whether or not to keep already fetched notifications when getting next page
- **useNotifications()**
- **notificationsPerPage:** Number representing how many notifications to get per fetch
- **isInfiniteScroll:** Whether or not to keep already fetched notifications when getting next page
- **params:* (optional) Additional parameters
- **unreadFirst:** (optional, default `true`, since 1.3.0) Whether or not unread messages should be sorted at the top, regardless of timestamp
- **nextPage:**: A function to be called to fetch the next page of notifications
- **notifications:** Array of notifications, of type
- **notification.read:** Mark the notification as read
- **markNotificationsAsRead**: Takes an array of notification IDs and marks them as read. Max 1000 IDs
- **markAllNotificationsAsRead**: Mark all notifications as read.

```ts
{
title: string
sentAt: number
body: string
id: string
isRead: boolean
url: string | null
type: string
markAsRead: () => Promise<void>
chris13524 marked this conversation as resolved.
Show resolved Hide resolved
chris13524 marked this conversation as resolved.
Show resolved Hide resolved
}
```

Expand All @@ -362,26 +415,61 @@ const notificationsPage = client.getNotificationHistory({

const notificationsPerPage = 5
const isInfiniteScroll = true
const unreadFirst = true

const { nextPage } = client.pageNotifications(notificationsPerPage, isInfiniteScroll)(onUpdate)
let notifications = []

const onUpdate = ({notifications: fetchedNotifications}: GetNotificationsReturn) => {
notifications = fetchedNotifications
}

const {
nextPage,
markNotificationAsRead,
markAllNotificationsAsRead
} = client.pageNotifications(
notificationsPerPage,
isInfiniteScroll,
specifiedAccount // OR undefined,
specifiedDomain // OR undefined,
unreadFirst
)(onUpdate)


// marking a single notification as read
await notifications[0].markAsRead();

// mark specific notifications as read
await markNotificationsAsRead(notifications.slice(2).map(n => n.id));

// mark all notifications as read
await markAllNotificationsAsRead();
```

#### References

- **notificationsPerPage:** Number representing how many notifications to get per fetch
- **isInfiniteScroll:** Whether or not to keep already fetched notifications when getting next page
- **pageNotifications:**
- **notificationsPerPage:** Number representing how many notifications to get per fetch
- **isInfiniteScroll:** Whether or not to keep already fetched notifications when getting next page
- **params:* (optional) Additional parameters
- **unreadFirst:** (optional, default `true`, since 1.3.0) Whether or not unread messages should be sorted at the top, regardless of timestamp
- **onUpdate:**: A callback that will be called whenever notifications get updated
- **nextPage:**: A function to be called to fetch the next page of notifications
- **notifications:** Array of notifications, of type
- **notification.markAsRead:** Mark the notification as read
- **markNotificationsAsRead**: Takes an array of notification IDs and marks them as read. Max 1000 IDs
- **markAllNotificationsAsRead**: Mark all notifications as read.

```ts
{
title: string
sentAt: number
body: string
id: string
isRead: boolean // since 1.3.0
url: string | null
type: string
read: () => Promise<void> // since 1.3.0
}
```

Expand Down
28 changes: 17 additions & 11 deletions docs/web3inbox/frontend-integration/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ import {
import { useCallback, useEffect } from 'react'
import { useSignMessage, useAccount } from 'wagmi'

import Messages from './Messages'
import Notifications from './Notifications'

export default function App() {
// Wagmi Hooks
Expand Down Expand Up @@ -134,7 +134,7 @@ export default function App() {
{isSubscribed ? 'Unsubscribe' : 'Subscribe'}
</button>
<hr />
{isSubscribed ? <Messages /> : null}
{isSubscribed ? <Notifications /> : null}
</div>
</div>
)}
Expand All @@ -145,34 +145,39 @@ export default function App() {
```

```tsx
//Messages.tsx
// Notifications.tsx
import { useNotifications } from '@web3inbox/react'
import React from 'react'
import styles from '@/styles/Messages.module.css'
import styles from '@/styles/Notifications.module.css'

function Messages() {
const { data: notifications } = useNotifications(3, false)
function Notifications() {
const { data: subscription } = useSubscription()
const { data: notifications } = useNotifications(5)

return (
<div>
<h2 className={styles.heading}>Previous Messages</h2>
<div className={styles.messageContainer}>
<h2 className={styles.heading}>Notifications</h2>
<p>You have {subscription.unreadCount} unread notifications.</p>
<div className={styles.notificationsContainer}>
{!notifications?.length ? (
<p className={styles.fallbackText}>No messages yet.</p>
<p className={styles.fallbackText}>No notifications yet.</p>
) : (
notifications.map(({ id, ...message }) => (
<div key={id} className={styles.message}>
<h3>{message.title}</h3>
<p>{message.body}</p>
<p>{message.isRead ? "Read" : "Unread"}</p>
<button onClick={message.markAsRead}>Mark as read</button>
</div>
))
)}
</div>
<button onClick={nextPage}>Next page</button>
</div>
)
}

export default Messages
export default Notifications
```

</PlatformTabItem>
Expand Down Expand Up @@ -209,7 +214,8 @@ client.pageNotifications(
isInfiniteScroll
)(notifications => {
// add logic to display notifications here.
//if isInfiniteScroll is true, notifications will contain all notifications fetched so far, else it will only fetch current page
// if isInfiniteScroll is true, notifications will contain all notifications fetched so far, else it will only fetch current page
// See API docs for more information on `pageNotifications()` and how to use `notifications`
})
```

Expand Down
Loading