diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx b/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx index 6f6f4c2039..dbeb6181d2 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx +++ b/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx @@ -5,9 +5,12 @@ import { Chip, CircularProgress, Container, + FormControlLabel, + FormGroup, InputAdornment, Link, Stack, + Switch, TextField, Tooltip, Typography, @@ -20,6 +23,8 @@ import CallReceivedIcon from '@mui/icons-material/CallReceived'; import PersonIcon from '@mui/icons-material/Person'; import PersonOffIcon from '@mui/icons-material/PersonOff'; import ErrorIcon from '@mui/icons-material/Error'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; import { NymLogo } from '@nymproject/react/logo/NymLogo'; import { NymThemeProvider } from '@nymproject/mui-theme'; import { useTheme } from '@mui/material/styles'; @@ -28,9 +33,10 @@ import { DropzoneDialog } from 'react-mui-dropzone'; import UploadFileIcon from '@mui/icons-material/UploadFile'; import ArticleIcon from '@mui/icons-material/Article'; import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; +import { Headers } from '@nymproject/sdk'; import { ThemeToggle } from './ThemeToggle'; import { AppContextProvider, useAppContext } from './context'; -import { MixnetContextProvider, parseBinaryMessageHeaders, useMixnetContext } from './context/mixnet'; +import { BinaryMessageHeaders, MixnetContextProvider, useMixnetContext } from './context/mixnet'; export const AppTheme: React.FC = ({ children }) => { const { mode } = useAppContext(); @@ -45,6 +51,7 @@ interface Log { fileDownloadUrl?: string; filesize?: number; timestamp: Date; + headers?: Headers; } interface UploadState { @@ -52,11 +59,48 @@ interface UploadState { files: File[]; } +const ClientAddress: React.FC<{ label?: string; tooltip?: string; address?: string }> = ({ + label, + address, + tooltip, +}) => { + const copy = useClipboard(); + + if (!address) { + return } />; + } + + const addressShort = `${address.slice(0, 24)}...`; + + return ( + + + {label} {addressShort} + + ) : ( + <>{addressShort} + ) + } + onClick={() => { + if (address) { + copy.copy(address); + } + }} + icon={} + /> + + ); +}; + export const Content: React.FC = () => { const theme = useTheme(); const { isReady, address, connect, events, sendTextMessage, sendBinaryMessage } = useMixnetContext(); - const copy = useClipboard(); + const [revealSenderAddress, setRevealSenderAddress] = React.useState(false); const [sendToSelf, setSendToSelf] = React.useState(false); const [recipient, setRecipient] = React.useState(); const handleRecipientChange = (event: React.ChangeEvent) => { @@ -94,13 +138,8 @@ export const Content: React.FC = () => { React.useEffect(() => { if (isReady) { - // // mixnet v1 - // const validatorApiUrl = 'https://validator.nymtech.net/api'; - // const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; - - // mixnet v2 - const validatorApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081"; - const preferredGatewayIdentityKey = undefined; // '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL'; + const validatorApiUrl = 'https://validator.nymtech.net/api'; + const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; connect({ clientId: 'Example Client', @@ -117,6 +156,7 @@ export const Content: React.FC = () => { kind: 'rx', timestamp: new Date(), message: e.args.payload, + headers: e.args.headers, }); setLogTrigger(Date.now()); }); @@ -142,8 +182,12 @@ export const Content: React.FC = () => { React.useEffect(() => { if (events) { const unsubcribe = events.subscribeToBinaryMessageReceivedEvent((e) => { - // the headers will be JSON (see the mixnet context for how they are created), so parse them - const headers = parseBinaryMessageHeaders(e.args.headers); + if (!e.args.headers) { + console.error('Expected headers, got undefined 😢', e.args); + return; + } + + const headers = e.args.headers as BinaryMessageHeaders; const blob = new Blob([new Uint8Array(e.args.payload)], { type: headers.mimeType }); log.current.push({ @@ -152,6 +196,7 @@ export const Content: React.FC = () => { filename: headers.filename, fileDownloadUrl: URL.createObjectURL(blob), filesize: e.args.payload.length, + headers: e.args.headers, }); setLogTrigger(Date.now()); }); @@ -184,7 +229,8 @@ export const Content: React.FC = () => { message, }); setLogTrigger(Date.now()); - await sendTextMessage({ payload: message, recipient }); + const senderAddress = revealSenderAddress ? address : undefined; + await sendTextMessage({ payload: message, recipient, headers: { senderAddress } }); await Promise.all( files.map(async (f) => { @@ -200,7 +246,7 @@ export const Content: React.FC = () => { return await sendBinaryMessage({ payload: new Uint8Array(buffer), recipient, - headers: { filename: f.name, mimeType: f.type }, + headers: { filename: f.name, mimeType: f.type, senderAddress }, }); } catch (e) { addErrorLog('Failed to send file', f.name); @@ -249,20 +295,7 @@ export const Content: React.FC = () => { ) : ( <> } label="Connected" variant="outlined" /> - {address && ( - - { - if (address) { - copy.copy(address); - } - }} - icon={} - /> - - )} + )} @@ -347,9 +380,34 @@ export const Content: React.FC = () => { /> - + + + + setRevealSenderAddress((prevState) => !prevState)} + /> + } + label={ + revealSenderAddress ? ( + + + Reveal your address to the recipient + + ) : ( + + + Hide your address from the recipient + + ) + } + /> + + )} @@ -398,6 +456,16 @@ export const Content: React.FC = () => { )} )} + {item.kind === 'rx' && + (item.headers?.senderAddress ? ( + + ) : ( + + ))} ))} diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/context/mixnet.tsx b/sdk/typescript/examples/react-webpack-with-theme-example/src/context/mixnet.tsx index 6704796b48..b234e8537d 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/src/context/mixnet.tsx +++ b/sdk/typescript/examples/react-webpack-with-theme-example/src/context/mixnet.tsx @@ -1,14 +1,11 @@ import * as React from 'react'; -import { createNymMixnetClient, IWebWorkerEvents, NymClientConfig, NymMixnetClient } from '@nymproject/sdk'; +import { createNymMixnetClient, IWebWorkerEvents, NymClientConfig, NymMixnetClient, Headers } from '@nymproject/sdk'; -export interface BinaryMessageHeaders { +export interface BinaryMessageHeaders extends Headers { filename: string; mimeType: string; } -export const parseBinaryMessageHeaders = (headers: string): BinaryMessageHeaders => - JSON.parse(headers) as BinaryMessageHeaders; - interface State { // data isReady: boolean; @@ -17,7 +14,7 @@ interface State { // methods connect: (config: NymClientConfig) => Promise; - sendTextMessage: (args: { payload: string; recipient: string }) => Promise; + sendTextMessage: (args: { payload: string; recipient: string; headers?: Headers }) => Promise; sendBinaryMessage: (args: { payload: Uint8Array; recipient: string; headers: BinaryMessageHeaders }) => Promise; } @@ -62,7 +59,7 @@ export const MixnetContextProvider: React.FC = ({ children }) => { await nym.current.client.start(config); }; - const sendTextMessage = async (args: { payload: string; recipient: string }) => { + const sendTextMessage = async (args: { payload: string; recipient: string; headers?: Headers }) => { if (!nym.current?.client) { console.error('Nym client has not initialised. Please wrap in useEffect on `isReady` prop of this context.'); return; @@ -76,8 +73,7 @@ export const MixnetContextProvider: React.FC = ({ children }) => { return; } // convert headers to JSON - const sendArgs = { ...args, headers: JSON.stringify(args.headers) }; - await nym.current.client.sendBinaryMessage(sendArgs); + await nym.current.client.sendBinaryMessage(args); }; const value = React.useMemo(