feat(auth-wallet): add handling for push methods/events (#145)
Co-authored-by: Ben Kremer <ben@walletconnect.com>
This commit is contained in:
parent
2ff0874778
commit
34cf292b06
1022
wallets/react-wallet-auth/package-lock.json
generated
1022
wallets/react-wallet-auth/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,12 +15,15 @@
|
||||
"@nextui-org/react": "1.0.8-beta.5",
|
||||
"@polkadot/keyring": "^10.1.2",
|
||||
"@solana/web3.js": "1.43.0",
|
||||
"@walletconnect/auth-client": "2.1.0",
|
||||
"@walletconnect/utils": "2.7.6",
|
||||
"@walletconnect/auth-client": "^2.1.0",
|
||||
"@walletconnect/core": "^2.7.6",
|
||||
"@walletconnect/push-client": "0.10.0",
|
||||
"@walletconnect/utils": "^2.7.6",
|
||||
"bs58": "5.0.0",
|
||||
"cosmos-wallet": "1.2.0",
|
||||
"ethers": "5.6.6",
|
||||
"framer-motion": "6.3.3",
|
||||
"lokijs": "^1.5.12",
|
||||
"mnemonic-keyring": "1.4.0",
|
||||
"next": "12.1.5",
|
||||
"react": "17.0.2",
|
||||
@ -33,7 +36,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "17.0.35",
|
||||
"@types/react": "18.0.9",
|
||||
"@walletconnect/types": "2.7.6",
|
||||
"@walletconnect/types": "^2.7.6",
|
||||
"eslint": "8.15.0",
|
||||
"eslint-config-next": "12.1.6",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
|
10
wallets/react-wallet-auth/public/icons/notification-icon.svg
Normal file
10
wallets/react-wallet-auth/public/icons/notification-icon.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="15" height="23" viewBox="0 0 15 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.0001 0C6.07054 0 3.52589 2.0154 2.8549 4.86708L0.578472 14.5419C0.283081 15.7973 1.23561 17 2.52531 17H15.4749C16.7646 17 17.7171 15.7973 17.4217 14.5419L15.1453 4.86709C14.4743 2.0154 11.9297 0 9.0001 0Z" fill="url(#gradient)" />
|
||||
<path d="M12.7231 19.9655C13.0268 19.1947 12.3285 15.5 11.5001 15.5H6.5001C5.67167 15.5 4.97344 19.1947 5.27708 19.9655C5.86177 21.4497 7.30827 22.5 9.0001 22.5C10.6919 22.5 12.1384 21.4497 12.7231 19.9655Z" fill="url(#gradient" />
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0" y1="0" x2="0" y2="100%">
|
||||
<stop offset="0%" stop-color="#A562D5" />
|
||||
<stop offset="100%" stop-color="#306FEB" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 786 B |
@ -1,5 +1,6 @@
|
||||
import ModalStore from '@/store/ModalStore'
|
||||
import AuthenticationRequestModal from '@/views/AuthenticationRequestModal'
|
||||
import PushRequestModal from '@/views/PushRequestModal'
|
||||
import { Modal as NextModal } from '@nextui-org/react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
|
||||
@ -9,6 +10,7 @@ export default function Modal() {
|
||||
return (
|
||||
<NextModal blur open={open} style={{ border: '1px solid rgba(139, 139, 139, 0.4)' }}>
|
||||
{view === 'AuthenticationRequest' && <AuthenticationRequestModal />}
|
||||
{view === 'PushRequest' && <PushRequestModal />}
|
||||
</NextModal>
|
||||
)
|
||||
}
|
||||
|
@ -23,6 +23,17 @@ export default function Navigation() {
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link href="/notifications" passHref>
|
||||
<a className="navLink">
|
||||
<Image
|
||||
alt="notifications icon"
|
||||
src="/icons/notification-icon.svg"
|
||||
width={25}
|
||||
height={25}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link href="/walletconnect" passHref>
|
||||
<a className="navLink">
|
||||
<Avatar
|
||||
|
@ -0,0 +1,75 @@
|
||||
import { truncate } from '@/utils/HelperUtil'
|
||||
import { Avatar, Button, Card, Link, Text, Tooltip } from '@nextui-org/react'
|
||||
import Image from 'next/image'
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
interface IProps {
|
||||
logo?: string
|
||||
title?: string
|
||||
body?: string
|
||||
url?: string
|
||||
publishedAt?: number
|
||||
onDelete: () => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default function NotificationItem({
|
||||
logo,
|
||||
title,
|
||||
body,
|
||||
url,
|
||||
publishedAt,
|
||||
onDelete
|
||||
}: IProps) {
|
||||
return (
|
||||
<Card
|
||||
bordered
|
||||
borderWeight="light"
|
||||
css={{
|
||||
position: 'relative',
|
||||
marginBottom: '$6',
|
||||
minHeight: '70px'
|
||||
}}
|
||||
>
|
||||
<Card.Body
|
||||
css={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<Avatar src={logo} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text h5 css={{ marginLeft: '$9' }}>
|
||||
{title}
|
||||
</Text>
|
||||
{/* <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
|
||||
</div> */}
|
||||
<Text h6 css={{ marginLeft: '$9' }}>
|
||||
{body}
|
||||
</Text>
|
||||
{publishedAt && (
|
||||
<Text span css={{ marginLeft: '$9', fontSize: '12px' }}>
|
||||
{new Date(publishedAt).toLocaleDateString()} -{' '}
|
||||
{new Date(publishedAt).toLocaleTimeString()}
|
||||
</Text>
|
||||
)}
|
||||
<Link href={url} css={{ marginLeft: '$9' }} target="_blank" rel="noopener noreferrer">
|
||||
{url}
|
||||
</Link>
|
||||
</div>
|
||||
<Tooltip content="Delete" placement="left">
|
||||
<Button size="sm" color="error" flat onClick={onDelete} css={{ minWidth: 'auto' }}>
|
||||
<Image src={'/icons/delete-icon.svg'} width={15} height={15} alt="delete icon" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import SettingsStore from '@/store/SettingsStore'
|
||||
import { createOrRestoreEIP155Wallet } from '@/utils/EIP155WalletUtil'
|
||||
import { createAuthClient } from '@/utils/WalletConnectUtil'
|
||||
import { createAuthClient, createPushClient } from '@/utils/WalletConnectUtil'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
export default function useInitialization() {
|
||||
@ -11,6 +11,7 @@ export default function useInitialization() {
|
||||
const { eip155Addresses } = createOrRestoreEIP155Wallet()
|
||||
SettingsStore.setEIP155Address(eip155Addresses[0])
|
||||
await createAuthClient()
|
||||
await createPushClient()
|
||||
setInitialized(true)
|
||||
} catch (err: unknown) {
|
||||
alert(err)
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
|
||||
import ModalStore from '@/store/ModalStore'
|
||||
import { authClient } from '@/utils/WalletConnectUtil'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { authClient, pushClient } from '@/utils/WalletConnectUtil'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function useWalletConnectEventsManager(initialized: boolean) {
|
||||
/******************************************************************************
|
||||
@ -9,6 +8,7 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
|
||||
*****************************************************************************/
|
||||
useEffect(() => {
|
||||
if (initialized) {
|
||||
// Auth client events
|
||||
authClient.on('auth_request', ({ id, params }) => {
|
||||
console.log('auth_request', { id, params })
|
||||
ModalStore.open('AuthenticationRequest', {
|
||||
@ -19,5 +19,19 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (pushClient) {
|
||||
// Push client events
|
||||
pushClient.on('push_proposal', async ({ id, topic, params }) => {
|
||||
console.log('push_proposal', { id, topic, params })
|
||||
ModalStore.open('PushRequest', {
|
||||
pushRequest: {
|
||||
id,
|
||||
topic,
|
||||
params
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}, [initialized])
|
||||
}
|
||||
|
103
wallets/react-wallet-auth/src/pages/notifications.tsx
Normal file
103
wallets/react-wallet-auth/src/pages/notifications.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import NotificationItem from '@/components/NotificationItem'
|
||||
import PageHeader from '@/components/PageHeader'
|
||||
import { getAndFormatNotifications, pushClient } from '@/utils/WalletConnectUtil'
|
||||
import { Text, Collapse, Grid, Avatar, Button } from '@nextui-org/react'
|
||||
import { PushClientTypes } from '@walletconnect/push-client'
|
||||
import Image from 'next/image'
|
||||
import { Fragment, useCallback, useEffect, useState } from 'react'
|
||||
|
||||
export default function NotificationsPage() {
|
||||
const [notifications, setNotifications] = useState<
|
||||
{
|
||||
subscription: PushClientTypes.PushSubscription
|
||||
messages: PushClientTypes.PushMessageRecord[]
|
||||
}[]
|
||||
>([])
|
||||
|
||||
const handleDeleteNotification = useCallback(async (messageId: number) => {
|
||||
pushClient.deletePushMessage({ id: messageId })
|
||||
const formattedNotifications = getAndFormatNotifications()
|
||||
setNotifications(formattedNotifications)
|
||||
}, [])
|
||||
|
||||
const handleDeleteSubscription = useCallback(async (topic: string) => {
|
||||
await pushClient.deleteSubscription({ topic })
|
||||
const formattedNotifications = getAndFormatNotifications()
|
||||
setNotifications(formattedNotifications)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
pushClient.on('push_message', () => {
|
||||
const formattedNotifications = getAndFormatNotifications()
|
||||
setNotifications(formattedNotifications)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Get notifications for the initial state
|
||||
useEffect(() => {
|
||||
const formattedNotifications = getAndFormatNotifications()
|
||||
setNotifications(formattedNotifications)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader title="Notifications" />
|
||||
|
||||
<Grid.Container gap={2}>
|
||||
<Grid>
|
||||
<Collapse.Group shadow>
|
||||
{notifications.map(notification => (
|
||||
<Collapse
|
||||
key={notification.subscription.topic}
|
||||
title={<Text h4>{notification.subscription.metadata.name}</Text>}
|
||||
subtitle={notification.subscription.metadata.description}
|
||||
contentLeft={
|
||||
<Avatar
|
||||
size="lg"
|
||||
src={notification.subscription.metadata.icons[0]}
|
||||
color="secondary"
|
||||
bordered
|
||||
squared
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
bordered
|
||||
css={{
|
||||
width: '100%',
|
||||
marginBottom: '$6'
|
||||
}}
|
||||
color="error"
|
||||
auto
|
||||
onClick={() => handleDeleteSubscription(notification.subscription.topic)}
|
||||
>
|
||||
<Image src={'/icons/delete-icon.svg'} width={15} height={15} alt="delete icon" />
|
||||
Delete subscription
|
||||
</Button>
|
||||
{notification.messages.length ? (
|
||||
notification.messages
|
||||
.sort((a, b) => b.publishedAt - a.publishedAt)
|
||||
.map(({ id, message, publishedAt }) => (
|
||||
<NotificationItem
|
||||
key={id}
|
||||
logo={message.icon}
|
||||
url={message.url}
|
||||
title={message.title}
|
||||
body={message.body}
|
||||
publishedAt={publishedAt}
|
||||
onDelete={() => handleDeleteNotification(id)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Text css={{ opacity: '0.5', textAlign: 'center', marginTop: '$6' }}>
|
||||
No notifications
|
||||
</Text>
|
||||
)}
|
||||
</Collapse>
|
||||
))}
|
||||
</Collapse.Group>
|
||||
</Grid>
|
||||
</Grid.Container>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
import { SessionTypes, SignClientTypes } from '@walletconnect/types'
|
||||
import { proxy } from 'valtio'
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
interface ModalData {
|
||||
authenticationRequest: any
|
||||
authenticationRequest?: any
|
||||
pushRequest?: any
|
||||
}
|
||||
|
||||
interface State {
|
||||
open: boolean
|
||||
view?: 'AuthenticationRequest'
|
||||
view?: 'AuthenticationRequest' | 'PushRequest'
|
||||
data?: ModalData
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,20 @@
|
||||
import AuthClient from '@walletconnect/auth-client'
|
||||
import pkg from '@walletconnect/auth-client/package.json'
|
||||
import { Core } from '@walletconnect/core'
|
||||
import { WalletClient } from '@walletconnect/push-client'
|
||||
|
||||
const core = new Core({
|
||||
projectId: process.env.NEXT_PUBLIC_PROJECT_ID!
|
||||
})
|
||||
|
||||
console.log(`AuthClient@${pkg.version}`)
|
||||
|
||||
export let authClient: AuthClient
|
||||
export let pushClient: WalletClient
|
||||
|
||||
export async function createAuthClient() {
|
||||
authClient = await AuthClient.init({
|
||||
core,
|
||||
projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
|
||||
relayUrl: process.env.NEXT_PUBLIC_RELAY_URL || 'wss://relay.walletconnect.com',
|
||||
metadata: {
|
||||
@ -16,4 +24,36 @@ export async function createAuthClient() {
|
||||
icons: ['https://avatars.githubusercontent.com/u/37784886']
|
||||
}
|
||||
})
|
||||
const authClientId = await authClient.core.crypto.getClientId()
|
||||
console.log({ authClientId })
|
||||
}
|
||||
|
||||
export async function createPushClient() {
|
||||
pushClient = await WalletClient.init({
|
||||
logger: 'debug',
|
||||
core,
|
||||
projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
|
||||
relayUrl: process.env.NEXT_PUBLIC_RELAY_URL || 'wss://relay.walletconnect.com'
|
||||
})
|
||||
const pushClientId = await pushClient.core.crypto.getClientId()
|
||||
console.log({ pushClientId })
|
||||
}
|
||||
|
||||
export const getAndFormatNotifications = () => {
|
||||
if (!pushClient) {
|
||||
return []
|
||||
}
|
||||
const activeSubscriptions = pushClient.getActiveSubscriptions()
|
||||
const allMessagesWithSubscription = Object.entries(activeSubscriptions)
|
||||
.map(([topic, subscription]) => ({
|
||||
subscription,
|
||||
messages: Object.values(
|
||||
pushClient.getMessageHistory({
|
||||
topic
|
||||
})
|
||||
)
|
||||
}))
|
||||
.reverse()
|
||||
|
||||
return allMessagesWithSubscription
|
||||
}
|
||||
|
71
wallets/react-wallet-auth/src/views/PushRequestModal.tsx
Normal file
71
wallets/react-wallet-auth/src/views/PushRequestModal.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { Fragment, useCallback, useEffect, useState } from 'react'
|
||||
import RequestModalContainer from '@/components/RequestModalContainer'
|
||||
import ModalStore from '@/store/ModalStore'
|
||||
import { Button, Col, Link, Modal, Row, Text } from '@nextui-org/react'
|
||||
import { createOrRestoreEIP155Wallet } from '@/utils/EIP155WalletUtil'
|
||||
import { pushClient } from '@/utils/WalletConnectUtil'
|
||||
import { getWalletAddressFromParams } from '@/utils/HelperUtil'
|
||||
|
||||
export default function PushRequestModal() {
|
||||
const pushRequest = ModalStore.state.data?.pushRequest
|
||||
const { params, id } = pushRequest
|
||||
const [iss, setIss] = useState<string>()
|
||||
const { eip155Wallets, eip155Addresses } = createOrRestoreEIP155Wallet()
|
||||
useEffect(() => {
|
||||
const iss = `did:pkh:${pushRequest.params.account}`
|
||||
setIss(iss)
|
||||
}, [pushRequest.params.account, eip155Addresses])
|
||||
|
||||
const onSign = useCallback(
|
||||
async (message: string) => {
|
||||
const wallet = eip155Wallets[getWalletAddressFromParams(eip155Addresses, params.account)]
|
||||
const signature = await wallet.signMessage(message)
|
||||
return signature
|
||||
},
|
||||
[eip155Wallets, eip155Addresses, params.account]
|
||||
)
|
||||
|
||||
const onApprove = useCallback(async () => {
|
||||
if (pushRequest && iss) {
|
||||
await pushClient.approve({ id, onSign })
|
||||
ModalStore.close()
|
||||
}
|
||||
}, [pushRequest, id, iss, onSign])
|
||||
|
||||
if (!pushRequest) {
|
||||
return <Text>Missing push request</Text>
|
||||
}
|
||||
|
||||
// Handle reject action
|
||||
async function onReject() {
|
||||
await pushClient.reject({ id, reason: 'User rejected push subscription request' })
|
||||
|
||||
ModalStore.close()
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<RequestModalContainer title="Push Request">
|
||||
<Row>
|
||||
<Col>
|
||||
<Text h5>Message</Text>
|
||||
<Text style={{ whiteSpace: 'pre-wrap' }} color="$gray400">
|
||||
Subscribe to{' '}
|
||||
<Link href={params.metadata.url} target="_blank" rel="noopener noreferrer">
|
||||
{params.metadata.url}
|
||||
</Link>
|
||||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</RequestModalContainer>
|
||||
<Modal.Footer>
|
||||
<Button auto flat color="error" onClick={onReject}>
|
||||
Reject
|
||||
</Button>
|
||||
<Button auto flat color="success" onClick={onApprove}>
|
||||
Approve
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user