diff --git a/packages/restapi/src/lib/pushNotification/notification.ts b/packages/restapi/src/lib/pushNotification/notification.ts index c1e5a1438..6e7b245c5 100644 --- a/packages/restapi/src/lib/pushNotification/notification.ts +++ b/packages/restapi/src/lib/pushNotification/notification.ts @@ -14,6 +14,7 @@ import { getCAIPWithChainId, validateCAIP, getFallbackETHCAIPAddress, + pCAIP10ToWallet, } from '../helpers'; import { PushNotificationBaseClass } from './pushNotificationBase'; @@ -50,11 +51,16 @@ export class Notification extends PushNotificationBaseClass { raw = false, } = options || {}; try { - const account = options?.account - ? options.account - : this.account - ? getFallbackETHCAIPAddress(this.env!, this.account!) - : null; + let account: string | null; + if (options?.account) { + if (this.isValidPCaip(options.account)) { + account = pCAIP10ToWallet(options.account); + } else { + account = options.account; + } + } else if(this.account){ + account = getFallbackETHCAIPAddress(this.env!, this.account!) + } // guest mode and valid address check this.checkUserAddressExists(account!); const nonCaipAccount = this.getAddressFromCaip(account!); @@ -99,11 +105,16 @@ export class Notification extends PushNotificationBaseClass { channel = null, raw, } = options || {}; - const account = options?.account - ? options.account - : this.account - ? getFallbackETHCAIPAddress(this.env!, this.account!) - : null; + let account: string | null; + if (options?.account) { + if (this.isValidPCaip(options.account)) { + account = pCAIP10ToWallet(options.account); + } else { + account = options.account; + } + } else if(this.account){ + account = getFallbackETHCAIPAddress(this.env!, this.account!) + } this.checkUserAddressExists(account!); return await PUSH_USER.getSubscriptions({ user: account!, diff --git a/packages/restapi/src/lib/pushNotification/pushNotificationBase.ts b/packages/restapi/src/lib/pushNotification/pushNotificationBase.ts index 7fe081696..1e8f1bbde 100644 --- a/packages/restapi/src/lib/pushNotification/pushNotificationBase.ts +++ b/packages/restapi/src/lib/pushNotification/pushNotificationBase.ts @@ -33,6 +33,8 @@ import { import { axiosGet, axiosPost } from '../utils/axiosUtil'; import { PushAPI } from '../pushapi/PushAPI'; import { channel } from 'diagnostics_channel'; +import * as viem from 'viem'; + // ERROR CONSTANTS const ERROR_ACCOUNT_NEEDED = 'Account is required'; @@ -843,7 +845,7 @@ export class PushNotificationBaseClass { throw new Error('Signer is not provided'); } const pushSigner = new Signer(this.signer); - let addAliasRes + let addAliasRes; if (!pushSigner.isViemSigner(this.signer)) { if (!this.signer.provider) { throw new Error('ethers provider is not provided'); @@ -875,12 +877,13 @@ export class PushNotificationBaseClass { throw new Error('Signer is not provided'); } const pushSigner = new Signer(this.signer); - let verifyAliasRes + let verifyAliasRes; if (!pushSigner.isViemSigner(this.signer)) { if (!this.signer.provider) { throw new Error('ethers provider is not provided'); } - const addAliasTrxPromise = contract!['verifyChannelAlias'](channelAddress); + const addAliasTrxPromise = + contract!['verifyChannelAlias'](channelAddress); const addAliasTrx = await addAliasTrxPromise; await this.signer?.provider?.waitForTransaction(addAliasTrx.hash); verifyAliasRes = addAliasTrx.hash; @@ -926,4 +929,13 @@ export class PushNotificationBaseClass { protected getAddressFromCaip(caipAddress: string): string { return caipAddress?.split(':')[caipAddress?.split(':').length - 1]; } + + protected isValidPCaip(address: string): boolean { + const addressComponents = address.split(':'); + return ( + addressComponents.length == 2 && + addressComponents[0] == 'eip155' && + viem.isAddress(addressComponents[1]) + ); + } } diff --git a/packages/restapi/tests/lib/notification/notification.test.ts b/packages/restapi/tests/lib/notification/notification.test.ts index 9e98fb390..36a6f7b0b 100644 --- a/packages/restapi/tests/lib/notification/notification.test.ts +++ b/packages/restapi/tests/lib/notification/notification.test.ts @@ -89,6 +89,15 @@ describe('PushAPI.notification functionality', () => { expect(response).not.null; }); + + it('Should return feeds when signer with provider is used', async () => { + const response = await userKate.notification.list('SPAM', { + account: 'eip155:0xD8634C39BBFd4033c0d3289C4515275102423681', + }); + // console.log(response) + expect(response).not.null; + }); + it('Should return feeds when viem is used', async () => { const response = await userViem.notification.list('SPAM'); // console.log(response); @@ -267,11 +276,18 @@ describe('PushAPI.notification functionality', () => { expect(response.length).not.equal(0); }); + it('Signer with account: Should return response', async () => { + const response = await userKate.notification.subscriptions({ + account: 'eip155:0xD8634C39BBFd4033c0d3289C4515275102423681', + }); + expect(response).not.null; + expect(response.length).not.equal(0); + }); + it('Signer with account: Should return response', async () => { const response = await userKate.notification.subscriptions({ account: '0xD8634C39BBFd4033c0d3289C4515275102423681', }); - // console.log(JSON.stringify(response)); expect(response).not.null; expect(response.length).not.equal(0); }); @@ -282,7 +298,6 @@ describe('PushAPI.notification functionality', () => { raw: false, channel: '0xD8634C39BBFd4033c0d3289C4515275102423681', }); - console.log(JSON.stringify(response)); expect(response).not.null; }); }); diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx index 49a4afe39..ab9cbb9f2 100644 --- a/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx @@ -1,20 +1,34 @@ -import { ReactElement, ReactNode, useContext, useEffect, useState } from 'react'; +import { ReactNode, useContext, useEffect, useRef, useState } from 'react'; import moment from 'moment'; +import { MdDownload } from 'react-icons/md'; import { TwitterTweetEmbed } from 'react-twitter-embed'; import styled from 'styled-components'; -import { MdDownload } from 'react-icons/md'; import { ChatDataContext } from '../../../context'; import { useChatData } from '../../../hooks'; -import { Image, Section, Span } from '../../reusables'; +import { Div, Image, Section, Span } from '../../reusables'; import { checkTwitterUrl } from '../helpers/twitter'; import { ThemeContext } from '../theme/ThemeProvider'; -import { FILE_ICON } from '../../../config'; -import { formatFileSize, getPfp, pCAIP10ToWallet, shortenText } from '../../../helpers'; -import { FileMessageContent } from '../../../types'; +import { useConnectWallet, useSetChain } from '@web3-onboard/react'; +import { ethers } from 'ethers'; +import { BsLightning } from 'react-icons/bs'; +import { FaBell, FaLink, FaRegThumbsUp } from 'react-icons/fa'; +import { MdError, MdOpenInNew } from 'react-icons/md'; +import { FILE_ICON, allowedNetworks, device } from '../../../config'; +import { formatFileSize, getPfp, pCAIP10ToWallet, shortenText, sign, toSerialisedHexString } from '../../../helpers'; +import { createBlockie } from '../../../helpers/blockies'; +import { FileMessageContent, FrameDetails, IFrame, IFrameButton } from '../../../types'; +import { extractWebLink, getFormattedMetadata, hasWebLink } from '../../../utilities'; import { IMessagePayload, TwitterFeedReturnType } from '../exportedTypes'; +import { Button, TextInput } from '../reusables'; + +import { FileCard } from './cards/file/FileCard'; +import { GIFCard } from './cards/gif/GIFCard'; +import { ImageCard } from './cards/image/ImageCard'; +import { MessageCard } from './cards/message/MessageCard'; +import { TwitterCard } from './cards/twitter/TwitterCard'; const SenderMessageAddress = ({ chat }: { chat: IMessagePayload }) => { const { user } = useContext(ChatDataContext); @@ -36,17 +50,53 @@ const SenderMessageAddress = ({ chat }: { chat: IMessagePayload }) => { const SenderMessageProfilePicture = ({ chat }: { chat: IMessagePayload }) => { const { user } = useContext(ChatDataContext); - const [pfp, setPfp] = useState(''); - const getUserPfp = async () => { - const pfp = await getPfp({ - user: user, - recipient: chat.fromCAIP10?.split(':')[1], - }); - if (pfp) { - setPfp(pfp); + const [chatPic, setChatPic] = useState({ + pfpsrc: null as string | null, + blockie: null as string | null, + }); + + // For blockie if icon is missing + const blockieContainerRef = useRef(null); + + useEffect(() => { + if (blockieContainerRef.current && chatPic.blockie && chatPic.pfpsrc === null) { + const blockie = createBlockie(chatPic.blockie || '', { size: 8, scale: 5 }); + blockieContainerRef.current.innerHTML = ''; // Clear the container to avoid duplicating the canvas + blockieContainerRef.current.appendChild(blockie); } - }; + }, [chatPic.blockie]); + useEffect(() => { + const getUserPfp = async () => { + try { + const pfp = await getPfp({ + user: user, + recipient: chat.fromCAIP10?.split(':')[1], + }); + + if (pfp) { + setChatPic({ + pfpsrc: pfp, + blockie: null, + }); + } else { + setChatPic({ + pfpsrc: null, + blockie: chat.fromCAIP10?.split(':')[1], + }); + } + } catch (error) { + console.error('UIWeb::components::chat::ChatViewBubble::SenderMessageProfilePicture::getUserPfp error', error); + + // fallback to blockie + setChatPic({ + pfpsrc: null, + blockie: chat.fromCAIP10?.split(':')[1], + }); + } + }; + + // resolve user pfp getUserPfp(); }, [chat.fromCAIP10]); @@ -55,17 +105,30 @@ const SenderMessageProfilePicture = ({ chat }: { chat: IMessagePayload }) => { justifyContent="start" alignItems="start" > - {chat.fromCAIP10 !== user?.account && ( -
- {pfp && ( + {chat.fromCAIP10?.split(':')[1] !== user?.account && ( +
+ {chatPic.pfpsrc && ( profile picture )} + + {!chatPic.pfpsrc && chatPic.blockie && ( +
+ )}
)}
@@ -76,23 +139,20 @@ const MessageWrapper = ({ chat, children, isGroup, - maxWidth, }: { chat: IMessagePayload; children: ReactNode; isGroup: boolean; - maxWidth?: string; }) => { const { user } = useChatData(); const theme = useContext(ThemeContext); return ( -
{isGroup && chat?.fromCAIP10 !== user?.account && }
} {children}
-
- ); -}; - -const MessageCard = ({ chat, position, isGroup }: { chat: IMessagePayload; position: number; isGroup: boolean }) => { - const theme = useContext(ThemeContext); - const time = moment(chat.timestamp).format('hh:mm a'); - return ( - - - {' '} -
- {chat?.messageContent?.split('\n').map((str) => ( - - {str} - - ))} -
- - {time} - -
-
- ); -}; - -const FileCard = ({ chat, isGroup }: { chat: IMessagePayload; position: number; isGroup: boolean }) => { - const fileContent: FileMessageContent = JSON.parse(chat?.messageContent); - const name = fileContent.name; - - const content = fileContent.content as string; - const size = fileContent.size; - - return ( - -
- extension icon -
- - {shortenText(name, 11)} - - - {formatFileSize(size)} - -
- - - -
-
- ); -}; - -const ImageCard = ({ chat, position, isGroup }: { chat: IMessagePayload; position: number; isGroup: boolean }) => { - return ( - -
- -
-
- ); -}; - -const GIFCard = ({ chat, position, isGroup }: { chat: IMessagePayload; position: number; isGroup: boolean }) => { - return ( - -
- -
-
- ); -}; - -const TwitterCard = ({ - chat, - tweetId, - isGroup, - position, -}: { - chat: IMessagePayload; - tweetId: string; - isGroup: boolean; - position: number; -}) => { - return ( - -
- -
-
+ ); }; export const ChatViewBubble = ({ decryptedMessagePayload, - isGroup = false, + isGroup, }: { decryptedMessagePayload: IMessagePayload; - isGroup?: boolean; + isGroup: boolean; }) => { const { user } = useChatData(); const position = @@ -333,55 +185,79 @@ export const ChatViewBubble = ({ decryptedMessagePayload.messageType = 'TwitterFeedLink'; } - if (decryptedMessagePayload.messageType === 'GIF') { - return ( - - ); - } - if (decryptedMessagePayload.messageType === 'Image') { - return ( - - ); - } - if (decryptedMessagePayload.messageType === 'File') { - return ( - - ); - } - if (decryptedMessagePayload.messageType === 'TwitterFeedLink') { - return ( - - ); - } return ( - + isGroup={isGroup} + > + {/* Message Card */} + {decryptedMessagePayload.messageType === 'Text' && ( + + )} + + {/* Image Card */} + {decryptedMessagePayload.messageType === 'Image' && ( + + )} + + {/* File Card */} + {decryptedMessagePayload.messageType === 'File' && ( + + )} + + {/* Gif Card */} + {decryptedMessagePayload.messageType === 'GIF' && ( + + )} + + {/* Twitter Card */} + {decryptedMessagePayload.messageType === 'TwitterFeedLink' && ( + + )} + + {/* Default Message Card */} + {decryptedMessagePayload.messageType !== 'Text' && + decryptedMessagePayload.messageType !== 'Image' && + decryptedMessagePayload.messageType !== 'File' && + decryptedMessagePayload.messageType !== 'GIF' && + decryptedMessagePayload.messageType !== 'TwitterFeedLink' && ( + + )} + ); }; -const FileDownloadIconAnchor = styled.a` - font-size: 20px; -`; -const MessageSection = styled(Section)<{ border: string }>` - border: ${(props) => props.border}; +const MessageSection = styled(Section)` + max-width: 70%; + + @media ${device.tablet} { + max-width: 90%; + } `; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/file/FileCard.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/file/FileCard.tsx new file mode 100644 index 000000000..4a452cb80 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/file/FileCard.tsx @@ -0,0 +1,88 @@ +// React + Web3 Essentials + +// External Packages +import styled from 'styled-components'; + +// Internal Compoonents +import { + formatFileSize, + getPfp, + pCAIP10ToWallet, + shortenText, + sign, + toSerialisedHexString, +} from '../../../../../helpers'; +import { Image, Section, Span } from '../../../../reusables'; + +// Internal Configs +import { FILE_ICON, allowedNetworks } from '../../../../../config'; + +// Assets +import { MdDownload } from 'react-icons/md'; + +// Interfaces & Types +import { FileMessageContent, FrameDetails, IFrame, IFrameButton } from '../../../../../types'; +import { IMessagePayload } from '../../../exportedTypes'; + +// Constants + +// Exported Interfaces & Types + +// Exported Functions +export const FileCard = ({ chat, isGroup }: { chat: IMessagePayload; position: number; isGroup: boolean }) => { + const fileContent: FileMessageContent = JSON.parse(chat?.messageContent); + const name = fileContent.name; + + const content = fileContent.content as string; + const size = fileContent.size; + + return ( +
+ extension icon +
+ + {shortenText(name, 11)} + + + {formatFileSize(size)} + +
+ + + +
+ ); +}; + +const FileDownloadIconAnchor = styled.a` + font-size: 20px; +`; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/gif/GIFCard.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/gif/GIFCard.tsx new file mode 100644 index 000000000..ba671b577 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/gif/GIFCard.tsx @@ -0,0 +1,36 @@ +// React + Web3 Essentials + +// External Packages + +// Internal Compoonents +import { Image, Section, Span } from '../../../../reusables'; + +// Internal Configs + +// Assets + +// Interfaces & Types +import { IMessagePayload } from '../../../exportedTypes'; + +// Constants + +// Exported Interfaces & Types + +// Exported Functions +export const GIFCard = ({ chat, position, isGroup }: { chat: IMessagePayload; position: number; isGroup: boolean }) => { + return ( +
+ +
+ ); +}; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/image/ImageCard.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/image/ImageCard.tsx new file mode 100644 index 000000000..12068eead --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/image/ImageCard.tsx @@ -0,0 +1,45 @@ +// React + Web3 Essentials + +// External Packages + +// Internal Compoonents +import { Image, Section } from '../../../../reusables'; + +// Internal Configs + +// Assets + +// Interfaces & Types +import { IMessagePayload } from '../../../exportedTypes'; + +// Constants + +// Exported Interfaces & Types + +// Exported Functions + +export const ImageCard = ({ + chat, + position, + isGroup, +}: { + chat: IMessagePayload; + position: number; + isGroup: boolean; +}) => { + return ( +
+ +
+ ); +}; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/FrameRenderer.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/FrameRenderer.tsx new file mode 100644 index 000000000..a73d28ee3 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/FrameRenderer.tsx @@ -0,0 +1,526 @@ +// React + Web3 Essentials +import { useContext, useState } from 'react'; + +// External Packages +import { useConnectWallet, useSetChain } from '@web3-onboard/react'; +import { ethers } from 'ethers'; +import styled from 'styled-components'; + +// Internal Compoonents +import { FILE_ICON, allowedNetworks } from '../../../../../config'; +import { + formatFileSize, + getPfp, + pCAIP10ToWallet, + shortenText, + sign, + toSerialisedHexString, +} from '../../../../../helpers'; +import { useChatData } from '../../../../../hooks'; +import { extractWebLink, getFormattedMetadata, hasWebLink } from '../../../../../utilities'; +import { Anchor, Button, Image, Section, Span } from '../../../../reusables'; +import { TextInput } from '../../../reusables'; +import useToast from '../../../reusables/NewToast'; +import { ThemeContext } from '../../../theme/ThemeProvider'; + +// Internal Configs + +// Assets +import { BsLightning } from 'react-icons/bs'; +import { FaBell, FaLink, FaRegThumbsUp } from 'react-icons/fa'; +import { MdError, MdOpenInNew } from 'react-icons/md'; + +// Interfaces & Types +import { IFrame, IFrameButton } from '../../../../../types'; +import { IChatTheme } from '../../../exportedTypes'; +import { getAddress, toHex } from 'viem'; + +interface FrameInputProps extends React.InputHTMLAttributes { + theme: IChatTheme; +} + +// Constants + +// Exported Interfaces & Types + +// Exported Functions +export const FrameRenderer = ({ + url, + account, + messageId, + frameData, + proxyServer, +}: { + url: string; + account: string; + messageId: string; + frameData: IFrame; + proxyServer: string; +}) => { + const { env, user, pgpPrivateKey } = useChatData(); + + const [{ wallet }] = useConnectWallet(); + const [{ connectedChain }, setChain] = useSetChain(); + + const frameRenderer = useToast(); + const [FrameData, setFrameData] = useState