Skip to content

Commit 642ce7f

Browse files
authored
Merge pull request #1376 from WalletConnect/feat/web3inbox-read-unread
feat: read/unread status
2 parents 08d5fe8 + 23a99d7 commit 642ce7f

File tree

5 files changed

+162
-22
lines changed

5 files changed

+162
-22
lines changed

docs/api/notify/usage.mdx

+8-5
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,14 @@ const accountSubscriptions = notifyClient.getActiveSubscriptions({
226226

227227
```javascript
228228
// Will return all past Notify messages for the provided subscription topic, keyed by messageId.
229-
const messageHistory = notifyClient.getNotificationHistory({
230-
topic,
231-
limit: 10,
232-
startingAfter: 'notification-id'
233-
})
229+
const messageHistory = notifyClient.getNotificationHistory({ topic, limit: 10, startingAfter: 'notification-id', unreadFirst: true })
230+
```
231+
232+
#### Marking notification as read
233+
234+
```javascript
235+
// Will mark the 2 specific notifications as read
236+
const messageHistory = notifyClient.markNotificationsAsRead({ topic, notificationIds: ["notification-1", "notification-2" ] })
234237
```
235238

236239
</PlatformTabItem>

docs/web3inbox/about.mdx

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Some of the key features of the Web3Inbox SDK include:
2323
- **Device push notifications:** Push notifications to the user's wallet (if it supports Notify API) or the [Web3Inbox.com app](https://app.web3inbox.com).
2424
- **Notification history:** Notifications are stored and can be accessed from any device.
2525
- **Spam protection/subscription control.** Using notification types, subscribers can opt-out of certain notification types they do not want to receive.
26+
- **Tracking read status across devices.**
2627

2728
## How do users receive notifications?
2829

docs/web3inbox/backend-integration.mdx

+42
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,46 @@ curl 'https://notify.walletconnect.com/<PROJECT_ID>/subscribers' \
206206

207207
</Tabs>
208208

209+
## Mark all notifications as read
210+
211+
Unless marked as read by an app frontend, notifications will always be "unread". Because of this, when you
212+
initially add support for displaying unread status or unread count to your frontend, users that have received
213+
notifications in the past will have notifications display as "unread" even if they already have seen them.
214+
This can potentially be an undesireable user experience.
215+
216+
To mitigate this problem, you can make a one-time call to the `/v1/<project-id>/mark-all-as-read` API endpoint
217+
which will mark all existing notifications as read. Notifications marked as read in this way will not contribute
218+
to read rate analytics. After you deploy your integration of unread states, you can call this endpoint to
219+
reset the unread state for all of your existing notifications.
220+
221+
<Tabs queryString={'api-client'}>
222+
223+
<TabItem value="javascript" label="JavaScript">
224+
225+
```typescript
226+
const PROJECT_ID = '<PROJECT_ID>'
227+
const NOTIFY_API_SECRET = '<NOTIFY_API_SECRET>'
228+
const response = await fetch(`https://notify.walletconnect.com/v1/${PROJECT_ID}/mark-all-as-read`, {
229+
method: 'POST',
230+
headers: {
231+
Authorization: `Bearer ${NOTIFY_API_SECRET}`
232+
}
233+
})
234+
```
235+
236+
</TabItem>
237+
238+
<TabItem value="curl" label="cURL">
239+
240+
```bash
241+
curl -X POST 'https://notify.walletconnect.com/v1/<PROJECT_ID>/mark-all-as-read' \
242+
--header 'Authorization: Bearer <NOTIFY_API_SECRET>'
243+
```
244+
245+
</TabItem>
246+
247+
</Tabs>
248+
209249
## Rate limits
210250

211251
To protect our system and subscribers, various limits and rate limits are in-place.
@@ -219,3 +259,5 @@ Rate limits are implemented as [token bucket](https://en.wikipedia.org/wiki/Toke
219259
- 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.
220260
- `GET /<project-id>/subscribers`
221261
- 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.
262+
- `POST /v1/<project-id>/mark-all-as-read`
263+
- 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.

docs/web3inbox/frontend-integration/api.mdx

+94-6
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ const isSubscribed = Boolean(subscription)
249249
scope: ScopeMap
250250
expiry: number
251251
symkey: string
252+
unreadCount: number
252253
}
253254
```
254255

@@ -307,6 +308,7 @@ client.watchSubscriptions(subscriptions => console.log({ subscriptions }))
307308
scope: ScopeMap
308309
expiry: number
309310
symkey: string
311+
unreadCount: number
310312
}
311313
```
312314

@@ -330,24 +332,75 @@ Get notifications
330332
// watch notifications of current account's subscription to current dapp
331333
const notificationsPerPage = 5
332334
const isInfiniteScroll = true
335+
const unreadFirst = true
336+
337+
const {
338+
data: notifications,
339+
nextPage,
340+
markNotificationsAsRead,
341+
markAllNotificationsAsRead
342+
} = useNotifications(
343+
notificationsPerPage,
344+
isInfiniteScroll,
345+
account,
346+
domain,
347+
unreadFirst,
348+
onRead // optional function to run whenever messages are read
349+
)
350+
351+
// marking a single notification as read
352+
await notifications[0].markAsRead();
353+
354+
// mark specific notifications as read for default account and under default domain
355+
await markNotificationsAsRead(notifications.slice(2).map(n => n.id));
356+
357+
// mark specific notifications as read for specified account under default domain
358+
await markNotificationsAsRead(notifications.slice(2).map(n => n.id), differentAccount);
359+
360+
// mark specific notifications as read for default account under specified domain
361+
await markNotificationsAsRead(notifications.slice(2).map(n => n.id), undefined, differentDomain);
362+
363+
// mark specific notifications as read for specified account under specified domain
364+
await markNotificationsAsRead(notifications.slice(2).map(n => n.id), differentAccount, differentDomain);
365+
366+
// mark all notifications as read for default account under default domain
367+
await markAllNotificationsAsRead();
368+
369+
// mark all notifications as read for specified account under default domain
370+
await markAllNotificationsAsRead(differentAccount);
371+
372+
// mark all notifications as read for default account under specified domain
373+
await markAllNotificationsAsRead(undefined, differentDomain);
374+
375+
// mark all notifications as read for specified account under specified domain
376+
await markAllNotificationsAsRead(differentAccount, differentDomain);
377+
333378

334-
const { data: notifications, nextPage } = useNotifications(notificationsPerPage, isInfiniteScroll)
335379
```
336380

337381
#### References
338382

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

343394
```ts
344395
{
345396
title: string
346397
sentAt: number
347398
body: string
348399
id: string
400+
isRead: boolean
349401
url: string | null
350402
type: string
403+
markAsRead: () => Promise<void>
351404
}
352405
```
353406

@@ -365,26 +418,61 @@ const notificationsPage = client.getNotificationHistory({
365418

366419
const notificationsPerPage = 5
367420
const isInfiniteScroll = true
421+
const unreadFirst = true
368422

369-
const { nextPage } = client.pageNotifications(notificationsPerPage, isInfiniteScroll)(onUpdate)
423+
let notifications = []
424+
425+
const onUpdate = ({notifications: fetchedNotifications}: GetNotificationsReturn) => {
426+
notifications = fetchedNotifications
427+
}
428+
429+
const {
430+
nextPage,
431+
markNotificationAsRead,
432+
markAllNotificationsAsRead
433+
} = client.pageNotifications(
434+
notificationsPerPage,
435+
isInfiniteScroll,
436+
specifiedAccount // OR undefined,
437+
specifiedDomain // OR undefined,
438+
unreadFirst
439+
)(onUpdate)
440+
441+
442+
// marking a single notification as read
443+
await notifications[0].markAsRead();
444+
445+
// mark specific notifications as read
446+
await markNotificationsAsRead(notifications.slice(2).map(n => n.id));
447+
448+
// mark all notifications as read
449+
await markAllNotificationsAsRead();
370450
```
371451

372452
#### References
373453

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

380466
```ts
381467
{
382468
title: string
383469
sentAt: number
384470
body: string
385471
id: string
472+
isRead: boolean // since 1.3.0
386473
url: string | null
387474
type: string
475+
read: () => Promise<void> // since 1.3.0
388476
}
389477
```
390478

docs/web3inbox/frontend-integration/usage.mdx

+17-11
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ import {
7878
import { useCallback, useEffect } from 'react'
7979
import { useSignMessage, useAccount } from 'wagmi'
8080

81-
import Messages from './Messages'
81+
import Notifications from './Notifications'
8282

8383
export default function App() {
8484
// Wagmi Hooks
@@ -134,7 +134,7 @@ export default function App() {
134134
{isSubscribed ? 'Unsubscribe' : 'Subscribe'}
135135
</button>
136136
<hr />
137-
{isSubscribed ? <Messages /> : null}
137+
{isSubscribed ? <Notifications /> : null}
138138
</div>
139139
</div>
140140
)}
@@ -145,34 +145,39 @@ export default function App() {
145145
```
146146

147147
```tsx
148-
//Messages.tsx
148+
// Notifications.tsx
149149
import { useNotifications } from '@web3inbox/react'
150150
import React from 'react'
151-
import styles from '@/styles/Messages.module.css'
151+
import styles from '@/styles/Notifications.module.css'
152152

153-
function Messages() {
154-
const { data: notifications } = useNotifications(3, false)
153+
function Notifications() {
154+
const { data: subscription } = useSubscription()
155+
const { data: notifications } = useNotifications(5)
155156

156157
return (
157158
<div>
158-
<h2 className={styles.heading}>Previous Messages</h2>
159-
<div className={styles.messageContainer}>
159+
<h2 className={styles.heading}>Notifications</h2>
160+
<p>You have {subscription.unreadCount} unread notifications.</p>
161+
<div className={styles.notificationsContainer}>
160162
{!notifications?.length ? (
161-
<p className={styles.fallbackText}>No messages yet.</p>
163+
<p className={styles.fallbackText}>No notifications yet.</p>
162164
) : (
163165
notifications.map(({ id, ...message }) => (
164166
<div key={id} className={styles.message}>
165167
<h3>{message.title}</h3>
166168
<p>{message.body}</p>
169+
<p>{message.isRead ? "Read" : "Unread"}</p>
170+
<button onClick={message.markAsRead}>Mark as read</button>
167171
</div>
168172
))
169173
)}
170174
</div>
175+
<button onClick={nextPage}>Next page</button>
171176
</div>
172177
)
173178
}
174179

175-
export default Messages
180+
export default Notifications
176181
```
177182

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

0 commit comments

Comments
 (0)