From 9799efb8759b0288735d8547004cc89c0ad7e829 Mon Sep 17 00:00:00 2001 From: Michael J Feher Date: Fri, 12 Apr 2024 08:18:35 -0400 Subject: [PATCH] chore: cleanup demo application --- .../src/connect/connect.gateway.ts | 40 +-- sites/dapp-ui/src/App.tsx | 78 +++--- sites/dapp-ui/src/components/Chat.tsx | 89 ------- .../home => components}/ConnectModal.tsx | 4 +- .../src/components/user/Credential.tsx | 7 - .../src/components/user/SessionMenu.tsx | 4 +- sites/dapp-ui/src/hooks/index.ts | 7 + sites/dapp-ui/src/hooks/useAccountInfo.ts | 18 ++ sites/dapp-ui/src/hooks/useAlgod.ts | 16 ++ sites/dapp-ui/src/hooks/usePeerConnection.ts | 14 +- .../user => hooks}/useUserState.ts | 0 sites/dapp-ui/src/pages/connected.tsx | 152 ++++++++++-- .../src/pages/dashboard/Registered.tsx | 77 ------ .../pages/dashboard/WaitForRegistration.tsx | 83 ------- .../pages/{home/GetStarted.tsx => home.tsx} | 21 +- sites/dapp-ui/src/pages/index.ts | 3 + .../{peering/WaitForPeers.tsx => peering.tsx} | 38 +-- sites/dapp-ui/src/store.ts | 2 +- sites/dapp-ui/tsconfig.json | 6 +- sites/dapp-ui/vite.config.ts | 232 ++++++++++-------- 20 files changed, 419 insertions(+), 472 deletions(-) delete mode 100644 sites/dapp-ui/src/components/Chat.tsx rename sites/dapp-ui/src/{pages/home => components}/ConnectModal.tsx (97%) delete mode 100644 sites/dapp-ui/src/components/user/Credential.tsx create mode 100644 sites/dapp-ui/src/hooks/index.ts create mode 100644 sites/dapp-ui/src/hooks/useAccountInfo.ts create mode 100644 sites/dapp-ui/src/hooks/useAlgod.ts rename sites/dapp-ui/src/{components/user => hooks}/useUserState.ts (100%) delete mode 100644 sites/dapp-ui/src/pages/dashboard/Registered.tsx delete mode 100644 sites/dapp-ui/src/pages/dashboard/WaitForRegistration.tsx rename sites/dapp-ui/src/pages/{home/GetStarted.tsx => home.tsx} (68%) create mode 100644 sites/dapp-ui/src/pages/index.ts rename sites/dapp-ui/src/pages/{peering/WaitForPeers.tsx => peering.tsx} (74%) diff --git a/services/liquid-auth-api-js/src/connect/connect.gateway.ts b/services/liquid-auth-api-js/src/connect/connect.gateway.ts index 600b2cd..bb69a89 100644 --- a/services/liquid-auth-api-js/src/connect/connect.gateway.ts +++ b/services/liquid-auth-api-js/src/connect/connect.gateway.ts @@ -1,10 +1,11 @@ -import type { Handshake, Server, Socket } from 'socket.io'; +import type { Server, Socket } from 'socket.io'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ConnectedSocket, MessageBody, - OnGatewayConnection, OnGatewayDisconnect, + OnGatewayConnection, + OnGatewayDisconnect, OnGatewayInit, SubscribeMessage, WebSocketGateway, @@ -18,7 +19,9 @@ import { RedisIoAdapter } from '../adapters/redis-io.adapter.js'; origin: '*', }, }) -export class ConnectGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { +export class ConnectGateway + implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect +{ private timers = new Map(); private ioAdapter: RedisIoAdapter; private readonly logger = new Logger(ConnectGateway.name); @@ -54,12 +57,20 @@ export class ConnectGateway implements OnGatewayInit, OnGatewayConnection, OnGat socket.conn.close(); // you can also use socket.disconnect(), but in that case the client // will not try to reconnect + } else { + if ( + typeof session.wallet === 'string' && + socket.rooms.has(session.wallet) === false + ) { + this.logger.debug(`(*) Client Joining Room ${session.wallet}`); + socket.join(session.wallet); + } } }); }, 200); - if(this.timers.has(request.sessionID)) { - clearInterval(this.timers.get(request.sessionID)); + if (this.timers.has(request.sessionID)) { + clearInterval(this.timers.get(request.sessionID)); } this.timers.set(request.sessionID, timer); @@ -77,9 +88,11 @@ export class ConnectGateway implements OnGatewayInit, OnGatewayConnection, OnGat handleDisconnect(socket: Socket) { const request = socket.request as Record; - this.logger.debug(`(*) Client Disconnected with Session: ${request.sessionID}`); - if(this.timers.has(request.sessionID)) { - clearInterval(this.timers.get(request.sessionID)); + this.logger.debug( + `(*) Client Disconnected with Session: ${request.sessionID}`, + ); + if (this.timers.has(request.sessionID)) { + clearInterval(this.timers.get(request.sessionID)); } } /** @@ -103,12 +116,12 @@ export class ConnectGateway implements OnGatewayInit, OnGatewayConnection, OnGat const session = await this.authService.findSession(request.sessionID); console.log('Session', session); if (session) { - console.log('Listening to auth messages') + console.log('Listening to auth messages'); await this.ioAdapter.subClient.subscribe('auth'); // Handle messages const obs$: Observable = new Observable((observer) => { - const handleAuthMessage = async (channel, eventMessage)=> { + const handleAuthMessage = async (channel, eventMessage) => { console.log('Link->Message', channel, eventMessage); const { data } = JSON.parse(eventMessage); console.log(body.requestId, data.requestId, data, body); @@ -123,12 +136,9 @@ export class ConnectGateway implements OnGatewayInit, OnGatewayConnection, OnGat this.ioAdapter.subClient.off('message', handleAuthMessage); observer.complete(); } - } + }; - this.ioAdapter.subClient.on( - 'message', - handleAuthMessage, - ); + this.ioAdapter.subClient.on('message', handleAuthMessage); }); return obs$.pipe( map((obs$) => ({ diff --git a/sites/dapp-ui/src/App.tsx b/sites/dapp-ui/src/App.tsx index a40c4ae..b3a20dd 100644 --- a/sites/dapp-ui/src/App.tsx +++ b/sites/dapp-ui/src/App.tsx @@ -9,8 +9,7 @@ import { } from './Contexts'; import Layout from './Layout'; -import { GetStartedCard } from './pages/home/GetStarted'; -import { RegisteredCard } from './pages/dashboard/Registered'; +import { HomePage } from './pages/home.tsx'; import { createTheme, CssBaseline } from '@mui/material'; import { DEFAULT_THEME } from './theme.tsx'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; @@ -18,10 +17,17 @@ import { ThemeProvider } from '@emotion/react'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { createHashRouter, RouterProvider } from 'react-router-dom'; -import { WaitForPeersCard } from './pages/peering/WaitForPeers.tsx'; +import { PeeringPage } from './pages/peering.tsx'; import ConnectedPage from './pages/connected.tsx'; +import { Algodv2 } from 'algosdk'; +import { AlgodContext } from './hooks/useAlgod.ts'; const queryClient = new QueryClient(); +const algod = new Algodv2( + process.env.VITE_ALGOD_TOKEN || '', + process.env.VITE_ALGOD_SERVER || 'https://testnet-api.algonode.cloud', + process.env.VITE_ALGOD_PORT || 443, +); const DEFAULT_CONFIG: RTCConfiguration = { iceServers: [ { @@ -40,7 +46,7 @@ const router = createHashRouter([ path: '/', element: ( - + ), }, @@ -48,7 +54,7 @@ const router = createHashRouter([ path: '/peering', element: ( - + ), }, @@ -60,14 +66,6 @@ const router = createHashRouter([ ), }, - { - path: '/registered', - element: ( - - - - ), - }, ]); export default function ProviderApp() { const [open, setOpen] = useState(false); @@ -97,32 +95,40 @@ export default function ProviderApp() { () => createTheme({ ...DEFAULT_THEME, - palette: { - ...DEFAULT_THEME.palette, - mode, - }, + palette: + mode === 'dark' + ? { + primary: { main: '#9966ff' }, + mode: 'dark', + } + : { ...DEFAULT_THEME.palette }, }), [mode], ); + console.log(theme, DEFAULT_THEME.palette); return ( - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + ); } diff --git a/sites/dapp-ui/src/components/Chat.tsx b/sites/dapp-ui/src/components/Chat.tsx deleted file mode 100644 index fecc1b7..0000000 --- a/sites/dapp-ui/src/components/Chat.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import Avatar from '@mui/material/Avatar'; -import Divider from '@mui/material/Divider'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemAvatar from '@mui/material/ListItemAvatar'; -import ListItemText from '@mui/material/ListItemText'; -import { Message, useMessageStore } from '../store.ts'; -import TextField from '@mui/material/TextField'; -import { useContext, useState } from 'react'; -import Button from '@mui/material/Button'; -import { DataChannelContext } from '../hooks/useDataChannel.ts'; - -const MESSAGE_TYPES = { - local: 'Local', - remote: 'Remote', -}; -const MESSAGE_AVATARS = { - local: '/maskable-icon.png', - remote: '/logo-inverted.png', -}; - -function ChatMessage(message: Message) { - return ( - - - - - - - ); -} - -export function ChatList() { - const { dataChannel } = useContext(DataChannelContext); - - const messages = useMessageStore((state) => state.messages); - const addMessage = useMessageStore((state) => state.addMessage); - const [message, setMessage] = useState(''); - - const isReady = dataChannel && dataChannel.readyState === 'open'; - return ( - <> - - {messages.map((message, i) => { - return ( -
- - -
- ); - })} -
-
- setMessage(e.target.value)} - > - -
- {/**/} - - ); -} diff --git a/sites/dapp-ui/src/pages/home/ConnectModal.tsx b/sites/dapp-ui/src/components/ConnectModal.tsx similarity index 97% rename from sites/dapp-ui/src/pages/home/ConnectModal.tsx rename to sites/dapp-ui/src/components/ConnectModal.tsx index 8192f5c..3be09b3 100644 --- a/sites/dapp-ui/src/pages/home/ConnectModal.tsx +++ b/sites/dapp-ui/src/components/ConnectModal.tsx @@ -7,9 +7,9 @@ import { Message } from '@liquid/auth-client/connect'; import QRCodeStyling, { Options } from 'qr-code-styling'; import { useEffect, useState } from 'react'; import { Fade } from '@mui/material'; -import { useSocket } from '../../hooks/useSocket'; +import { useSocket } from '../hooks/useSocket'; import nacl from 'tweetnacl'; -import { useCredentialStore, Credential } from '../../store'; +import { useCredentialStore, Credential } from '../store'; import { useNavigate } from 'react-router-dom'; const style = { position: 'absolute' as const, diff --git a/sites/dapp-ui/src/components/user/Credential.tsx b/sites/dapp-ui/src/components/user/Credential.tsx deleted file mode 100644 index 96559e4..0000000 --- a/sites/dapp-ui/src/components/user/Credential.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export function Credential() { - return ( -
-

Credential

-
- ); -} diff --git a/sites/dapp-ui/src/components/user/SessionMenu.tsx b/sites/dapp-ui/src/components/user/SessionMenu.tsx index 0c0c06f..09e5ec9 100644 --- a/sites/dapp-ui/src/components/user/SessionMenu.tsx +++ b/sites/dapp-ui/src/components/user/SessionMenu.tsx @@ -1,8 +1,8 @@ import { Avatar, Badge, CircularProgress, Menu } from '@mui/material'; import IconButton from '@mui/material/IconButton'; import React, { useState } from 'react'; -import { useSocket } from '../../hooks/useSocket.ts'; -import { useUserState } from './useUserState.ts'; +import { useSocket } from '@/hooks/useSocket.ts'; +import { useUserState } from '@/hooks/useUserState.ts'; import { StatusCard } from './StatusCard.tsx'; export function SessionMenu() { diff --git a/sites/dapp-ui/src/hooks/index.ts b/sites/dapp-ui/src/hooks/index.ts new file mode 100644 index 0000000..fbbe4b9 --- /dev/null +++ b/sites/dapp-ui/src/hooks/index.ts @@ -0,0 +1,7 @@ +export * from './useAccountInfo'; +export * from './useAddress'; +export * from './useAlgod'; +export * from './useDataChannel'; +export * from './usePeerConnection'; +export * from './useSocket'; +export * from './useUserState'; diff --git a/sites/dapp-ui/src/hooks/useAccountInfo.ts b/sites/dapp-ui/src/hooks/useAccountInfo.ts new file mode 100644 index 0000000..43242d5 --- /dev/null +++ b/sites/dapp-ui/src/hooks/useAccountInfo.ts @@ -0,0 +1,18 @@ +import { useQuery } from '@tanstack/react-query'; +import { useAlgod } from './useAlgod.ts'; + +export function useAccountInfo( + address: string | null, + refetchInterval?: number, +) { + const algod = useAlgod(); + return useQuery({ + refetchInterval, + queryKey: ['accountInfo', address], + queryFn: async () => { + if (!algod || !address) return; + return await algod.accountInformation(address).do(); + }, + enabled: !!algod && !!address, + }); +} diff --git a/sites/dapp-ui/src/hooks/useAlgod.ts b/sites/dapp-ui/src/hooks/useAlgod.ts new file mode 100644 index 0000000..ea66f1a --- /dev/null +++ b/sites/dapp-ui/src/hooks/useAlgod.ts @@ -0,0 +1,16 @@ +import { createContext, useContext } from 'react'; +import { Algodv2 } from 'algosdk'; + +type AlgodState = { algod: Algodv2 | null }; +export const AlgodContext = createContext({ + algod: null, +} as AlgodState); + +export function useAlgod() { + const { algod } = useContext(AlgodContext); + if (!algod) + throw new Error( + 'Algod not found, make sure it is provided in the context.', + ); + return algod; +} diff --git a/sites/dapp-ui/src/hooks/usePeerConnection.ts b/sites/dapp-ui/src/hooks/usePeerConnection.ts index f0d6590..fb4b3a8 100644 --- a/sites/dapp-ui/src/hooks/usePeerConnection.ts +++ b/sites/dapp-ui/src/hooks/usePeerConnection.ts @@ -37,14 +37,16 @@ export function usePeerConnection( onIceCandidate: (event: RTCPeerConnectionIceEvent) => void, ) { const { peerConnection } = useContext(PeerConnectionContext); - window.peerConnection = peerConnection; + useEffect(() => { if (!peerConnection) return; function handleICEGatheringStateChange() { console.log(`ICE gathering state: ${peerConnection?.iceGatheringState}`); } function handleConnectionStateChange() { - console.log(`Connection state change: ${peerConnection.connectionState}`); + console.log( + `Connection state change: ${peerConnection?.connectionState}`, + ); } function handleSignalingStateChange() { @@ -57,9 +59,6 @@ export function usePeerConnection( ); } - function handleICECandidateError(event) { - console.error('ICE Candidate Error', event); - } peerConnection.addEventListener( 'icegatheringstatechange', handleICEGatheringStateChange, @@ -77,10 +76,7 @@ export function usePeerConnection( handleICEConnectionStateChange, ); peerConnection.addEventListener('icecandidate', onIceCandidate); - peerConnection.addEventListener( - 'icecandidateerror', - handleICECandidateError, - ); + return () => { peerConnection.removeEventListener( 'icegatheringstatechange', diff --git a/sites/dapp-ui/src/components/user/useUserState.ts b/sites/dapp-ui/src/hooks/useUserState.ts similarity index 100% rename from sites/dapp-ui/src/components/user/useUserState.ts rename to sites/dapp-ui/src/hooks/useUserState.ts diff --git a/sites/dapp-ui/src/pages/connected.tsx b/sites/dapp-ui/src/pages/connected.tsx index 0881371..53104f6 100644 --- a/sites/dapp-ui/src/pages/connected.tsx +++ b/sites/dapp-ui/src/pages/connected.tsx @@ -5,64 +5,164 @@ import { import { PeerConnectionContext } from '../hooks/usePeerConnection.ts'; import { useContext, useEffect, useState } from 'react'; import Button from '@mui/material/Button'; -import algosdk from 'algosdk'; +import { + Transaction, + encodeUnsignedTransaction, + waitForConfirmation, + makePaymentTxnWithSuggestedParamsFromObject, +} from 'algosdk'; import { toBase64URL, fromBase64Url } from '@liquid/core/encoding'; - -const algodClient = new algosdk.Algodv2( - '', - 'https://testnet-api.algonode.cloud', - 443, -); +import { useAlgod } from '../hooks/useAlgod.ts'; +import { useAccountInfo } from '../hooks/useAccountInfo.ts'; +import FormControl from '@mui/material/FormControl'; +import { Box, CircularProgress, Input, Slider } from '@mui/material'; +import Typography from '@mui/material/Typography'; +import { useMessageStore } from '../store.ts'; export default function ConnectedPage() { + const algod = useAlgod(); const walletStr = window.localStorage.getItem('wallet'); const wallet = walletStr ? JSON.parse(walletStr) : null; - const [txn, setTxn] = useState(null); + const [txn, setTxn] = useState(null); const { peerConnection } = useContext(PeerConnectionContext); const datachannel = useDataChannel('remote', peerConnection); + const accountInfo = useAccountInfo(wallet, 3000); + const [from, setFrom] = useState(wallet); + const [to, setTo] = useState(wallet); + const [amount, setAmount] = useState(0); + const [isWaitingForSignature, setIsWaitingForSignature] = useState(false); + const [isWaitingForConfirmation, setIsWaitingForConfirmation] = + useState(false); + const addMessage = useMessageStore((state) => state.addMessage); + const messages = useMessageStore((state) => state.messages); // Receive response useDataChannelMessages((event) => { + addMessage({ + type: 'remote', + data: JSON.parse(event.data), + timestamp: Date.now(), + }); if (!txn) return; async function handleMessage() { if (!txn) return; - console.log(event); - const sig = fromBase64Url(event.data); + const message = JSON.parse(event.data); + if (message.type !== 'transaction-signature') return; + + if (txn.txID() !== message.txId) throw new Error('Invalid txId'); + + const sig = fromBase64Url(message.sig); const signedTxn = txn.attachSignature(wallet, sig); - const { txId } = await algodClient.sendRawTransaction(signedTxn).do(); - const result = await algosdk.waitForConfirmation(algodClient, txId, 4); - console.log(result); + setIsWaitingForSignature(false); + setIsWaitingForConfirmation(true); + const { txId } = await algod.sendRawTransaction(signedTxn).do(); + await waitForConfirmation(algod, txId, 4); + setIsWaitingForConfirmation(false); } handleMessage(); }); // Send Transaction useEffect(() => { - if (!txn || !datachannel) return; - datachannel?.send(toBase64URL(txn.bytesToSign())); - }, [txn, datachannel]); + if ( + !txn || + !datachannel || + isWaitingForSignature || + isWaitingForConfirmation + ) + return; + const txnMessage = { + type: 'transaction', + txn: toBase64URL(encodeUnsignedTransaction(txn)), + }; + addMessage({ type: 'local', data: txnMessage, timestamp: Date.now() }); + datachannel?.send(JSON.stringify(txnMessage)); + setIsWaitingForSignature(true); + }, [txn, datachannel, isWaitingForSignature]); + + if (accountInfo.data && accountInfo.data.amount === 0) { + return ( + +

Account has no funds

+

{accountInfo.data.address}

+
{JSON.stringify(accountInfo.data, null, 2)}
+
+ ); + } + if (isWaitingForSignature || isWaitingForConfirmation) { + return ( + +

+ Waiting for {isWaitingForConfirmation ? 'Confirmation' : 'Signature'} +

+ +
+ ); + } return ( -
- Connected + + + Send Payment Transaction + + From + setFrom(e.target.value)} + /> + + + To + setTo(e.target.value)} + /> + + + + Amount (in microalgos) + + setAmount(value as number)} + /> + + + -
+ + Messages + + {messages.map((message, i) => ( + +
{JSON.stringify(message, null, 2)}
+
+ ))} + ); } diff --git a/sites/dapp-ui/src/pages/dashboard/Registered.tsx b/sites/dapp-ui/src/pages/dashboard/Registered.tsx deleted file mode 100644 index a82d63b..0000000 --- a/sites/dapp-ui/src/pages/dashboard/Registered.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import CardMedia from '@mui/material/CardMedia'; -import CardContent from '@mui/material/CardContent'; -import Typography from '@mui/material/Typography'; -import CardActions from '@mui/material/CardActions'; -import Button from '@mui/material/Button'; -import Card from '@mui/material/Card'; -import { assertion } from '@liquid/auth-client/assertion'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableRow, -} from '@mui/material'; -import { useCredentialStore } from '../../store'; - -export function RegisteredCard() { - const credentials = useCredentialStore((state) => state.addresses); - const handleTestCredentialClick = () => { - const credId = window.localStorage.getItem('credId'); - if (!credId) return; - assertion(credId); - }; - return ( - - - - - Registered (3 of 3) - - - Mobile Device is Connected and Registered - - - - - Address - Actions - - - - {Object.keys(credentials).map((address) => ( - - - {address.slice(0, 5)}...{address.slice(-5)} - - - - - - ))} - -
-
- - - -
- ); -} diff --git a/sites/dapp-ui/src/pages/dashboard/WaitForRegistration.tsx b/sites/dapp-ui/src/pages/dashboard/WaitForRegistration.tsx deleted file mode 100644 index e0f7817..0000000 --- a/sites/dapp-ui/src/pages/dashboard/WaitForRegistration.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import CardMedia from '@mui/material/CardMedia'; -import CardContent from '@mui/material/CardContent'; -import Typography from '@mui/material/Typography'; -import Card from '@mui/material/Card'; -import { CircularProgress } from '@mui/material'; -import { useContext, useEffect } from 'react'; -import { useSocket } from '../../hooks/useSocket'; -import { StateContext } from '../../Contexts'; -import { useCredentialStore } from '../../store'; -import { useAddressQuery } from '../../hooks/useAddress'; - -export function WaitForRegistrationCard() { - const credentials = useCredentialStore((state) => state.addresses); - const save = useCredentialStore((state) => state.update); - const { state: step, setState } = useContext(StateContext); - const walletStr = window.localStorage.getItem('wallet'); - const wallet = walletStr ? JSON.parse(walletStr) : null; - const { socket } = useSocket(); - const address = useAddressQuery(wallet); - - useEffect(() => { - if (step !== 'connected') { - return; - } - socket.emit( - 'wait', - { wallet }, - async ({ - data: { credId, device }, - }: { - data: { credId: string; device: string }; - }) => { - save({ - name: wallet, - credentials: [ - ...credentials[wallet].credentials, - { id: credId, device }, - ], - }); - window.localStorage.setItem('credId', credId); - setState('registered'); - }, - ); - }); - useEffect(() => { - socket.on('call-candidate', (data: any) => { - console.log(data); - }); - }, [socket]); - return ( - - - - - Connected (2 of 3) - - - Waiting for Passkey registration for - address: - - {address.isLoading && } - {address.isFetched && ( - - {' '} - {address.data} - - )} - - - ); -} diff --git a/sites/dapp-ui/src/pages/home/GetStarted.tsx b/sites/dapp-ui/src/pages/home.tsx similarity index 68% rename from sites/dapp-ui/src/pages/home/GetStarted.tsx rename to sites/dapp-ui/src/pages/home.tsx index 2018215..c167739 100644 --- a/sites/dapp-ui/src/pages/home/GetStarted.tsx +++ b/sites/dapp-ui/src/pages/home.tsx @@ -3,9 +3,13 @@ import CardContent from '@mui/material/CardContent'; import Typography from '@mui/material/Typography'; import CardActions from '@mui/material/CardActions'; import Card from '@mui/material/Card'; -import { ConnectModal } from './ConnectModal'; +import { ConnectModal } from '@/components/ConnectModal'; import Button from '@mui/material/Button'; -export function GetStartedCard() { +import { assertion } from '@liquid/auth-client'; +import { useNavigate } from 'react-router-dom'; +export function HomePage() { + const credId = window.localStorage.getItem('credId'); + const navigate = useNavigate(); return ( - Get Started (1 of 3) + Get Started (1 of 2) Start by connecting a valid wallet, this is the first step in a three @@ -35,7 +39,16 @@ export function GetStartedCard() { - + {credId && ( + + )} ); diff --git a/sites/dapp-ui/src/pages/index.ts b/sites/dapp-ui/src/pages/index.ts new file mode 100644 index 0000000..9b532e6 --- /dev/null +++ b/sites/dapp-ui/src/pages/index.ts @@ -0,0 +1,3 @@ +export * from './connected.tsx'; +export * from './home.tsx'; +export * from './peering.tsx'; diff --git a/sites/dapp-ui/src/pages/peering/WaitForPeers.tsx b/sites/dapp-ui/src/pages/peering.tsx similarity index 74% rename from sites/dapp-ui/src/pages/peering/WaitForPeers.tsx rename to sites/dapp-ui/src/pages/peering.tsx index a829285..6e0e9b4 100644 --- a/sites/dapp-ui/src/pages/peering/WaitForPeers.tsx +++ b/sites/dapp-ui/src/pages/peering.tsx @@ -3,20 +3,19 @@ import CardContent from '@mui/material/CardContent'; import Typography from '@mui/material/Typography'; import Card from '@mui/material/Card'; import { CircularProgress } from '@mui/material'; -import { useEffect } from 'react'; -import { useSocket } from '../../hooks/useSocket'; +import { useEffect, useState } from 'react'; +import { useSocket } from '@/hooks/useSocket'; import { useNavigate } from 'react-router-dom'; -import { usePeerConnection } from '../../hooks/usePeerConnection.ts'; -import { - useDataChannel, - useDataChannelMessages, -} from '../../hooks/useDataChannel.ts'; +import { usePeerConnection } from '@/hooks/usePeerConnection'; +import { useDataChannel, useDataChannelMessages } from '@/hooks/useDataChannel'; +import { useMessageStore } from '@/store'; -export function WaitForPeersCard() { +export function PeeringPage() { const navigate = useNavigate(); const walletStr = window.localStorage.getItem('wallet'); + const addMessage = useMessageStore((state) => state.addMessage); const wallet = walletStr ? JSON.parse(walletStr) : null; - + const [credentialId, setCredentialId] = useState(null); const { socket } = useSocket(); // const address = useAddressQuery(wallet); const peerConnection = usePeerConnection((event) => { @@ -31,14 +30,25 @@ export function WaitForPeersCard() { }); const datachannel = useDataChannel('remote', peerConnection); useDataChannelMessages((event) => { - console.log(event); + addMessage({ + type: 'remote', + data: JSON.parse(event.data), + timestamp: Date.now(), + }); + const data = JSON.parse(event.data); + if (data?.type === 'credential') { + window.localStorage.setItem('credId', data.id); + setCredentialId(data.id); + } }); + + // Once we have a valid credential, continue to the connected page useEffect(() => { - if (!datachannel) return; + if (!datachannel || !credentialId) return; // datachannel.send('Hello World') navigate('/connected'); - }, [datachannel]); + }, [datachannel, credentialId, navigate]); useEffect(() => { if (!peerConnection) return; @@ -88,10 +98,10 @@ export function WaitForPeersCard() { /> - Waiting for Peer Connection (2 of 3) + Waiting for Peer Confirmation (2 of 2) - Waiting for Passkey registration for + Waiting for message from peer for address: diff --git a/sites/dapp-ui/src/store.ts b/sites/dapp-ui/src/store.ts index 68b1ef6..3a486a6 100644 --- a/sites/dapp-ui/src/store.ts +++ b/sites/dapp-ui/src/store.ts @@ -60,7 +60,7 @@ export const usePeerStore = create((set) => ({ })); export type Message = { - text: string; + data: any; type: 'local' | 'remote'; timestamp: number; }; diff --git a/sites/dapp-ui/tsconfig.json b/sites/dapp-ui/tsconfig.json index a7fc6fb..439469d 100644 --- a/sites/dapp-ui/tsconfig.json +++ b/sites/dapp-ui/tsconfig.json @@ -18,7 +18,11 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/sites/dapp-ui/vite.config.ts b/sites/dapp-ui/vite.config.ts index 7bef127..f79e467 100644 --- a/sites/dapp-ui/vite.config.ts +++ b/sites/dapp-ui/vite.config.ts @@ -1,111 +1,131 @@ -import {defineConfig, splitVendorChunkPlugin} from 'vite' -import { VitePWA } from 'vite-plugin-pwa' +import { defineConfig, splitVendorChunkPlugin } from 'vite'; +import { VitePWA } from 'vite-plugin-pwa'; import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'; -import react from '@vitejs/plugin-react-swc' -import {rename} from 'node:fs/promises' -import {resolve} from 'node:path' -import {mkdirp} from 'mkdirp'; +import react from '@vitejs/plugin-react-swc'; +import { rename } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { mkdirp } from 'mkdirp'; -const API_DIR = resolve(__dirname, '..', '..', 'services', 'liquid-auth-api-js') -const PUBLIC_DIR = resolve(API_DIR, 'public') -const VIEW_DIR = resolve(API_DIR, 'views') -console.log(API_DIR) +const API_DIR = resolve( + __dirname, + '..', + '..', + 'services', + 'liquid-auth-api-js', +); +const PUBLIC_DIR = resolve(API_DIR, 'public'); +const VIEW_DIR = resolve(API_DIR, 'views'); +console.log(API_DIR); export default defineConfig({ - // base: '/app', - server: { - hmr: { - port: 8000, - host: 'localhost' - } - // proxy: { - // '^/auth/.*': 'http://localhost:3000', - // '^/connect/.*': 'http://localhost:3000', - // '^/attestation/.*': 'http://localhost:3000', - // '^/assertion/.*': 'http://localhost:3000', - // '/socket.io': { - // target: 'ws://localhost:3000', - // ws: true, - // }, - // } + // base: '/app', + server: { + hmr: { + port: 8000, + host: 'localhost', }, - build: { - // rollupOptions: { - // output: { - // manualChunks: { - // // 'algorand': ['tweetnacl', 'algosdk'], - // 'socket.io': ['socket.io-client'], - // 'react': ['react', 'react-dom', '@tanstack/react-query'], - // 'material': ['@mui/material', '@mui/icons-material'] - // } - // } - // }, - outDir: PUBLIC_DIR, + // proxy: { + // '^/auth/.*': 'http://localhost:3000', + // '^/connect/.*': 'http://localhost:3000', + // '^/attestation/.*': 'http://localhost:3000', + // '^/assertion/.*': 'http://localhost:3000', + // '/socket.io': { + // target: 'ws://localhost:3000', + // ws: true, + // }, + // } + }, + build: { + // rollupOptions: { + // output: { + // manualChunks: { + // // 'algorand': ['tweetnacl', 'algosdk'], + // 'socket.io': ['socket.io-client'], + // 'react': ['react', 'react-dom', '@tanstack/react-query'], + // 'material': ['@mui/material', '@mui/icons-material'] + // } + // } + // }, + outDir: PUBLIC_DIR, + }, + resolve: { + alias: { + '@/components': resolve(__dirname, 'src', 'components'), + '@/hooks': resolve(__dirname, 'src', 'hooks'), + '@/pages': resolve(__dirname, 'src', 'pages'), + '@/store': resolve(__dirname, 'src', 'store'), }, - plugins: [ - VitePWA({ - includeAssets: ['logo-background.svg', 'apple-touch-icon.png', 'maskable-icon.png'], - workbox: { - navigateFallback: null, - }, - manifest: { - name: 'Liquid dApp', - short_name: 'Liquid', - description: 'FIDO2/Passkey Authentication', - theme_color: '#121212', - icons: [ - { - src: 'icons/48x48.png', - sizes: '48x48', - type: 'image/png' - }, - { - src: 'icons/72x72.png', - sizes: '72x72', - type: 'image/png' - }, - { - src: 'icons/96x96.png', - sizes: '96x96', - type: 'image/png' - }, - { - src: 'icons/144x144.png', - sizes: '144x144', - type: 'image/png' - }, - { - src: 'icons/192x192.png', - sizes: '192x192', - type: 'image/png' - }, - { - src: 'icons/512x512.png', - sizes: '512x512', - type: 'image/png' - }, - { - src: 'maskable-icon.png', - sizes: '512x512', - type: 'image/png', - purpose: "maskable" - } - ] - } - }), - splitVendorChunkPlugin(), - ViteImageOptimizer(), - react(), - { - name: 'move-index-file', - closeBundle: async () => { - await mkdirp(VIEW_DIR) - try { - await rename(resolve(PUBLIC_DIR, 'index.html'), resolve(VIEW_DIR, 'index.html')) - } catch (e) { - console.log('Skipping') - } - - } - }, - ], -}) + }, + plugins: [ + VitePWA({ + includeAssets: [ + 'logo-background.svg', + 'apple-touch-icon.png', + 'maskable-icon.png', + ], + workbox: { + navigateFallback: null, + }, + manifest: { + name: 'Liquid dApp', + short_name: 'Liquid', + description: 'FIDO2/Passkey Authentication', + theme_color: '#121212', + icons: [ + { + src: 'icons/48x48.png', + sizes: '48x48', + type: 'image/png', + }, + { + src: 'icons/72x72.png', + sizes: '72x72', + type: 'image/png', + }, + { + src: 'icons/96x96.png', + sizes: '96x96', + type: 'image/png', + }, + { + src: 'icons/144x144.png', + sizes: '144x144', + type: 'image/png', + }, + { + src: 'icons/192x192.png', + sizes: '192x192', + type: 'image/png', + }, + { + src: 'icons/512x512.png', + sizes: '512x512', + type: 'image/png', + }, + { + src: 'maskable-icon.png', + sizes: '512x512', + type: 'image/png', + purpose: 'maskable', + }, + ], + }, + }), + splitVendorChunkPlugin(), + ViteImageOptimizer(), + react(), + { + name: 'move-index-file', + closeBundle: async () => { + await mkdirp(VIEW_DIR); + try { + await rename( + resolve(PUBLIC_DIR, 'index.html'), + resolve(VIEW_DIR, 'index.html'), + ); + } catch (e) { + console.log('Skipping'); + } + }, + }, + ], +});