From b95601b560d19c15b7dad2b145edef0544870484 Mon Sep 17 00:00:00 2001 From: Bhupesh-MS Date: Mon, 5 Aug 2024 17:33:05 +0530 Subject: [PATCH 01/49] configure profile new UI screen --- .../ConfigureProfile/ConfigureProfile.css | 83 +++++++++++++- .../ConfigureProfile/MobileView.tsx | 105 ++++++++++++++---- .../ConfigureProfile/NormalView.tsx | 98 ++++++++++++---- .../Preferences/DM3Profile/DM3Profile.tsx | 4 +- .../Preferences/Heading/Heading.tsx | 3 +- .../src/components/Preferences/MobileView.tsx | 11 +- .../src/components/Preferences/NormalView.tsx | 11 +- .../src/context/ModalContext.tsx | 34 ++++-- .../src/hooks/modals/useModal.ts | 19 ++++ 9 files changed, 306 insertions(+), 62 deletions(-) diff --git a/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css b/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css index 3086de0c7..bad5cb031 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css +++ b/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css @@ -52,7 +52,7 @@ } .configure-btn { - padding: 8px 16px 8px 16px; + padding: 5px 30px 5px 30px; box-shadow: 0px 0px 44px 0px var(--button-shadow); width: fit-content; } @@ -81,6 +81,76 @@ input::placeholder { font-size: 11px; } +.dm3-address { + word-break: break-word; +} + +.address-tooltip{ + background: var(--normal-btn-hover); + color: var(--text-primary-color); + margin-left: 0.6rem; + padding: 2px 7px 2px 7px; + border-radius: 13px; + font-size: 10px; + font-weight: bold; + cursor: pointer; + position: relative; +} + +.address-tooltip-text{ + visibility: hidden; + position: absolute; + z-index: 1; + width: 300px; + border-radius: 6px; + padding: 6px 6px 6px 6px; + margin: 22px 9px 10px 0px; + background-color: var(--text-secondary-color); + font-size: 0.7rem; + font-weight: 100; + line-height: 1rem; + cursor: text; +} + +.address-tooltip:hover .address-tooltip-text { + visibility: visible; +} + +.add-prof-btn-disabled { + background: var(--normal-btn-inactive); + color: var(--disabled-btn-text); + border: none; +} + +.add-prof-btn-active { + background-color: var(--normal-btn-hover); + border: var(--normal-btn-border); + color: var(--text-primary-color); +} + +.dm3-prof-select-container{ + border: 1px solid #FFFFFF; /* TODO : Replace hardcode color with dynamic color */ + border-radius: 5px; +} + +.dm3-prof-select-type{ + background: #D9D9D9; /* TODO : Replace hardcode color with dynamic color */ + color: black; + font-weight: 500; + font-size: 14px; + padding: 0.5rem 0.5rem 0.5rem 0.5rem; + line-height: 24px; + border-radius: 5px; +} + +.name-option{ + color: #FFFFFF; + font-weight: 500; + font-size: 14px; + line-height: 24px; + margin-left: 0.5rem; +} + /* =================== Mobile Responsive CSS =================== */ @media only screen and (min-width: 800px) and (max-width: 1150px) { @@ -114,7 +184,7 @@ input::placeholder { } .configure-btn { - padding: 6px 14px 4px 14px; + padding: 4px 14px 4px 14px; } .dm3-name-content { @@ -135,4 +205,13 @@ input::placeholder { .name-service-selector{ font-size: 12px; } + + .dm3-address { + word-break: break-word; + font-size: 13px !important; + } + + .address-tooltip-text{ + margin: 22px 0px 10px -118px !important; + } } diff --git a/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx index 378d48b12..79451a8ed 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx @@ -5,6 +5,7 @@ import { useChainId } from 'wagmi'; import { AuthContext } from '../../context/AuthContext'; import { useMainnetProvider } from '../../hooks/mainnetprovider/useMainnetProvider'; import { + BUTTON_CLASS, dm3NamingServices, fetchComponent, fetchDM3NameComponent, @@ -14,6 +15,7 @@ import { } from './bl'; import { ConfigureProfileContext } from './context/ConfigureProfileContext'; import { DM3ConfigurationContext } from '../../context/DM3ConfigurationContext'; +import { ModalContext } from '../../context/ModalContext'; export function MobileView() { const connectedChainId = useChainId(); @@ -22,6 +24,8 @@ export function MobileView() { const { account, ethAddress } = useContext(AuthContext); + const { configureProfileModal, setConfigureProfileModal } = useContext(ModalContext); + const { setEnsName, dm3NameServiceSelected, @@ -32,27 +36,86 @@ export function MobileView() { const { dm3Configuration } = useContext(DM3ConfigurationContext); - // handles ENS name and address - useEffect(() => { - getEnsName( - mainnetProvider, - ethAddress!, - account!, - (name: string) => setEnsName(name), - dm3Configuration.addressEnsSubdomain, - ); - }, [ethAddress]); + // // handles ENS name and address + // useEffect(() => { + // getEnsName( + // mainnetProvider, + // ethAddress!, + // account!, + // (name: string) => setEnsName(name), + // dm3Configuration.addressEnsSubdomain, + // ); + // }, [ethAddress]); - useEffect(() => { - if (connectedChainId) { - setNamingServiceSelected(fetchServiceFromChainId(connectedChainId)); - } - }, []); + // useEffect(() => { + // if (connectedChainId) { + // setNamingServiceSelected(fetchServiceFromChainId(connectedChainId)); + // } + // }, []); return (
+ {/* Wallet address */} +
+
+

+ Wallet Address + + i + + You can use your wallet address as a username. + A virtual profile is created and stored at a dm3 service. + There are no transaction costs for creation and administration. +
+ You can receive messages sent to your wallet address. +
+
+

+
+

+ {ethAddress && + ethAddress + + dm3Configuration.addressEnsSubdomain} +

+ {/*
+
+ You can use your wallet address as a username. A + virtual profile is created and stored at a dm3 + service. There are no transaction costs for creation + and administration. +
+
+ You can receive messages sent to your wallet + address. +
+
*/} +
+ + {/* Add profile button */} +
+ +
+ {/* DM3 Name */} -
+ {/*
- {fetchComponent(namingServiceSelected, dm3Configuration.chainId)} + {fetchComponent(namingServiceSelected, dm3Configuration.chainId)} */}
); } diff --git a/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx index 0f9fd8a29..04ac86684 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx @@ -6,6 +6,7 @@ import tickIcon from '../../assets/images/white-tick.svg'; import { AuthContext } from '../../context/AuthContext'; import { useMainnetProvider } from '../../hooks/mainnetprovider/useMainnetProvider'; import { + BUTTON_CLASS, dm3NamingServices, fetchComponent, fetchDM3NameComponent, @@ -15,6 +16,7 @@ import { } from './bl'; import { ConfigureProfileContext } from './context/ConfigureProfileContext'; import { DM3ConfigurationContext } from '../../context/DM3ConfigurationContext'; +import { ModalContext } from '../../context/ModalContext'; export function NormalView() { const connectedChainId = useChainId(); @@ -23,6 +25,8 @@ export function NormalView() { const { account, ethAddress } = useContext(AuthContext); + const { configureProfileModal, setConfigureProfileModal } = useContext(ModalContext); + const { setEnsName, dm3NameServiceSelected, @@ -34,15 +38,15 @@ export function NormalView() { const { dm3Configuration } = useContext(DM3ConfigurationContext); // handles ENS name and address - useEffect(() => { - getEnsName( - mainnetProvider, - ethAddress!, - account!, - (name: string) => setEnsName(name), - dm3Configuration.addressEnsSubdomain, - ); - }, [ethAddress]); + // useEffect(() => { + // getEnsName( + // mainnetProvider, + // ethAddress!, + // account!, + // (name: string) => setEnsName(name), + // dm3Configuration.addressEnsSubdomain, + // ); + // }, [ethAddress]); useEffect(() => { if (connectedChainId) { @@ -53,29 +57,56 @@ export function NormalView() { return (
{/* Wallet Address */} -
-
+
+ {/*
{ethAddress && } -
+
*/} -
+

Wallet Address + + i + + You can use your wallet address as a username. + A virtual profile is created and stored at a dm3 service. + There are no transaction costs for creation and administration. +
+ You can receive messages sent to your wallet address. +
+

{ethAddress && ethAddress + - dm3Configuration.addressEnsSubdomain} + dm3Configuration.addressEnsSubdomain}

-
+ + {/* Add profile button */} +
+ +
+ {/*
You can use your wallet address as a username. A virtual profile is created and stored at a dm3 @@ -86,12 +117,33 @@ export function NormalView() { You can receive messages sent to your wallet address.
-
+
*/} +
+
+ +
+
+ Add new dm3 profile - select type +
+
+ + +
+ + +
+
+
{/* DM3 Name */} -
+ {/*
- {fetchComponent(namingServiceSelected, dm3Configuration.chainId)} + {fetchComponent(namingServiceSelected, dm3Configuration.chainId)} */}
); } diff --git a/packages/messenger-widget/src/components/Preferences/DM3Profile/DM3Profile.tsx b/packages/messenger-widget/src/components/Preferences/DM3Profile/DM3Profile.tsx index a486a2b41..fedc603d9 100644 --- a/packages/messenger-widget/src/components/Preferences/DM3Profile/DM3Profile.tsx +++ b/packages/messenger-widget/src/components/Preferences/DM3Profile/DM3Profile.tsx @@ -13,8 +13,8 @@ export function DM3Profile() { const description = screenWidth <= MOBILE_SCREEN_WIDTH ? '' - : 'Your dm3 profile needs to be published. You can use your own ENS name, ' + - 'get a DM3 name, or keep your wallet address.'; + : 'Your dm3 profile needs to be published. You can use your own web3 name (ENS, ...),' + + 'get a dm3 name, or keep your wallet address.'; return (
diff --git a/packages/messenger-widget/src/components/Preferences/Heading/Heading.tsx b/packages/messenger-widget/src/components/Preferences/Heading/Heading.tsx index 8603c8efc..4db407ec6 100644 --- a/packages/messenger-widget/src/components/Preferences/Heading/Heading.tsx +++ b/packages/messenger-widget/src/components/Preferences/Heading/Heading.tsx @@ -14,7 +14,7 @@ export interface IHeading { export function Heading(props: IHeading) { const { screenWidth } = useContext(DM3ConfigurationContext); - const { setShowPreferencesModal, setShowProfileConfigurationModal } = + const { setShowPreferencesModal, setShowProfileConfigurationModal, resetConfigureProfileModal } = useContext(ModalContext); return ( @@ -33,6 +33,7 @@ export function Heading(props: IHeading) { src={closeIcon} alt="close" onClick={() => { + resetConfigureProfileModal(); setShowPreferencesModal(false); closeConfigurationModal( setShowProfileConfigurationModal, diff --git a/packages/messenger-widget/src/components/Preferences/MobileView.tsx b/packages/messenger-widget/src/components/Preferences/MobileView.tsx index 08e430182..214b5c9ce 100644 --- a/packages/messenger-widget/src/components/Preferences/MobileView.tsx +++ b/packages/messenger-widget/src/components/Preferences/MobileView.tsx @@ -10,6 +10,7 @@ export function MobileView() { setShowPreferencesModal, showProfileConfigurationModal, setShowProfileConfigurationModal, + resetConfigureProfileModal } = useContext(ModalContext); const [optionChoosen, setOptionChoosen] = useState(null); @@ -24,6 +25,13 @@ export function MobileView() { } }, []); + // reset states of configure profile modal if any other component is loaded + useEffect(() => { + if (optionChoosen && optionChoosen.name !== "dm3 Profile") { + resetConfigureProfileModal(); + } + }, [optionChoosen]); + return (
{ + resetConfigureProfileModal(); setShowPreferencesModal(false); closeConfigurationModal( setShowProfileConfigurationModal, diff --git a/packages/messenger-widget/src/components/Preferences/NormalView.tsx b/packages/messenger-widget/src/components/Preferences/NormalView.tsx index 82fe6bdf3..f088dcac2 100644 --- a/packages/messenger-widget/src/components/Preferences/NormalView.tsx +++ b/packages/messenger-widget/src/components/Preferences/NormalView.tsx @@ -11,6 +11,7 @@ export function NormalView() { setShowPreferencesModal, showProfileConfigurationModal, setShowProfileConfigurationModal, + resetConfigureProfileModal } = useContext(ModalContext); const [optionChoosen, setOptionChoosen] = useState(null); @@ -25,6 +26,13 @@ export function NormalView() { } }, []); + // reset states of configure profile modal if any other component is loaded + useEffect(() => { + if (optionChoosen && optionChoosen.name !== "dm3 Profile") { + resetConfigureProfileModal(); + } + }, [optionChoosen]); + return (
{ + resetConfigureProfileModal(); setShowPreferencesModal(false); closeConfigurationModal( setShowProfileConfigurationModal, diff --git a/packages/messenger-widget/src/context/ModalContext.tsx b/packages/messenger-widget/src/context/ModalContext.tsx index bbbb83439..ae222f972 100644 --- a/packages/messenger-widget/src/context/ModalContext.tsx +++ b/packages/messenger-widget/src/context/ModalContext.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { NewContact } from '../interfaces/utils'; import { MessageActionType } from '../utils/enum-type-utils'; -import { IOpenEmojiPopup, useModal } from '../hooks/modals/useModal'; +import { IConfigureProfileModal, IOpenEmojiPopup, useModal } from '../hooks/modals/useModal'; export type ModalContextType = { loaderContent: string; @@ -22,33 +22,39 @@ export type ModalContextType = { setShowAboutModal: (show: boolean) => void; showAddConversationModal: boolean; setShowAddConversationModal: (show: boolean) => void; + configureProfileModal: IConfigureProfileModal + setConfigureProfileModal: (modal: IConfigureProfileModal) => void, + resetConfigureProfileModal: () => void; resetModalStates: () => void; }; export const ModalContext = React.createContext({ loaderContent: '', - setLoaderContent: (content: string) => {}, + setLoaderContent: (content: string) => { }, contactToHide: undefined, - setContactToHide: (action: string | undefined) => {}, + setContactToHide: (action: string | undefined) => { }, addConversation: { active: false, ensName: undefined, processed: false, }, - setAddConversation: (contact: NewContact) => {}, + setAddConversation: (contact: NewContact) => { }, openEmojiPopup: { action: false, data: undefined }, - setOpenEmojiPopup: (action: IOpenEmojiPopup) => {}, + setOpenEmojiPopup: (action: IOpenEmojiPopup) => { }, lastMessageAction: MessageActionType.NONE, - setLastMessageAction: (action: MessageActionType) => {}, + setLastMessageAction: (action: MessageActionType) => { }, showProfileConfigurationModal: false, - setShowProfileConfigurationModal: (show: boolean) => {}, + setShowProfileConfigurationModal: (show: boolean) => { }, showPreferencesModal: false, - setShowPreferencesModal: (show: boolean) => {}, + setShowPreferencesModal: (show: boolean) => { }, showAboutModal: false, - setShowAboutModal: (show: boolean) => {}, + setShowAboutModal: (show: boolean) => { }, showAddConversationModal: false, - setShowAddConversationModal: (show: boolean) => {}, - resetModalStates: () => {}, + setShowAddConversationModal: (show: boolean) => { }, + configureProfileModal: { isAddProfileButtonActive: true }, + setConfigureProfileModal: (modal: IConfigureProfileModal) => { }, + resetConfigureProfileModal: () => { }, + resetModalStates: () => { }, }); export const ModalContextProvider = ({ children }: { children?: any }) => { @@ -71,6 +77,9 @@ export const ModalContextProvider = ({ children }: { children?: any }) => { setShowAboutModal, showAddConversationModal, setShowAddConversationModal, + configureProfileModal, + setConfigureProfileModal, + resetConfigureProfileModal, resetModalStates, } = useModal(); @@ -96,6 +105,9 @@ export const ModalContextProvider = ({ children }: { children?: any }) => { showAddConversationModal, setShowAddConversationModal, resetModalStates, + configureProfileModal, + setConfigureProfileModal, + resetConfigureProfileModal }} > {children} diff --git a/packages/messenger-widget/src/hooks/modals/useModal.ts b/packages/messenger-widget/src/hooks/modals/useModal.ts index df4f9328f..4c1165a6b 100644 --- a/packages/messenger-widget/src/hooks/modals/useModal.ts +++ b/packages/messenger-widget/src/hooks/modals/useModal.ts @@ -8,6 +8,10 @@ export interface IOpenEmojiPopup { data: MessageProps | undefined; } +export interface IConfigureProfileModal { + isAddProfileButtonActive: boolean; +} + export const useModal = () => { const [loaderContent, setLoaderContent] = useState(''); @@ -40,6 +44,15 @@ export const useModal = () => { const [showAboutModal, setShowAboutModal] = useState(false); + const [configureProfileModal, setConfigureProfileModal] = useState({ + isAddProfileButtonActive: true + }); + + const resetConfigureProfileModal = () => { + setConfigureProfileModal({ + isAddProfileButtonActive: true + }); + } const resetModalStates = () => { setLoaderContent(''); setContactToHide(undefined); @@ -54,6 +67,9 @@ export const useModal = () => { setShowPreferencesModal(false); setShowAboutModal(false); setShowAddConversationModal(false); + setConfigureProfileModal({ + isAddProfileButtonActive: true + }) }; return { @@ -76,5 +92,8 @@ export const useModal = () => { showAddConversationModal, setShowAddConversationModal, resetModalStates, + configureProfileModal, + setConfigureProfileModal, + resetConfigureProfileModal }; }; From 58a89b4035b08d165aac9fbd72fb757aa630cd6d Mon Sep 17 00:00:00 2001 From: Bhupesh-MS Date: Mon, 5 Aug 2024 19:51:34 +0530 Subject: [PATCH 02/49] added radio buttons for profile --- .../ConfigureProfile/ConfigureProfile.css | 55 ++++++++++++++++++- .../ConfigureProfile/NormalView.tsx | 32 +++++++++-- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css b/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css index bad5cb031..0eee2b8ab 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css +++ b/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css @@ -144,13 +144,66 @@ input::placeholder { } .name-option{ - color: #FFFFFF; + color: #FFFFFF; /* TODO : Replace hardcode color with dynamic color */ font-weight: 500; font-size: 14px; line-height: 24px; margin-left: 0.5rem; + display: flex; + align-items: center; } +/* Customized CSS for radio button */ +.radio { + input[type="radio"] { + opacity: 0; + + .radio-label { + &:before { + content: ''; + background: #1F2029; /* TODO : Replace hardcode color with dynamic color */ + border-radius: 100%; + border: 1px solid #FFFFFF; /* TODO : Replace hardcode color with dynamic color */ + display: inline-block; + width: 1.4em; + height: 1.4em; + position: relative; + top: -0.2em; + margin-right: 1em; + vertical-align: top; + cursor: pointer; + text-align: center; + transition: all 250ms ease; + } + } + &:checked { + + .radio-label { + &:before { + background-color: #D9D9D9; /* TODO : Replace hardcode color with dynamic color */ + box-shadow: inset 0 0 0 4px #1F2029; /* TODO : Replace hardcode color with dynamic color */ + } + } + } + &:focus { + + .radio-label { + &:before { + outline: none; + border-color: #D9D9D9; /* TODO : Replace hardcode color with dynamic color */ + } + } + } + /* &:disabled { + + .radio-label { + &:before { + box-shadow: inset 0 0 0 4px #f4f4f4; + border-color: darken(#f4f4f4, 25%); + background: darken(#f4f4f4, 25%); + } + } + } */ + } + } + + /* =================== Mobile Responsive CSS =================== */ @media only screen and (min-width: 800px) and (max-width: 1150px) { diff --git a/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx index 04ac86684..670856f48 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx @@ -1,6 +1,6 @@ import '../../styles/modal.css'; import './ConfigureProfile.css'; -import { useContext, useEffect } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useChainId } from 'wagmi'; import tickIcon from '../../assets/images/white-tick.svg'; import { AuthContext } from '../../context/AuthContext'; @@ -27,6 +27,17 @@ export function NormalView() { const { configureProfileModal, setConfigureProfileModal } = useContext(ModalContext); + const [optionSelected, setOptionSelected] = useState(1); + + const options = [ + { id: 1, name: "Claim a dm3 Name (dm3 cloud, Optimism, ...)", type: "dm3Name" }, + { id: 2, name: "use your own Name (ENS, GENOME, ...)", type: "ownName" } + ] + + useEffect(() => { + console.log("[[[[[[[[[[[[[[", optionSelected); + }, [optionSelected]) + const { setEnsName, dm3NameServiceSelected, @@ -125,12 +136,21 @@ export function NormalView() {
Add new dm3 profile - select type
+
- - -
- - + + {options.map((option) => +
+ setOptionSelected(option.id) + }> + + +
)} +
+
+ + ); +} diff --git a/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css b/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css index 0eee2b8ab..3389eb3f9 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css +++ b/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css @@ -66,10 +66,11 @@ } .name-service-selector { - background: var(--preferences-highlighted-color); + background-color: var(--text-primary-color); font-size: 14px; padding: 0.3rem; - font-weight: 500; + font-weight: bold; + color: #0D0D0D; outline: none; } @@ -129,22 +130,22 @@ input::placeholder { } .dm3-prof-select-container{ - border: 1px solid #FFFFFF; /* TODO : Replace hardcode color with dynamic color */ + border: 1px solid var(--text-primary-color); border-radius: 5px; } .dm3-prof-select-type{ - background: #D9D9D9; /* TODO : Replace hardcode color with dynamic color */ - color: black; + background: var(--configure-profile-modal-background-color); font-weight: 500; font-size: 14px; padding: 0.5rem 0.5rem 0.5rem 0.5rem; line-height: 24px; border-radius: 5px; + color: black; } .name-option{ - color: #FFFFFF; /* TODO : Replace hardcode color with dynamic color */ + color: var(--text-primary-color); font-weight: 500; font-size: 14px; line-height: 24px; @@ -153,6 +154,10 @@ input::placeholder { align-items: center; } +.prof-option-container{ + padding: 1rem; +} + /* Customized CSS for radio button */ .radio { input[type="radio"] { @@ -160,9 +165,9 @@ input::placeholder { + .radio-label { &:before { content: ''; - background: #1F2029; /* TODO : Replace hardcode color with dynamic color */ + background: #1F2029; border-radius: 100%; - border: 1px solid #FFFFFF; /* TODO : Replace hardcode color with dynamic color */ + border: 1px solid var(--text-primary-color); display: inline-block; width: 1.4em; height: 1.4em; @@ -178,8 +183,8 @@ input::placeholder { &:checked { + .radio-label { &:before { - background-color: #D9D9D9; /* TODO : Replace hardcode color with dynamic color */ - box-shadow: inset 0 0 0 4px #1F2029; /* TODO : Replace hardcode color with dynamic color */ + background-color: var(--configure-profile-modal-background-color); + box-shadow: inset 0 0 0 4px #1F2029; } } } @@ -187,22 +192,16 @@ input::placeholder { + .radio-label { &:before { outline: none; - border-color: #D9D9D9; /* TODO : Replace hardcode color with dynamic color */ + border-color: var(--configure-profile-modal-background-color); } } } - /* &:disabled { - + .radio-label { - &:before { - box-shadow: inset 0 0 0 4px #f4f4f4; - border-color: darken(#f4f4f4, 25%); - background: darken(#f4f4f4, 25%); - } - } - } */ } } + .ens-components-container{ + padding: 1.25rem; + } /* =================== Mobile Responsive CSS =================== */ @@ -213,12 +212,20 @@ input::placeholder { .conversation-error { font-size: 0.7rem !important; } + .ens-components-container{ + padding: 1rem; + } } @media only screen and (max-width: 800px) { .profile-input { padding: 0.2rem 0.5rem 0.2rem 0.5rem; width: 100%; + font-size: 12px; + } + + .profile-input::placeholder{ + font-size: 10px !important; } .dm3-name-container { @@ -257,6 +264,7 @@ input::placeholder { .name-service-selector{ font-size: 12px; + width: 100%; } .dm3-address { @@ -265,6 +273,37 @@ input::placeholder { } .address-tooltip-text{ - margin: 22px 0px 10px -118px !important; + margin: 22px 0px 10px -83px !important; } + + .prof-option-container{ + padding: 1rem 1rem 1rem 0rem; + } + + .name-select-container{ + margin-left: -11px; + } + + .radio { + input[type="radio"] { + opacity: 0; + + .radio-label { + &:before { + width: 2em !important; + } + } + } + } + + .dm3-prof-select-type{ + font-size: 12px; + } + + .title-content { + font-size: 12px; + } + + .ens-components-container{ + padding: 0.5rem; + } } diff --git a/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx index 79451a8ed..909856d81 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx @@ -4,18 +4,19 @@ import { useContext, useEffect } from 'react'; import { useChainId } from 'wagmi'; import { AuthContext } from '../../context/AuthContext'; import { useMainnetProvider } from '../../hooks/mainnetprovider/useMainnetProvider'; -import { - BUTTON_CLASS, - dm3NamingServices, - fetchComponent, - fetchDM3NameComponent, - fetchServiceFromChainId, - getEnsName, - namingServices, -} from './bl'; +import { BUTTON_CLASS, fetchServiceFromChainId, getEnsName } from './bl'; import { ConfigureProfileContext } from './context/ConfigureProfileContext'; import { DM3ConfigurationContext } from '../../context/DM3ConfigurationContext'; import { ModalContext } from '../../context/ModalContext'; +import { ProfileScreenType, ProfileType } from '../../utils/enum-type-utils'; +import { ProfileTypeSelector } from './ProfileTypeSelector'; +import { ClaimDM3Name } from './ClaimDM3Name'; +import { ConfigureDM3NameContext } from './context/ConfigureDM3NameContext'; +import DeleteDM3Name from '../DeleteDM3Name/DeleteDM3Name'; +import deleteIcon from '../../assets/images/delete.svg'; +import { CloudStorage } from './CloudStorage'; +import { OwnStorage } from './OwnStorage'; +import { ClaimOwnName } from './ClaimOwnName'; export function MobileView() { const connectedChainId = useChainId(); @@ -24,53 +25,78 @@ export function MobileView() { const { account, ethAddress } = useContext(AuthContext); - const { configureProfileModal, setConfigureProfileModal } = useContext(ModalContext); + const { configureProfileModal, setConfigureProfileModal } = + useContext(ModalContext); + + const { setEnsName, setNamingServiceSelected, existingEnsName } = + useContext(ConfigureProfileContext); const { - setEnsName, - dm3NameServiceSelected, - setDm3NameServiceSelected, - namingServiceSelected, - setNamingServiceSelected, - } = useContext(ConfigureProfileContext); + existingDm3Name, + setShowDeleteConfirmation, + showDeleteConfirmation, + updateDeleteConfirmation, + handleClaimOrRemoveDm3Name, + } = useContext(ConfigureDM3NameContext); const { dm3Configuration } = useContext(DM3ConfigurationContext); - // // handles ENS name and address - // useEffect(() => { - // getEnsName( - // mainnetProvider, - // ethAddress!, - // account!, - // (name: string) => setEnsName(name), - // dm3Configuration.addressEnsSubdomain, - // ); - // }, [ethAddress]); - - // useEffect(() => { - // if (connectedChainId) { - // setNamingServiceSelected(fetchServiceFromChainId(connectedChainId)); - // } - // }, []); + const ensDomainName = + (existingDm3Name && + (existingEnsName?.endsWith('.gno') || + existingEnsName?.endsWith('.gnosis.eth') + ? 'GNO' + : 'ENS')) ?? + null; + + // handles ENS name and address + useEffect(() => { + getEnsName( + mainnetProvider, + ethAddress!, + account!, + (name: string) => setEnsName(name), + dm3Configuration.addressEnsSubdomain, + ); + }, [ethAddress]); + + useEffect(() => { + if (connectedChainId) { + setNamingServiceSelected(fetchServiceFromChainId(connectedChainId)); + } + }, []); return ( -
- {/* Wallet address */} +
+ {/* Delete DM3 name confirmation popup modal */} + {showDeleteConfirmation && ( + + )} +
+ {/* Wallet address */}

Wallet Address - + i - You can use your wallet address as a username. - A virtual profile is created and stored at a dm3 service. - There are no transaction costs for creation and administration. + You can use your wallet address as a username. A + virtual profile is created and stored at a dm3 + service. There are no transaction costs for + creation and administration.
- You can receive messages sent to your wallet address. + + {' '} + You can receive messages sent to your wallet + address. +

@@ -80,92 +106,139 @@ export function MobileView() { font-size-14 font-weight-500 line-height-24 grey-text" > {ethAddress && - ethAddress + - dm3Configuration.addressEnsSubdomain} + ethAddress + dm3Configuration.addressEnsSubdomain}

- {/*
-
- You can use your wallet address as a username. A - virtual profile is created and stored at a dm3 - service. There are no transaction costs for creation - and administration. -
-
- You can receive messages sent to your wallet - address. -
-
*/}
+ {/* Existing DM3 name */} + {existingDm3Name && ( +
+

+ DM3 Name + + i + + DM3 name is used as a username and can be used + by any address to send the messages to this DM3 + name. +
+ + {' '} + You can receive messages sent to your DM3 + name. + +
+
+

+

+ {existingDm3Name} +

+ {/* Delete icon */} + remove setShowDeleteConfirmation(true)} + /> +
+ )} + + {/* Existing ENS name */} + {ensDomainName && existingEnsName && ( +
+

+ {ensDomainName} Name + + i + + {ensDomainName} name is used as a username and + can be used by any address to send the messages + to this {ensDomainName} + name. +
+ + {' '} + You can receive messages sent to your{' '} + {ensDomainName} + name. + +
+
+

+

+ {existingEnsName} +

+ {/* Delete icon */} + remove setShowDeleteConfirmation(true)} + /> +
+ )} + {/* Add profile button */} -
+
- {/* DM3 Name */} - {/*
-
-
- -
-
*/} - - {/* {fetchDM3NameComponent( - dm3NameServiceSelected, - dm3Configuration.chainId, - )} */} - - {/* ENS Name */} - {/*
-
-
- + {/* Screen to select profile type */} + {configureProfileModal.onScreen === + ProfileScreenType.SELECT_TYPE && } + + {/* Screen to select storage type */} + {configureProfileModal.onScreen === + ProfileScreenType.SELECT_STORAGE && ( +
+ {configureProfileModal.profileOptionSelected === + ProfileType.DM3_NAME ? ( + + ) : ( + + )}
-
+ )} - {fetchComponent(namingServiceSelected, dm3Configuration.chainId)} */} + {/* Screen to claim profile name */} + {configureProfileModal.onScreen === ProfileScreenType.CLAIM_NAME && + (configureProfileModal.profileOptionSelected === + ProfileType.DM3_NAME ? ( + + ) : ( + + ))}
); } diff --git a/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx index 670856f48..b505969f0 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx @@ -1,22 +1,22 @@ import '../../styles/modal.css'; import './ConfigureProfile.css'; -import { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect } from 'react'; import { useChainId } from 'wagmi'; -import tickIcon from '../../assets/images/white-tick.svg'; import { AuthContext } from '../../context/AuthContext'; import { useMainnetProvider } from '../../hooks/mainnetprovider/useMainnetProvider'; -import { - BUTTON_CLASS, - dm3NamingServices, - fetchComponent, - fetchDM3NameComponent, - fetchServiceFromChainId, - getEnsName, - namingServices, -} from './bl'; +import { BUTTON_CLASS, fetchServiceFromChainId, getEnsName } from './bl'; import { ConfigureProfileContext } from './context/ConfigureProfileContext'; import { DM3ConfigurationContext } from '../../context/DM3ConfigurationContext'; import { ModalContext } from '../../context/ModalContext'; +import { ProfileTypeSelector } from './ProfileTypeSelector'; +import { ProfileScreenType, ProfileType } from '../../utils/enum-type-utils'; +import { ClaimDM3Name } from './ClaimDM3Name'; +import { ConfigureDM3NameContext } from './context/ConfigureDM3NameContext'; +import deleteIcon from '../../assets/images/delete.svg'; +import DeleteDM3Name from '../DeleteDM3Name/DeleteDM3Name'; +import { CloudStorage } from './CloudStorage'; +import { OwnStorage } from './OwnStorage'; +import { ClaimOwnName } from './ClaimOwnName'; export function NormalView() { const connectedChainId = useChainId(); @@ -25,39 +25,40 @@ export function NormalView() { const { account, ethAddress } = useContext(AuthContext); - const { configureProfileModal, setConfigureProfileModal } = useContext(ModalContext); + const { configureProfileModal, setConfigureProfileModal } = + useContext(ModalContext); - const [optionSelected, setOptionSelected] = useState(1); - - const options = [ - { id: 1, name: "Claim a dm3 Name (dm3 cloud, Optimism, ...)", type: "dm3Name" }, - { id: 2, name: "use your own Name (ENS, GENOME, ...)", type: "ownName" } - ] - - useEffect(() => { - console.log("[[[[[[[[[[[[[[", optionSelected); - }, [optionSelected]) + const { setEnsName, setNamingServiceSelected, existingEnsName } = + useContext(ConfigureProfileContext); const { - setEnsName, - dm3NameServiceSelected, - setDm3NameServiceSelected, - namingServiceSelected, - setNamingServiceSelected, - } = useContext(ConfigureProfileContext); + existingDm3Name, + showDeleteConfirmation, + setShowDeleteConfirmation, + updateDeleteConfirmation, + handleClaimOrRemoveDm3Name, + } = useContext(ConfigureDM3NameContext); const { dm3Configuration } = useContext(DM3ConfigurationContext); + const ensDomainName = + (existingDm3Name && + (existingEnsName?.endsWith('.gno') || + existingEnsName?.endsWith('.gnosis.eth') + ? 'GNO' + : 'ENS')) ?? + null; + // handles ENS name and address - // useEffect(() => { - // getEnsName( - // mainnetProvider, - // ethAddress!, - // account!, - // (name: string) => setEnsName(name), - // dm3Configuration.addressEnsSubdomain, - // ); - // }, [ethAddress]); + useEffect(() => { + getEnsName( + mainnetProvider, + ethAddress!, + account!, + (name: string) => setEnsName(name), + dm3Configuration.addressEnsSubdomain, + ); + }, [ethAddress]); useEffect(() => { if (connectedChainId) { @@ -67,27 +68,37 @@ export function NormalView() { return (
- {/* Wallet Address */} -
- {/*
- {ethAddress && } -
*/} + {/* Delete DM3 name confirmation popup modal */} + {showDeleteConfirmation && ( + + )} +
+ {/* Wallet Address */}

Wallet Address - + i - You can use your wallet address as a username. - A virtual profile is created and stored at a dm3 service. - There are no transaction costs for creation and administration. + You can use your wallet address as a + username. A virtual profile is created and + stored at a dm3 service. There are no + transaction costs for creation and + administration.
- You can receive messages sent to your wallet address. + + {' '} + You can receive messages sent to your + wallet address. +

@@ -97,123 +108,133 @@ export function NormalView() { > {ethAddress && ethAddress + - dm3Configuration.addressEnsSubdomain} + dm3Configuration.addressEnsSubdomain}

+ {/* Existing DM3 name */} + {existingDm3Name && ( +
+

+ DM3 Name + + i + + DM3 name is used as a username and can + be used by any address to send the + messages to this DM3 name. +
+ + {' '} + You can receive messages sent to + your DM3 name. + +
+
+

+

+ {existingDm3Name} +

+ {/* Show delete */} + remove setShowDeleteConfirmation(true)} + /> +
+ )} + + {/* Existing ENS name */} + {ensDomainName && existingEnsName && ( +
+

+ {ensDomainName} Name + + i + + {ensDomainName} name is used as a + username and can be used by any address + to send the messages to this{' '} + {ensDomainName} name. +
+ + {' '} + You can receive messages sent to + your {ensDomainName} name. + +
+
+

+

+ {existingEnsName} +

+
+ )} + {/* Add profile button */} -
+
- {/*
-
- You can use your wallet address as a username. A - virtual profile is created and stored at a dm3 - service. There are no transaction costs for creation - and administration. -
-
- You can receive messages sent to your wallet - address. -
-
*/} -
-
- -
-
- Add new dm3 profile - select type -
- -
- - {options.map((option) => -
- setOptionSelected(option.id) - }> - - -
)} - -
-
-
- {/* DM3 Name */} - {/*
-
-
- -
-
*/} - - {/* {fetchDM3NameComponent( - dm3NameServiceSelected, - dm3Configuration.chainId, - )} */} - - {/* ENS Name */} - {/*
-
-
- + {/* Screen to select profile type */} + {configureProfileModal.onScreen === + ProfileScreenType.SELECT_TYPE && } + + {/* Screen to select storage type */} + {configureProfileModal.onScreen === + ProfileScreenType.SELECT_STORAGE && ( +
+ {configureProfileModal.profileOptionSelected === + ProfileType.DM3_NAME ? ( + + ) : ( + + )}
-
- - {fetchComponent(namingServiceSelected, dm3Configuration.chainId)} */} + )} + + {/* Screen to claim profile name */} + {configureProfileModal.onScreen === ProfileScreenType.CLAIM_NAME && + (configureProfileModal.profileOptionSelected === + ProfileType.DM3_NAME ? ( + + ) : ( + + ))}
); } diff --git a/packages/messenger-widget/src/components/ConfigureProfile/OwnStorage.tsx b/packages/messenger-widget/src/components/ConfigureProfile/OwnStorage.tsx new file mode 100644 index 000000000..7e8aa642d --- /dev/null +++ b/packages/messenger-widget/src/components/ConfigureProfile/OwnStorage.tsx @@ -0,0 +1,106 @@ +import { useContext, useState } from 'react'; +import { ModalContext } from '../../context/ModalContext'; +import { ProfileScreenType } from '../../utils/enum-type-utils'; +import { BUTTON_CLASS, NAME_SERVICES, namingServices } from './bl'; +import { ConfigureProfileContext } from './context/ConfigureProfileContext'; + +export function OwnStorage() { + const [errorMsg, setErrorMsg] = useState(null); + + const { configureProfileModal, setConfigureProfileModal } = + useContext(ModalContext); + + const { existingEnsName, namingServiceSelected, setNamingServiceSelected } = + useContext(ConfigureProfileContext); + + const isNameAlreadyConfigured = (): boolean => { + if ( + (existingEnsName?.endsWith('.gno') || + existingEnsName?.endsWith('.gnosis.eth')) && + namingServiceSelected === NAME_SERVICES.GENOME + ) { + setErrorMsg( + 'GNO name is already configured, only one GNO name can be configured at a time', + ); + return true; + } + if ( + existingEnsName?.endsWith('.eth') && + namingServiceSelected === NAME_SERVICES.ENS + ) { + setErrorMsg( + 'ENS name is already configured, only one ENS name can be configured at a time', + ); + return true; + } + return false; + }; + + const navigateToNextTab = () => { + if (!isNameAlreadyConfigured()) { + setErrorMsg(null); + setConfigureProfileModal({ + ...configureProfileModal, + onScreen: ProfileScreenType.CLAIM_NAME, + }); + } + }; + + return ( + <> +
+ Add new dm3 profile - claim DM3 profile - select storage +
+ +
+
+ +
+ +
+ {errorMsg ?? ''} +
+ +
+
+ To publish your dm3 profile, a transaction is sent to + set a text record in your ENS name or GNO name. + Transaction costs will apply for setting the profile and + administration. +
+
+ You can receive dm3 messages directly sent to your ENS + name or GNO name +
+
+
+
+ +
+ + ); +} diff --git a/packages/messenger-widget/src/components/ConfigureProfile/ProfileTypeSelector.tsx b/packages/messenger-widget/src/components/ConfigureProfile/ProfileTypeSelector.tsx new file mode 100644 index 000000000..8cf095a91 --- /dev/null +++ b/packages/messenger-widget/src/components/ConfigureProfile/ProfileTypeSelector.tsx @@ -0,0 +1,72 @@ +import { useContext } from 'react'; +import { ModalContext } from '../../context/ModalContext'; +import { ProfileScreenType, ProfileType } from '../../utils/enum-type-utils'; +import { BUTTON_CLASS } from './bl'; + +export function ProfileTypeSelector() { + const { configureProfileModal, setConfigureProfileModal } = + useContext(ModalContext); + + const profileOptions = [ + { + name: 'Claim a dm3 Name (dm3 cloud, Optimism, ...)', + type: ProfileType.DM3_NAME, + }, + { + name: 'use your own Name (ENS, GENOME, ...)', + type: ProfileType.OWN_NAME, + }, + ]; + + return ( +
+
+ Add new dm3 profile - select type +
+ +
+ {profileOptions.map((option, index) => ( +
+ setConfigureProfileModal({ + ...configureProfileModal, + profileOptionSelected: option.type, + }) + } + > + + +
+ ))} +
+
+ +
+
+ ); +} diff --git a/packages/messenger-widget/src/components/ConfigureProfile/bl.tsx b/packages/messenger-widget/src/components/ConfigureProfile/bl.tsx index 8c453aebb..06efe2379 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/bl.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/bl.tsx @@ -151,7 +151,7 @@ export const fetchExistingDM3Name = async ( } }; -const enum NAME_SERVICES { +export const enum NAME_SERVICES { ENS = 'Ethereum Network - Ethereum Name Service (ENS)', GENOME = 'Gnosis Network - Genome/SpaceID', OPTIMISM = 'Optimism Network', diff --git a/packages/messenger-widget/src/components/ConfigureProfile/chain/MobileView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/chain/MobileView.tsx index bcf3e5e7e..6903c0ab9 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/chain/MobileView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/chain/MobileView.tsx @@ -46,11 +46,13 @@ export const MobileView = ({ }; return ( <> - {/* ENS Name */} -
+
-

+

{propertyName}

{showError === NAME_TYPE.ENS_NAME && errorMsg}
- {!existingEnsName ? ( -
{ - e.preventDefault(); - handlePublishOrRemoveProfile( - existingEnsName - ? ACTION_TYPE.REMOVE - : ACTION_TYPE.CONFIGURE, - ); - }} - > - , - ) => handleNameChange(e)} - /> -
- ) : ( -

- {existingEnsName} -

- )} -
- {!existingEnsName && ( - + +
{ + e.preventDefault(); + handlePublishOrRemoveProfile( + ACTION_TYPE.CONFIGURE, + ); + }} + > + , + ) => handleNameChange(e)} + /> +
+
+
+
diff --git a/packages/messenger-widget/src/components/ConfigureProfile/chain/NormalView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/chain/NormalView.tsx index 0e95ebea1..567737463 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/chain/NormalView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/chain/NormalView.tsx @@ -1,5 +1,4 @@ import { useContext } from 'react'; -import tickIcon from '../../../assets/images/white-tick.svg'; import { ConfigureProfileContext } from '../context/ConfigureProfileContext'; import { ACTION_TYPE, @@ -45,110 +44,77 @@ export const NormalView = ({ setExistingEnsName(null); } }; + return ( <> - {/* ENS Name */} -
-
-
- -
-

- {propertyName} -

- +
+
{showError === NAME_TYPE.ENS_NAME && errorMsg}
-
-
- -
-
- {existingEnsName && } -
-
-

+

{propertyName}

- {!existingEnsName ? ( -
{ - e.preventDefault(); - handlePublishOrRemoveProfile( - existingEnsName - ? ACTION_TYPE.REMOVE - : ACTION_TYPE.CONFIGURE, - ); - }} - > - , - ) => handleNameChange(e)} - /> -
- ) : ( -

- {existingEnsName} -

- )} +
{ + e.preventDefault(); + handlePublishOrRemoveProfile( + ACTION_TYPE.CONFIGURE, + ); + }} + > + , + ) => handleNameChange(e)} + /> +
-
-
+ +
+
{label}
{note}
-
- {!existingEnsName && ( - +
+ +
+
); diff --git a/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/MobileView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/MobileView.tsx index 28b890106..15418e2a8 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/MobileView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/MobileView.tsx @@ -1,6 +1,5 @@ import { useContext } from 'react'; import { AuthContext } from '../../../context/AuthContext'; -import DeleteDM3Name from '../../DeleteDM3Name/DeleteDM3Name'; import { ConfigureDM3NameContext } from '../context/ConfigureDM3NameContext'; import { ConfigureProfileContext } from '../context/ConfigureProfileContext'; import { @@ -9,7 +8,6 @@ import { NAME_TYPE, PROFILE_INPUT_FIELD_CLASS, } from '../chain/common'; -import deleteIcon from '../../../assets/images/delete.svg'; export const MobileView = ({ nameExtension, @@ -24,25 +22,11 @@ export const MobileView = ({ const { errorMsg, showError } = useContext(ConfigureProfileContext); - const { - dm3Name, - existingDm3Name, - showDeleteConfirmation, - handleNameChange, - handleClaimOrRemoveDm3Name, - updateDeleteConfirmation, - setShowDeleteConfirmation, - } = useContext(ConfigureDM3NameContext); + const { dm3Name, handleNameChange, handleClaimOrRemoveDm3Name } = + useContext(ConfigureDM3NameContext); return (
- {/* Delete DM3 name confirmation popup modal */} - {showDeleteConfirmation && ( - - )}

{showError === NAME_TYPE.DM3_NAME && errorMsg}

- {!existingDm3Name ? ( -
{ - e.preventDefault(); - handleClaimOrRemoveDm3Name( - ACTION_TYPE.CONFIGURE, - setDisplayName, - submitDm3UsernameClaim, - ); - }} - > - , - ) => handleNameChange(e, NAME_TYPE.DM3_NAME)} - /> -

- {nameExtension} -

-
- ) : ( - <> -

- {existingDm3Name} - remove - setShowDeleteConfirmation(true) - } - /> -

- - )} -
-
- {!existingDm3Name && ( - - )} + {nameExtension} +

+ +
+
+
diff --git a/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/NormalView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/NormalView.tsx index 1a4456e3c..a39d312ca 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/NormalView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/NormalView.tsx @@ -1,7 +1,5 @@ import { useContext } from 'react'; import { AuthContext } from '../../../context/AuthContext'; -import DeleteDM3Name from '../../DeleteDM3Name/DeleteDM3Name'; -import tickIcon from './../../../assets/images/white-tick.svg'; import { ConfigureProfileContext } from '../context/ConfigureProfileContext'; import { ConfigureDM3NameContext } from '../context/ConfigureDM3NameContext'; import { @@ -10,7 +8,6 @@ import { NAME_TYPE, PROFILE_INPUT_FIELD_CLASS, } from '../chain/common'; -import deleteIcon from '../../../assets/images/delete.svg'; export const NormalView = ({ nameExtension, @@ -25,53 +22,20 @@ export const NormalView = ({ const { errorMsg, showError } = useContext(ConfigureProfileContext); - const { - dm3Name, - existingDm3Name, - showDeleteConfirmation, - handleNameChange, - handleClaimOrRemoveDm3Name, - updateDeleteConfirmation, - setShowDeleteConfirmation, - } = useContext(ConfigureDM3NameContext); + const { dm3Name, handleNameChange, handleClaimOrRemoveDm3Name } = + useContext(ConfigureDM3NameContext); return ( <> -
- {/* Delete DM3 name confirmation popup modal */} - {showDeleteConfirmation && ( - - )} - -
-
- -
-

- DM3 Name -

- +
+
{showError === NAME_TYPE.DM3_NAME && errorMsg}
-
-
- -
-
- {existingDm3Name && } -
-

{ - e.preventDefault(); - handleClaimOrRemoveDm3Name( - ACTION_TYPE.CONFIGURE, - setDisplayName, - submitDm3UsernameClaim, - ); - }} - > - , - ) => - handleNameChange(e, NAME_TYPE.DM3_NAME) - } - /> -

{ + e.preventDefault(); + handleClaimOrRemoveDm3Name( + ACTION_TYPE.CONFIGURE, + setDisplayName, + submitDm3UsernameClaim, + ); + }} + > + , + ) => handleNameChange(e, NAME_TYPE.DM3_NAME)} + /> +

- {nameExtension} -

- - ) : ( - <> -

- {existingDm3Name} - remove - setShowDeleteConfirmation(true) - } - /> -

- - )} + > + {nameExtension} +

+
-
-
+ +
+
You can get a DM3 name for free. Please check if your desired name is available. DM3 names are created and managed on Layer2 (e.g. Optimism). Small @@ -148,29 +92,28 @@ export const NormalView = ({
-
- {!existingDm3Name && ( - +
+ +
+
); diff --git a/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/cloudName/ConfigureCloudNameProfile.tsx b/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/cloudName/ConfigureCloudNameProfile.tsx index 4076db81e..9251ac01b 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/cloudName/ConfigureCloudNameProfile.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/cloudName/ConfigureCloudNameProfile.tsx @@ -1,22 +1,23 @@ import { useContext, useEffect } from 'react'; -import { DM3Name } from './../DM3Name'; +import { DM3Name } from '../DM3Name'; import { NAME_TYPE } from '../../chain/common'; import { AuthContext } from '../../../../context/AuthContext'; import { closeLoader, startLoader } from '../../../Loader/Loader'; import { claimSubdomain } from '../../../../adapters/offchainResolverApi'; import { ConfigureDM3NameContext } from '../../context/ConfigureDM3NameContext'; import { DM3ConfigurationContext } from '../../../../context/DM3ConfigurationContext'; -import { useMainnetProvider } from '../../../../hooks/mainnetprovider/useMainnetProvider'; import { ModalContext } from '../../../../context/ModalContext'; import { ConfigureProfileContext } from '../../context/ConfigureProfileContext'; +import { ProfileScreenType } from '../../../../utils/enum-type-utils'; export const ConfigureCloudNameProfile = () => { - const mainnetProvider = useMainnetProvider(); - const { setLoaderContent } = useContext(ModalContext); const { dm3Configuration } = useContext(DM3ConfigurationContext); + const { configureProfileModal, setConfigureProfileModal } = + useContext(ModalContext); + const { dm3NameServiceSelected } = useContext(ConfigureProfileContext); const { setExistingDm3Name, setError, setDm3Name } = useContext( @@ -47,6 +48,10 @@ export const ConfigureCloudNameProfile = () => { setDisplayName(ensName); setExistingDm3Name(ensName); + setConfigureProfileModal({ + ...configureProfileModal, + onScreen: ProfileScreenType.NONE, + }); } } catch (e) { setError('Name is not available', NAME_TYPE.DM3_NAME); diff --git a/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/optimismName/ConfigureOptimismNameProfile.tsx b/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/optimismName/ConfigureOptimismNameProfile.tsx index 03e1c0b9a..ae6aa2f88 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/optimismName/ConfigureOptimismNameProfile.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/dm3Names/optimismName/ConfigureOptimismNameProfile.tsx @@ -3,22 +3,27 @@ import { useContext, useEffect, useState } from 'react'; import { useChainId, useSwitchNetwork } from 'wagmi'; import { AuthContext } from '../../../../context/AuthContext'; import { ConfigureDM3NameContext } from '../../context/ConfigureDM3NameContext'; -import { closeLoader, startLoader } from './../../../Loader/Loader'; -import { IChain, NAME_TYPE } from './../../chain/common'; -import { DM3Name } from './../DM3Name'; +import { closeLoader, startLoader } from '../../../Loader/Loader'; +import { IChain, NAME_TYPE } from '../../chain/common'; +import { DM3Name } from '../DM3Name'; import { publishProfile } from './tx/publishProfile'; import { registerOpName } from './tx/registerOpName'; import { ModalContext } from '../../../../context/ModalContext'; import { fetchChainIdFromDM3ServiceName } from '../../bl'; import { DM3ConfigurationContext } from '../../../../context/DM3ConfigurationContext'; import { ConfigureProfileContext } from '../../context/ConfigureProfileContext'; +import { ProfileScreenType } from '../../../../utils/enum-type-utils'; export const ConfigureOptimismNameProfile = (props: IChain) => { const connectedChainId = useChainId(); const { switchNetwork } = useSwitchNetwork(); - const { setLoaderContent } = useContext(ModalContext); + const { + setLoaderContent, + configureProfileModal, + setConfigureProfileModal, + } = useContext(ModalContext); const { account, setDisplayName } = useContext(AuthContext); @@ -89,6 +94,10 @@ export const ConfigureOptimismNameProfile = (props: IChain) => { setDisplayName(ensName); setExistingDm3Name(ensName); + setConfigureProfileModal({ + ...configureProfileModal, + onScreen: ProfileScreenType.NONE, + }); } catch (e) { // check user rejects setError('Name is not available', NAME_TYPE.DM3_NAME); diff --git a/packages/messenger-widget/src/components/Preferences/DM3Profile/DM3Profile.tsx b/packages/messenger-widget/src/components/Preferences/DM3Profile/DM3Profile.tsx index fedc603d9..f9591d630 100644 --- a/packages/messenger-widget/src/components/Preferences/DM3Profile/DM3Profile.tsx +++ b/packages/messenger-widget/src/components/Preferences/DM3Profile/DM3Profile.tsx @@ -14,7 +14,7 @@ export function DM3Profile() { screenWidth <= MOBILE_SCREEN_WIDTH ? '' : 'Your dm3 profile needs to be published. You can use your own web3 name (ENS, ...),' + - 'get a dm3 name, or keep your wallet address.'; + 'get a dm3 name, or keep your wallet address.'; return (
diff --git a/packages/messenger-widget/src/components/Preferences/Heading/Heading.tsx b/packages/messenger-widget/src/components/Preferences/Heading/Heading.tsx index 4db407ec6..9f1d186ee 100644 --- a/packages/messenger-widget/src/components/Preferences/Heading/Heading.tsx +++ b/packages/messenger-widget/src/components/Preferences/Heading/Heading.tsx @@ -14,8 +14,11 @@ export interface IHeading { export function Heading(props: IHeading) { const { screenWidth } = useContext(DM3ConfigurationContext); - const { setShowPreferencesModal, setShowProfileConfigurationModal, resetConfigureProfileModal } = - useContext(ModalContext); + const { + setShowPreferencesModal, + setShowProfileConfigurationModal, + resetConfigureProfileModal, + } = useContext(ModalContext); return (
diff --git a/packages/messenger-widget/src/components/Preferences/MobileView.tsx b/packages/messenger-widget/src/components/Preferences/MobileView.tsx index 214b5c9ce..558dd64aa 100644 --- a/packages/messenger-widget/src/components/Preferences/MobileView.tsx +++ b/packages/messenger-widget/src/components/Preferences/MobileView.tsx @@ -10,7 +10,7 @@ export function MobileView() { setShowPreferencesModal, showProfileConfigurationModal, setShowProfileConfigurationModal, - resetConfigureProfileModal + resetConfigureProfileModal, } = useContext(ModalContext); const [optionChoosen, setOptionChoosen] = useState(null); @@ -27,7 +27,7 @@ export function MobileView() { // reset states of configure profile modal if any other component is loaded useEffect(() => { - if (optionChoosen && optionChoosen.name !== "dm3 Profile") { + if (optionChoosen && optionChoosen.name !== 'dm3 Profile') { resetConfigureProfileModal(); } }, [optionChoosen]); @@ -53,7 +53,7 @@ export function MobileView() { ' ', optionChoosen && optionChoosen.name === - item.name + item.name ? 'normal-btn-hover' : '', )} diff --git a/packages/messenger-widget/src/components/Preferences/NormalView.tsx b/packages/messenger-widget/src/components/Preferences/NormalView.tsx index f088dcac2..decf6e4d2 100644 --- a/packages/messenger-widget/src/components/Preferences/NormalView.tsx +++ b/packages/messenger-widget/src/components/Preferences/NormalView.tsx @@ -11,7 +11,7 @@ export function NormalView() { setShowPreferencesModal, showProfileConfigurationModal, setShowProfileConfigurationModal, - resetConfigureProfileModal + resetConfigureProfileModal, } = useContext(ModalContext); const [optionChoosen, setOptionChoosen] = useState(null); @@ -28,7 +28,7 @@ export function NormalView() { // reset states of configure profile modal if any other component is loaded useEffect(() => { - if (optionChoosen && optionChoosen.name !== "dm3 Profile") { + if (optionChoosen && optionChoosen.name !== 'dm3 Profile') { resetConfigureProfileModal(); } }, [optionChoosen]); @@ -71,7 +71,7 @@ export function NormalView() { ' ', optionChoosen && optionChoosen.name === - item.name + item.name ? 'normal-btn-hover' : '', )} diff --git a/packages/messenger-widget/src/context/ModalContext.tsx b/packages/messenger-widget/src/context/ModalContext.tsx index ae222f972..77e1bf7d6 100644 --- a/packages/messenger-widget/src/context/ModalContext.tsx +++ b/packages/messenger-widget/src/context/ModalContext.tsx @@ -1,7 +1,15 @@ import React from 'react'; import { NewContact } from '../interfaces/utils'; -import { MessageActionType } from '../utils/enum-type-utils'; -import { IConfigureProfileModal, IOpenEmojiPopup, useModal } from '../hooks/modals/useModal'; +import { + MessageActionType, + ProfileScreenType, + ProfileType, +} from '../utils/enum-type-utils'; +import { + IConfigureProfileModal, + IOpenEmojiPopup, + useModal, +} from '../hooks/modals/useModal'; export type ModalContextType = { loaderContent: string; @@ -22,39 +30,42 @@ export type ModalContextType = { setShowAboutModal: (show: boolean) => void; showAddConversationModal: boolean; setShowAddConversationModal: (show: boolean) => void; - configureProfileModal: IConfigureProfileModal - setConfigureProfileModal: (modal: IConfigureProfileModal) => void, + configureProfileModal: IConfigureProfileModal; + setConfigureProfileModal: (modal: IConfigureProfileModal) => void; resetConfigureProfileModal: () => void; resetModalStates: () => void; }; export const ModalContext = React.createContext({ loaderContent: '', - setLoaderContent: (content: string) => { }, + setLoaderContent: (content: string) => {}, contactToHide: undefined, - setContactToHide: (action: string | undefined) => { }, + setContactToHide: (action: string | undefined) => {}, addConversation: { active: false, ensName: undefined, processed: false, }, - setAddConversation: (contact: NewContact) => { }, + setAddConversation: (contact: NewContact) => {}, openEmojiPopup: { action: false, data: undefined }, - setOpenEmojiPopup: (action: IOpenEmojiPopup) => { }, + setOpenEmojiPopup: (action: IOpenEmojiPopup) => {}, lastMessageAction: MessageActionType.NONE, - setLastMessageAction: (action: MessageActionType) => { }, + setLastMessageAction: (action: MessageActionType) => {}, showProfileConfigurationModal: false, - setShowProfileConfigurationModal: (show: boolean) => { }, + setShowProfileConfigurationModal: (show: boolean) => {}, showPreferencesModal: false, - setShowPreferencesModal: (show: boolean) => { }, + setShowPreferencesModal: (show: boolean) => {}, showAboutModal: false, - setShowAboutModal: (show: boolean) => { }, + setShowAboutModal: (show: boolean) => {}, showAddConversationModal: false, - setShowAddConversationModal: (show: boolean) => { }, - configureProfileModal: { isAddProfileButtonActive: true }, - setConfigureProfileModal: (modal: IConfigureProfileModal) => { }, - resetConfigureProfileModal: () => { }, - resetModalStates: () => { }, + setShowAddConversationModal: (show: boolean) => {}, + configureProfileModal: { + profileOptionSelected: ProfileType.DM3_NAME, + onScreen: ProfileScreenType.NONE, + }, + setConfigureProfileModal: (modal: IConfigureProfileModal) => {}, + resetConfigureProfileModal: () => {}, + resetModalStates: () => {}, }); export const ModalContextProvider = ({ children }: { children?: any }) => { @@ -107,7 +118,7 @@ export const ModalContextProvider = ({ children }: { children?: any }) => { resetModalStates, configureProfileModal, setConfigureProfileModal, - resetConfigureProfileModal + resetConfigureProfileModal, }} > {children} diff --git a/packages/messenger-widget/src/hooks/modals/useModal.ts b/packages/messenger-widget/src/hooks/modals/useModal.ts index 4c1165a6b..1af516b7e 100644 --- a/packages/messenger-widget/src/hooks/modals/useModal.ts +++ b/packages/messenger-widget/src/hooks/modals/useModal.ts @@ -1,7 +1,11 @@ import { useState } from 'react'; import { NewContact } from '../../interfaces/utils'; import { MessageProps } from '../../interfaces/props'; -import { MessageActionType } from '../../utils/enum-type-utils'; +import { + MessageActionType, + ProfileScreenType, + ProfileType, +} from '../../utils/enum-type-utils'; export interface IOpenEmojiPopup { action: boolean; @@ -9,7 +13,8 @@ export interface IOpenEmojiPopup { } export interface IConfigureProfileModal { - isAddProfileButtonActive: boolean; + profileOptionSelected: ProfileType; + onScreen: ProfileScreenType; } export const useModal = () => { @@ -44,15 +49,18 @@ export const useModal = () => { const [showAboutModal, setShowAboutModal] = useState(false); - const [configureProfileModal, setConfigureProfileModal] = useState({ - isAddProfileButtonActive: true - }); + const [configureProfileModal, setConfigureProfileModal] = + useState({ + profileOptionSelected: ProfileType.DM3_NAME, + onScreen: ProfileScreenType.NONE, + }); const resetConfigureProfileModal = () => { setConfigureProfileModal({ - isAddProfileButtonActive: true + profileOptionSelected: ProfileType.DM3_NAME, + onScreen: ProfileScreenType.NONE, }); - } + }; const resetModalStates = () => { setLoaderContent(''); setContactToHide(undefined); @@ -68,8 +76,9 @@ export const useModal = () => { setShowAboutModal(false); setShowAddConversationModal(false); setConfigureProfileModal({ - isAddProfileButtonActive: true - }) + profileOptionSelected: ProfileType.DM3_NAME, + onScreen: ProfileScreenType.NONE, + }); }; return { @@ -92,8 +101,8 @@ export const useModal = () => { showAddConversationModal, setShowAddConversationModal, resetModalStates, - configureProfileModal, + configureProfileModal, setConfigureProfileModal, - resetConfigureProfileModal + resetConfigureProfileModal, }; }; diff --git a/packages/messenger-widget/src/utils/enum-type-utils.ts b/packages/messenger-widget/src/utils/enum-type-utils.ts index 90b10959f..933846cf8 100644 --- a/packages/messenger-widget/src/utils/enum-type-utils.ts +++ b/packages/messenger-widget/src/utils/enum-type-utils.ts @@ -33,3 +33,15 @@ export enum SiweValidityStatus { VALIDATED = 'VALIDATED', ERROR = 'ERROR', } + +export enum ProfileType { + DM3_NAME = 'dm3Name', + OWN_NAME = 'ownName', +} + +export enum ProfileScreenType { + SELECT_TYPE, + SELECT_STORAGE, + CLAIM_NAME, + NONE, +} diff --git a/packages/messenger-widget/src/utils/style-utils.ts b/packages/messenger-widget/src/utils/style-utils.ts index c25488b90..ede1c74af 100644 --- a/packages/messenger-widget/src/utils/style-utils.ts +++ b/packages/messenger-widget/src/utils/style-utils.ts @@ -52,6 +52,7 @@ export const setTheme = (theme: string | undefined | null) => { --alternate-contact-background-color: ${themeDetails.alternateContactBackgroundColor}; --menu-background-color: ${themeDetails.menuBackgroundColor}; --preferences-highlighted-color: ${themeDetails.preferencesHighlightedColor}; + --configure-profile-modal-background-color: ${themeDetails.configureProfileModalBackgroundColor}; }`, }), ); diff --git a/packages/messenger-widget/src/utils/theme-utils.ts b/packages/messenger-widget/src/utils/theme-utils.ts index 93f3c1c0f..f29497399 100644 --- a/packages/messenger-widget/src/utils/theme-utils.ts +++ b/packages/messenger-widget/src/utils/theme-utils.ts @@ -45,6 +45,7 @@ export const defaultTheme: any = { alternateContactBackgroundColor: '#5443931A', menuBackgroundColor: '#2A2B38', preferencesHighlightedColor: '#8b7ff4', + configureProfileModalBackgroundColor: '#D9D9D9', }; export const getCustomizedTheme = (themeCss: any) => { @@ -165,5 +166,8 @@ export const getCustomizedTheme = (themeCss: any) => { preferencesHighlightedColor: themeCss.preferencesHighlightedColor ?? defaultTheme.preferencesHighlightedColor, + configureProfileModalBackgroundColor: + themeCss.configureProfileModalBackgroundColor ?? + defaultTheme.configureProfileModalBackgroundColor, }; }; From 155c040808c3d185afb4edaaa226ca8240ea79a0 Mon Sep 17 00:00:00 2001 From: Bhupesh-MS Date: Fri, 9 Aug 2024 20:09:26 +0530 Subject: [PATCH 04/49] reverted back to old vscode formatter --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bd176969..9775ffb5f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,7 +5,7 @@ "editor.formatOnSave": true }, "[typescript]": { - "editor.defaultFormatter": "vscode.typescript-language-features", + "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }, "[typescriptreact]": { From ec41817b21370f607ead576aca9a36b165fad90e Mon Sep 17 00:00:00 2001 From: malteish Date: Wed, 14 Aug 2024 15:38:40 +0200 Subject: [PATCH 05/49] add metrics endpoint with mock output --- packages/delivery-service/nodemon.json | 6 ++ packages/delivery-service/package.json | 2 + packages/delivery-service/src/index.ts | 6 +- yarn.lock | 93 +++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 packages/delivery-service/nodemon.json diff --git a/packages/delivery-service/nodemon.json b/packages/delivery-service/nodemon.json new file mode 100644 index 000000000..0dcf83fc6 --- /dev/null +++ b/packages/delivery-service/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src"], + "ext": "ts", + "ignore": ["dist"], + "exec": "ts-node ./src/index.ts" +} \ No newline at end of file diff --git a/packages/delivery-service/package.json b/packages/delivery-service/package.json index 1d784e2d0..dc76cf83d 100644 --- a/packages/delivery-service/package.json +++ b/packages/delivery-service/package.json @@ -29,6 +29,7 @@ "start-inspect": "node --inspect=0.0.0.0:9229 ./dist/index.js", "test": "yarn run before:tests && jest --coverage --runInBand --transformIgnorePatterns 'node_modules/(?!(dm3-lib-\\w*)/)' ", "build": "yarn tsc ", + "dev": "nodemon --exec 'ts-node ./src/index.ts' --watch src --ext ts", "createDeliveryServiceProfile": "node --no-warnings ./cli.js", "before:tests": "docker compose -f docker-compose.test.yml up -d", "after:tests": "docker compose -f docker-compose.test.yml down" @@ -47,6 +48,7 @@ "babel-preset-env": "^1.7.0", "jest": "^29.2.2", "jest-mock-extended": "2.0.4", + "nodemon": "^3.1.4", "prettier": "^2.6.2", "superagent": "^8.0.3", "supertest": "^6.3.1", diff --git a/packages/delivery-service/src/index.ts b/packages/delivery-service/src/index.ts index 84a072f52..377e35aef 100644 --- a/packages/delivery-service/src/index.ts +++ b/packages/delivery-service/src/index.ts @@ -149,9 +149,9 @@ global.logger = winston.createLogger({ return res.status(200).send('Hello DM3'); }); - //Auth - //socketAuth - //restAuth + app.get('/metrics', (req, res) => { + return res.status(200).send({ status: 'better', message: 'ok' }); + }); app.use('/auth', Auth(db, serverSecret, web3Provider)); app.use('/profile', Profile(db, web3Provider, serverSecret)); diff --git a/yarn.lock b/yarn.lock index dd42d23fa..f60b0940d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2040,6 +2040,7 @@ __metadata: express: ^4.18.1 jest: ^29.2.2 jest-mock-extended: 2.0.4 + nodemon: ^3.1.4 prettier: ^2.6.2 redis: ^4.1.0 socket.io: ^4.5.1 @@ -12263,6 +12264,25 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.5.2": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: ~3.1.2 + braces: ~3.0.2 + fsevents: ~2.3.2 + glob-parent: ~5.1.2 + is-binary-path: ~2.1.0 + is-glob: ~4.0.1 + normalize-path: ~3.0.0 + readdirp: ~3.6.0 + dependenciesMeta: + fsevents: + optional: true + checksum: d2f29f499705dcd4f6f3bbed79a9ce2388cf530460122eed3b9c48efeab7a4e28739c6551fd15bec9245c6b9eeca7a32baa64694d64d9b6faeb74ddb8c4a413d + languageName: node + linkType: hard + "chownr@npm:^1.1.4": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -13604,6 +13624,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4": + version: 4.3.6 + resolution: "debug@npm:4.3.6" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 1630b748dea3c581295e02137a9f5cbe2c1d85fea35c1e6597a65ca2b16a6fce68cec61b299d480787ef310ba927dc8c92d3061faba0ad06c6a724672f66be7f + languageName: node + linkType: hard + "decamelize-keys@npm:^1.1.0": version: 1.1.1 resolution: "decamelize-keys@npm:1.1.1" @@ -18707,6 +18739,13 @@ __metadata: languageName: node linkType: hard +"ignore-by-default@npm:^1.0.1": + version: 1.0.1 + resolution: "ignore-by-default@npm:1.0.1" + checksum: 441509147b3615e0365e407a3c18e189f78c07af08564176c680be1fabc94b6c789cad1342ad887175d4ecd5225de86f73d376cec8e06b42fd9b429505ffcf8a + languageName: node + linkType: hard + "ignore@npm:^5.1.1": version: 5.3.1 resolution: "ignore@npm:5.3.1" @@ -24302,6 +24341,26 @@ __metadata: languageName: node linkType: hard +"nodemon@npm:^3.1.4": + version: 3.1.4 + resolution: "nodemon@npm:3.1.4" + dependencies: + chokidar: ^3.5.2 + debug: ^4 + ignore-by-default: ^1.0.1 + minimatch: ^3.1.2 + pstree.remy: ^1.1.8 + semver: ^7.5.3 + simple-update-notifier: ^2.0.0 + supports-color: ^5.5.0 + touch: ^3.1.0 + undefsafe: ^2.0.5 + bin: + nodemon: bin/nodemon.js + checksum: 3f003fc2c7bdaba559108320f188b7cb063220455e5da218ff3bf4f7468ad7059852da6e35a52b8c690cc27f6e36a433a9ad1f1bdb8096ec1ee3d930629cbeca + languageName: node + linkType: hard + "nofilter@npm:^1.0.4": version: 1.0.4 resolution: "nofilter@npm:1.0.4" @@ -26670,6 +26729,13 @@ __metadata: languageName: node linkType: hard +"pstree.remy@npm:^1.1.8": + version: 1.1.8 + resolution: "pstree.remy@npm:1.1.8" + checksum: 5cb53698d6bb34dfb278c8a26957964aecfff3e161af5fbf7cee00bbe9d8547c7aced4bd9cb193bce15fb56e9e4220fc02a5bf9c14345ffb13a36b858701ec2d + languageName: node + linkType: hard + "public-encrypt@npm:^4.0.0": version: 4.0.3 resolution: "public-encrypt@npm:4.0.3" @@ -28825,6 +28891,15 @@ __metadata: languageName: node linkType: hard +"simple-update-notifier@npm:^2.0.0": + version: 2.0.0 + resolution: "simple-update-notifier@npm:2.0.0" + dependencies: + semver: ^7.5.3 + checksum: 9ba00d38ce6a29682f64a46213834e4eb01634c2f52c813a9a7b8873ca49cdbb703696f3290f3b27dc067de6d9418b0b84bef22c3eb074acf352529b2d6c27fd + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -29969,7 +30044,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^5.3.0": +"supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" dependencies: @@ -30489,6 +30564,15 @@ __metadata: languageName: node linkType: hard +"touch@npm:^3.1.0": + version: 3.1.1 + resolution: "touch@npm:3.1.1" + bin: + nodetouch: bin/nodetouch.js + checksum: fb8c54207500eb760b6b9d77b9c5626cc027c9ad44431eed4268845f00f8c6bbfc95ce7e9da8e487f020aa921982a8bc5d8e909d0606e82686bd0a08a8e0539b + languageName: node + linkType: hard + "tough-cookie@npm:^4.0.0": version: 4.1.3 resolution: "tough-cookie@npm:4.1.3" @@ -31314,6 +31398,13 @@ __metadata: languageName: node linkType: hard +"undefsafe@npm:^2.0.5": + version: 2.0.5 + resolution: "undefsafe@npm:2.0.5" + checksum: f42ab3b5770fedd4ada175fc1b2eb775b78f609156f7c389106aafd231bfc210813ee49f54483d7191d7b76e483bc7f537b5d92d19ded27156baf57592eb02cc + languageName: node + linkType: hard + "underscore@npm:1.12.1": version: 1.12.1 resolution: "underscore@npm:1.12.1" From 6153b460a13f5dada29d41ae5f35ccd9eea66be9 Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 15 Aug 2024 10:28:28 +0200 Subject: [PATCH 06/49] add router to get metrics --- packages/delivery-service/src/index.ts | 6 ++---- packages/delivery-service/src/metrics.ts | 16 ++++++++++++++++ .../src/persistence/getDatabase.ts | 4 ++++ .../src/persistence/metrics/getMetrics.ts | 7 +++++++ .../src/persistence/metrics/index.ts | 5 +++++ 5 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 packages/delivery-service/src/metrics.ts create mode 100644 packages/delivery-service/src/persistence/metrics/getMetrics.ts create mode 100644 packages/delivery-service/src/persistence/metrics/index.ts diff --git a/packages/delivery-service/src/index.ts b/packages/delivery-service/src/index.ts index 377e35aef..1d085566b 100644 --- a/packages/delivery-service/src/index.ts +++ b/packages/delivery-service/src/index.ts @@ -29,6 +29,7 @@ import { Profile } from './profile/profile'; import RpcProxy from './rpc/rpc-proxy'; import { WebSocketManager } from './ws/WebSocketManager'; import { socketAuth } from './socketAuth'; +import Metrics from './metrics'; const app = express(); app.use(express.json({ limit: '50mb' })); @@ -149,10 +150,7 @@ global.logger = winston.createLogger({ return res.status(200).send('Hello DM3'); }); - app.get('/metrics', (req, res) => { - return res.status(200).send({ status: 'better', message: 'ok' }); - }); - + app.use('/metrics', Metrics(db)); app.use('/auth', Auth(db, serverSecret, web3Provider)); app.use('/profile', Profile(db, web3Provider, serverSecret)); app.use('/delivery', Delivery(web3Provider, db, serverSecret)); diff --git a/packages/delivery-service/src/metrics.ts b/packages/delivery-service/src/metrics.ts new file mode 100644 index 000000000..2e8b30018 --- /dev/null +++ b/packages/delivery-service/src/metrics.ts @@ -0,0 +1,16 @@ +import express from 'express'; +import { IDatabase } from './persistence/getDatabase'; + +export default (db: IDatabase) => { + const router = express.Router(); + router.get('', async (req, res) => { + //const [error, result] = await db.getMetrics(); + // if (error) { + // return res.status(500).send({ error }); + // } + const result = await db.getMetrics(); + return res.status(200).send(result); + }); + + return router; +}; diff --git a/packages/delivery-service/src/persistence/getDatabase.ts b/packages/delivery-service/src/persistence/getDatabase.ts index e9b36b144..e98064967 100644 --- a/packages/delivery-service/src/persistence/getDatabase.ts +++ b/packages/delivery-service/src/persistence/getDatabase.ts @@ -9,6 +9,7 @@ import { createClient } from 'redis'; import Account from './account'; import { getIdEnsName } from './getIdEnsName'; import Messages from './messages'; +import Metrics from './metrics'; import { syncAcknowledge } from './messages/syncAcknowledge'; import Notification from './notification'; import Otp from './otp'; @@ -90,6 +91,8 @@ export async function getDatabase( setOtp: Otp.setOtp(redis), getOtp: Otp.getOtp(redis), resetOtp: Otp.resetOtp(redis), + // Metrics + getMetrics: Metrics.getMetrics(redis), }; } @@ -155,6 +158,7 @@ export interface IDatabase extends IAccountDatabase { ensName: string, channelType: NotificationChannelType, ) => Promise; + getMetrics: () => Promise; } export type Redis = Awaited>; diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.ts new file mode 100644 index 000000000..d33095704 --- /dev/null +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.ts @@ -0,0 +1,7 @@ +import { Redis, RedisPrefix } from '../getDatabase'; + +export function getMetrics(redis: Redis): () => Promise { + return async () => { + return JSON.stringify({ yesterday: 1, today: 2 }); + }; +} diff --git a/packages/delivery-service/src/persistence/metrics/index.ts b/packages/delivery-service/src/persistence/metrics/index.ts new file mode 100644 index 000000000..0938ed5eb --- /dev/null +++ b/packages/delivery-service/src/persistence/metrics/index.ts @@ -0,0 +1,5 @@ +import { getMetrics } from './getMetrics'; + +export default { + getMetrics, +}; From 0d0eb1ee964da7b8fb52b70660f0f1ce24dce7b9 Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 15 Aug 2024 12:11:03 +0200 Subject: [PATCH 07/49] remove unused config file --- packages/delivery-service/nodemon.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 packages/delivery-service/nodemon.json diff --git a/packages/delivery-service/nodemon.json b/packages/delivery-service/nodemon.json deleted file mode 100644 index 0dcf83fc6..000000000 --- a/packages/delivery-service/nodemon.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "watch": ["src"], - "ext": "ts", - "ignore": ["dist"], - "exec": "ts-node ./src/index.ts" -} \ No newline at end of file From 40d24e7c39690bcaf6b081d8f915e65a374b053e Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 15 Aug 2024 12:11:20 +0200 Subject: [PATCH 08/49] introduce metrics types --- packages/delivery-service/src/metrics.ts | 16 ++++--- .../src/persistence/getDatabase.ts | 4 +- .../src/persistence/metrics/getMetrics.ts | 48 ++++++++++++++++++- .../src/persistence/metrics/index.ts | 5 ++ .../src/persistence/metrics/metricTypes.ts | 9 ++++ .../persistence/metrics/stringifyMetrics.ts | 29 +++++++++++ 6 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 packages/delivery-service/src/persistence/metrics/metricTypes.ts create mode 100644 packages/delivery-service/src/persistence/metrics/stringifyMetrics.ts diff --git a/packages/delivery-service/src/metrics.ts b/packages/delivery-service/src/metrics.ts index 2e8b30018..0383bcdde 100644 --- a/packages/delivery-service/src/metrics.ts +++ b/packages/delivery-service/src/metrics.ts @@ -1,15 +1,19 @@ import express from 'express'; import { IDatabase } from './persistence/getDatabase'; +import { stringifyMetrics } from './persistence/metrics/stringifyMetrics'; +/** + * The metrics endpoint returns the metrics of the delivery service. + * These can include the number of messages received, the cumulative size of messages received, etc., + * over a certain period of time. + * @param db The database object. + */ export default (db: IDatabase) => { const router = express.Router(); router.get('', async (req, res) => { - //const [error, result] = await db.getMetrics(); - // if (error) { - // return res.status(500).send({ error }); - // } - const result = await db.getMetrics(); - return res.status(200).send(result); + const metrics = await db.getMetrics(); + + return res.status(200).send(stringifyMetrics(metrics)); }); return router; diff --git a/packages/delivery-service/src/persistence/getDatabase.ts b/packages/delivery-service/src/persistence/getDatabase.ts index e98064967..889bce348 100644 --- a/packages/delivery-service/src/persistence/getDatabase.ts +++ b/packages/delivery-service/src/persistence/getDatabase.ts @@ -10,6 +10,7 @@ import Account from './account'; import { getIdEnsName } from './getIdEnsName'; import Messages from './messages'; import Metrics from './metrics'; +import type { MetricsMap, IntervalMetric } from './metrics'; import { syncAcknowledge } from './messages/syncAcknowledge'; import Notification from './notification'; import Otp from './otp'; @@ -23,6 +24,7 @@ export enum RedisPrefix { NotificationChannel = 'notificationChannel:', GlobalNotification = 'globalNotification:', Otp = 'otp:', + Metrics = 'metrics:', } export async function getRedisClient() { @@ -158,7 +160,7 @@ export interface IDatabase extends IAccountDatabase { ensName: string, channelType: NotificationChannelType, ) => Promise; - getMetrics: () => Promise; + getMetrics: () => Promise; } export type Redis = Awaited>; diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.ts index d33095704..6153fad37 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.ts @@ -1,7 +1,51 @@ import { Redis, RedisPrefix } from '../getDatabase'; +import { MetricsMap, IntervalMetric } from './metricTypes'; -export function getMetrics(redis: Redis): () => Promise { +// https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map + +// @ts-ignore +function replacer(key, value) { + if (value instanceof Map) { + return { + dataType: 'Map', + value: Array.from(value.entries()), // or with spread: value: [...value] + }; + } else { + return value; + } +} + +// function reviver(key, value) { +// if (typeof value === 'object' && value !== null) { +// if (value.dataType === 'Map') { +// return new Map(value.value); +// } +// } +// return value; +// } + +export function getMetrics(redis: Redis): () => Promise { return async () => { - return JSON.stringify({ yesterday: 1, today: 2 }); + const metrics: Map = new Map< + Date, + IntervalMetric + >(); + metrics.set(new Date('2024-08-09'), { + messageCount: 294, + messageSizeBytes: 29424, + notificationCount: 29, + }); + metrics.set(new Date('2024-08-10'), { + messageCount: 20, + messageSizeBytes: 294, + notificationCount: 2, + }); + metrics.set(new Date('2024-08-11'), { + messageCount: 24, + messageSizeBytes: 294, + notificationCount: 9, + }); + console.log('metrics', metrics); + return metrics; }; } diff --git a/packages/delivery-service/src/persistence/metrics/index.ts b/packages/delivery-service/src/persistence/metrics/index.ts index 0938ed5eb..98a5c80f4 100644 --- a/packages/delivery-service/src/persistence/metrics/index.ts +++ b/packages/delivery-service/src/persistence/metrics/index.ts @@ -1,5 +1,10 @@ import { getMetrics } from './getMetrics'; +import { stringifyMetrics } from './stringifyMetrics'; +import type { MetricsMap, IntervalMetric } from './metricTypes'; export default { getMetrics, + stringifyMetrics, }; + +export type { MetricsMap, IntervalMetric }; diff --git a/packages/delivery-service/src/persistence/metrics/metricTypes.ts b/packages/delivery-service/src/persistence/metrics/metricTypes.ts new file mode 100644 index 000000000..44251bb96 --- /dev/null +++ b/packages/delivery-service/src/persistence/metrics/metricTypes.ts @@ -0,0 +1,9 @@ +type IntervalMetric = { + messageCount: number; + messageSizeBytes: number; + notificationCount: number; +}; + +type MetricsMap = Map; + +export type { MetricsMap, IntervalMetric }; diff --git a/packages/delivery-service/src/persistence/metrics/stringifyMetrics.ts b/packages/delivery-service/src/persistence/metrics/stringifyMetrics.ts new file mode 100644 index 000000000..43d015f2c --- /dev/null +++ b/packages/delivery-service/src/persistence/metrics/stringifyMetrics.ts @@ -0,0 +1,29 @@ +import { Redis, RedisPrefix } from '../getDatabase'; +import { MetricsMap, IntervalMetric } from './metricTypes'; + +// https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map + +// @ts-ignore +function replacer(key, value) { + if (value instanceof Map) { + return { + dataType: 'Map', + value: Array.from(value.entries()), // or with spread: value: [...value] + }; + } else { + return value; + } +} + +// function reviver(key, value) { +// if (typeof value === 'object' && value !== null) { +// if (value.dataType === 'Map') { +// return new Map(value.value); +// } +// } +// return value; +// } + +export function stringifyMetrics(metrics: MetricsMap) { + return JSON.stringify(metrics, replacer); +} From fb7c9a0062beb236665e7df0a5eb76fab3e4e269 Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 15 Aug 2024 15:10:36 +0200 Subject: [PATCH 09/49] start building the metrics setter --- .../delivery-service/src/persistence/getDatabase.ts | 6 ++++-- .../delivery-service/src/persistence/metrics/index.ts | 6 ++++-- .../src/persistence/metrics/setMetrics.ts | 11 +++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 packages/delivery-service/src/persistence/metrics/setMetrics.ts diff --git a/packages/delivery-service/src/persistence/getDatabase.ts b/packages/delivery-service/src/persistence/getDatabase.ts index 889bce348..8e839ab33 100644 --- a/packages/delivery-service/src/persistence/getDatabase.ts +++ b/packages/delivery-service/src/persistence/getDatabase.ts @@ -9,9 +9,9 @@ import { createClient } from 'redis'; import Account from './account'; import { getIdEnsName } from './getIdEnsName'; import Messages from './messages'; -import Metrics from './metrics'; -import type { MetricsMap, IntervalMetric } from './metrics'; import { syncAcknowledge } from './messages/syncAcknowledge'; +import type { MetricsMap } from './metrics'; +import Metrics from './metrics'; import Notification from './notification'; import Otp from './otp'; @@ -95,6 +95,7 @@ export async function getDatabase( resetOtp: Otp.resetOtp(redis), // Metrics getMetrics: Metrics.getMetrics(redis), + setMetrics: Metrics.setMetrics(redis), }; } @@ -161,6 +162,7 @@ export interface IDatabase extends IAccountDatabase { channelType: NotificationChannelType, ) => Promise; getMetrics: () => Promise; + setMetrics: (metrics: MetricsMap) => Promise; } export type Redis = Awaited>; diff --git a/packages/delivery-service/src/persistence/metrics/index.ts b/packages/delivery-service/src/persistence/metrics/index.ts index 98a5c80f4..78e2329ac 100644 --- a/packages/delivery-service/src/persistence/metrics/index.ts +++ b/packages/delivery-service/src/persistence/metrics/index.ts @@ -1,10 +1,12 @@ import { getMetrics } from './getMetrics'; +import type { IntervalMetric, MetricsMap } from './metricTypes'; +import { setMetrics } from './setMetrics'; import { stringifyMetrics } from './stringifyMetrics'; -import type { MetricsMap, IntervalMetric } from './metricTypes'; export default { getMetrics, + setMetrics, stringifyMetrics, }; -export type { MetricsMap, IntervalMetric }; +export type { IntervalMetric, MetricsMap }; diff --git a/packages/delivery-service/src/persistence/metrics/setMetrics.ts b/packages/delivery-service/src/persistence/metrics/setMetrics.ts new file mode 100644 index 000000000..3393d6a29 --- /dev/null +++ b/packages/delivery-service/src/persistence/metrics/setMetrics.ts @@ -0,0 +1,11 @@ +import { Redis, RedisPrefix } from '../getDatabase'; +import { MetricsMap, IntervalMetric } from './metricTypes'; +import { stringifyMetrics } from './stringifyMetrics'; + +export function setMetrics(redis: Redis) { + return async (metrics: MetricsMap) => { + console.log('writing metrics to database:', metrics); + const metricsString = stringifyMetrics(metrics); + await redis.set(RedisPrefix.Metrics, metricsString); + }; +} From d3ee8a69a90ad2081a7763dc24efe46fd6ab8b44 Mon Sep 17 00:00:00 2001 From: Bhupesh-MS Date: Mon, 26 Aug 2024 21:05:30 +0530 Subject: [PATCH 10/49] added network dialog --- .../src/adapters/offchainResolverApi.ts | 29 + .../src/assets/images/active-node.svg | 9 + .../src/assets/images/inactive-node.svg | 9 + .../src/assets/images/update.svg | 9 + .../ConfigureProfile/ConfigureProfile.css | 20 +- .../ConfigureProfile/MobileView.tsx | 115 ++- .../ConfigureProfile/NormalView.tsx | 100 ++- .../context/ConfigureProfileContext.tsx | 19 +- .../src/components/DM3/DM3.tsx | 39 +- .../src/components/Preferences/MobileView.tsx | 29 +- .../Preferences/Network/Network.css | 149 ++++ .../Preferences/Network/Network.tsx | 195 ++++- .../src/components/Preferences/NormalView.tsx | 28 +- .../src/components/Preferences/bl.tsx | 2 +- .../src/context/DM3UserProfileContext.tsx | 86 +++ .../src/context/ModalContext.tsx | 24 + .../src/hooks/modals/useModal.ts | 11 + .../hooks/userProfile/useDm3UserProfile.tsx | 688 ++++++++++++++++++ packages/messenger-widget/src/utils/names.ts | 109 +++ .../offchain-resolver/src/http/profile.ts | 36 +- 20 files changed, 1614 insertions(+), 92 deletions(-) create mode 100644 packages/messenger-widget/src/assets/images/active-node.svg create mode 100644 packages/messenger-widget/src/assets/images/inactive-node.svg create mode 100644 packages/messenger-widget/src/assets/images/update.svg create mode 100644 packages/messenger-widget/src/components/Preferences/Network/Network.css create mode 100644 packages/messenger-widget/src/context/DM3UserProfileContext.tsx create mode 100644 packages/messenger-widget/src/hooks/userProfile/useDm3UserProfile.tsx create mode 100644 packages/messenger-widget/src/utils/names.ts diff --git a/packages/messenger-widget/src/adapters/offchainResolverApi.ts b/packages/messenger-widget/src/adapters/offchainResolverApi.ts index e79ccea58..5048844d2 100644 --- a/packages/messenger-widget/src/adapters/offchainResolverApi.ts +++ b/packages/messenger-widget/src/adapters/offchainResolverApi.ts @@ -76,6 +76,35 @@ export async function claimAddress( } } +/** + * updates profile with new delivery service nodes added + * @param address The ethereum address + * @param offchainResolverUrl The offchain resolver endpoint url + * @param subdomain The addr subdomain of the client .iE addr.dm3.eth + * @param signedUserProfile The signed dm3 user profile + */ +export async function updateProfile( + address: string, + offchainResolverUrl: string, + addrSubdomainDomain: string, + signedUserProfile: SignedUserProfile, +) { + try { + const url = `${offchainResolverUrl}/profile/address`; + const data = { + signedUserProfile, + address, + subdomain: addrSubdomainDomain, + }; + + const { status } = await axios.post(url, data); + return status === 200; + } catch (err) { + console.log('update profile failed'); + return false; + } +} + /** * returns the linked ENS name for an eth address * @param address The ethereum address diff --git a/packages/messenger-widget/src/assets/images/active-node.svg b/packages/messenger-widget/src/assets/images/active-node.svg new file mode 100644 index 000000000..dc842380f --- /dev/null +++ b/packages/messenger-widget/src/assets/images/active-node.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/messenger-widget/src/assets/images/inactive-node.svg b/packages/messenger-widget/src/assets/images/inactive-node.svg new file mode 100644 index 000000000..1258893c3 --- /dev/null +++ b/packages/messenger-widget/src/assets/images/inactive-node.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/messenger-widget/src/assets/images/update.svg b/packages/messenger-widget/src/assets/images/update.svg new file mode 100644 index 000000000..285d4deb4 --- /dev/null +++ b/packages/messenger-widget/src/assets/images/update.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css b/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css index 3389eb3f9..b143a2f18 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css +++ b/packages/messenger-widget/src/components/ConfigureProfile/ConfigureProfile.css @@ -158,6 +158,12 @@ input::placeholder { padding: 1rem; } +.update-btn { + margin-left: 1.2rem !important; + font-size: 12px; + border-radius: 6px; +} + /* Customized CSS for radio button */ .radio { input[type="radio"] { @@ -305,5 +311,17 @@ input::placeholder { .ens-components-container{ padding: 0.5rem; - } + } + + .update-btn { + margin-top: 1rem; + margin-left: 0rem !important; + font-size: 12px; + border-radius: 6px; + width: fit-content; + } + + .del-icon{ + height: 1rem; + } } diff --git a/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx index 909856d81..6eed98020 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx @@ -17,6 +17,8 @@ import deleteIcon from '../../assets/images/delete.svg'; import { CloudStorage } from './CloudStorage'; import { OwnStorage } from './OwnStorage'; import { ClaimOwnName } from './ClaimOwnName'; +import { DM3UserProfileContext } from '../../context/DM3UserProfileContext'; +import updateIcon from '../../assets/images/update.svg'; export function MobileView() { const connectedChainId = useChainId(); @@ -41,6 +43,13 @@ export function MobileView() { const { dm3Configuration } = useContext(DM3ConfigurationContext); + const { + nodes, + updateProfileWithTransaction, + isDm3NameConfigured, + isEnsNameConfigured, + } = useContext(DM3UserProfileContext); + const ensDomainName = (existingDm3Name && (existingEnsName?.endsWith('.gno') || @@ -49,6 +58,26 @@ export function MobileView() { : 'ENS')) ?? null; + const isProfileNotUpdatedForDm3Name = (): boolean => { + return nodes.filter( + (n) => + isDm3NameConfigured && + (!n.dm3Name?.isActive || !n.opName?.isActive), + ).length + ? true + : false; + }; + + const isProfileNotUpdatedForEnsName = (): boolean => { + return nodes.filter( + (n) => + isEnsNameConfigured && + (!n.ensName?.isActive || !n.gnosisName?.isActive), + ).length + ? true + : false; + }; + // handles ENS name and address useEffect(() => { getEnsName( @@ -112,10 +141,12 @@ export function MobileView() { {/* Existing DM3 name */} {existingDm3Name && ( -
+

DM3 Name @@ -134,18 +165,40 @@ export function MobileView() {

{existingDm3Name} + + {/* Delete icon */} + remove setShowDeleteConfirmation(true)} + />

- {/* Delete icon */} - remove setShowDeleteConfirmation(true)} - /> + + {isProfileNotUpdatedForDm3Name() && ( + + )}
)} @@ -153,8 +206,10 @@ export function MobileView() { {ensDomainName && existingEnsName && (

{ensDomainName} Name @@ -175,18 +230,32 @@ export function MobileView() {

{existingEnsName}

- {/* Delete icon */} - remove setShowDeleteConfirmation(true)} - /> + + {isProfileNotUpdatedForEnsName() && ( + + )}
)} diff --git a/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx index b505969f0..4fdfdba8b 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/NormalView.tsx @@ -17,6 +17,8 @@ import DeleteDM3Name from '../DeleteDM3Name/DeleteDM3Name'; import { CloudStorage } from './CloudStorage'; import { OwnStorage } from './OwnStorage'; import { ClaimOwnName } from './ClaimOwnName'; +import { DM3UserProfileContext } from '../../context/DM3UserProfileContext'; +import updateIcon from '../../assets/images/update.svg'; export function NormalView() { const connectedChainId = useChainId(); @@ -41,6 +43,33 @@ export function NormalView() { const { dm3Configuration } = useContext(DM3ConfigurationContext); + const { + nodes, + updateProfileWithTransaction, + isDm3NameConfigured, + isEnsNameConfigured, + } = useContext(DM3UserProfileContext); + + const isProfileNotUpdatedForDm3Name = (): boolean => { + return nodes.filter( + (n) => + isDm3NameConfigured && + (!n.dm3Name?.isActive || !n.opName?.isActive), + ).length + ? true + : false; + }; + + const isProfileNotUpdatedForEnsName = (): boolean => { + return nodes.filter( + (n) => + isEnsNameConfigured && + (!n.ensName?.isActive || !n.gnosisName?.isActive), + ).length + ? true + : false; + }; + const ensDomainName = (existingDm3Name && (existingEnsName?.endsWith('.gno') || @@ -116,8 +145,12 @@ export function NormalView() { {existingDm3Name && (

DM3 Name @@ -136,18 +169,42 @@ export function NormalView() {

{existingDm3Name}

- {/* Show delete */} + {/* Delete button */} remove setShowDeleteConfirmation(true)} /> + + {isProfileNotUpdatedForDm3Name() && ( + + )}
)} @@ -155,8 +212,12 @@ export function NormalView() { {ensDomainName && existingEnsName && (

{ensDomainName} Name @@ -176,11 +237,32 @@ export function NormalView() {

{existingEnsName}

+ {isProfileNotUpdatedForEnsName() && ( + + )}
)} diff --git a/packages/messenger-widget/src/components/ConfigureProfile/context/ConfigureProfileContext.tsx b/packages/messenger-widget/src/components/ConfigureProfile/context/ConfigureProfileContext.tsx index 8bc5127a1..ede84923e 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/context/ConfigureProfileContext.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/context/ConfigureProfileContext.tsx @@ -1,6 +1,8 @@ -import React, { useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { NAME_TYPE } from '../chain/common'; import { dm3NamingServices, namingServices } from '../bl'; +import { AuthContext } from '../../../context/AuthContext'; +import { DM3ConfigurationContext } from '../../../context/DM3ConfigurationContext'; export interface ConfigureProfileContextType { existingEnsName: string | null; @@ -50,6 +52,21 @@ export const ConfigureProfileContextProvider = (props: { children?: any }) => { namingServices[0].name, ); + const { displayName } = useContext(AuthContext); + const { dm3Configuration } = useContext(DM3ConfigurationContext); + + useEffect(() => { + setExistingEnsName( + displayName + ? !displayName.endsWith(dm3Configuration.addressEnsSubdomain) && + !displayName.endsWith(dm3Configuration.userEnsSubdomain) && + !displayName.endsWith('.op.dm3.eth') + ? displayName + : null + : null, + ); + }, []); + return ( - - - - {!isProfileReady ? ( - props.config.siwe ? ( - + + + + + {!isProfileReady ? ( + props.config.siwe ? ( + + ) : ( + + ) ) : ( - - ) - ) : ( - -
- -
-
- )} -
-
+ +
+ +
+
+ )} +
+
+
); } diff --git a/packages/messenger-widget/src/components/Preferences/MobileView.tsx b/packages/messenger-widget/src/components/Preferences/MobileView.tsx index 558dd64aa..6686d16a6 100644 --- a/packages/messenger-widget/src/components/Preferences/MobileView.tsx +++ b/packages/messenger-widget/src/components/Preferences/MobileView.tsx @@ -1,6 +1,6 @@ import './Preferences.css'; import { preferencesItems } from './bl'; -import { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect } from 'react'; import closeIcon from '../../assets/images/cross.svg'; import { closeConfigurationModal } from '../ConfigureProfile/bl'; import { ModalContext } from '../../context/ModalContext'; @@ -11,9 +11,11 @@ export function MobileView() { showProfileConfigurationModal, setShowProfileConfigurationModal, resetConfigureProfileModal, + preferencesOptionSelected, + setPreferencesOptionSelected, } = useContext(ModalContext); - const [optionChoosen, setOptionChoosen] = useState(null); + // const [optionChoosen, setOptionChoosen] = useState(null); /** * Opens DM3 profile configuration by default if user clicked @@ -21,16 +23,19 @@ export function MobileView() { */ useEffect(() => { if (showProfileConfigurationModal) { - setOptionChoosen(preferencesItems[1]); + setPreferencesOptionSelected(preferencesItems[1]); } }, []); // reset states of configure profile modal if any other component is loaded useEffect(() => { - if (optionChoosen && optionChoosen.name !== 'dm3 Profile') { + if ( + preferencesOptionSelected && + preferencesOptionSelected.name !== 'dm3 Profile' + ) { resetConfigureProfileModal(); } - }, [optionChoosen]); + }, [preferencesOptionSelected]); return (
@@ -51,15 +56,17 @@ export function MobileView() {
- setOptionChoosen(item) + setPreferencesOptionSelected( + item, + ) } > {item.icon} @@ -84,9 +91,9 @@ export function MobileView() {
- {optionChoosen && - optionChoosen.isEnabled && - optionChoosen.component} + {preferencesOptionSelected && + preferencesOptionSelected.isEnabled && + preferencesOptionSelected.component}
diff --git a/packages/messenger-widget/src/components/Preferences/Network/Network.css b/packages/messenger-widget/src/components/Preferences/Network/Network.css new file mode 100644 index 000000000..93e201d19 --- /dev/null +++ b/packages/messenger-widget/src/components/Preferences/Network/Network.css @@ -0,0 +1,149 @@ +.node-icon-active { + fill: var(--text-primary-color) !important; +} + +.network { + padding: 2rem; +} + +.node-name{ + min-width: 10rem; +} + +.network-text{ + font-size: 1rem; +} + +.node-btn { + padding: 5px 30px 5px 30px; + box-shadow: 0px 0px 44px 0px var(--button-shadow); + width: fit-content; + font-weight: 400; + font-size: 12px; + border-radius: 4px; + line-height: 24px; +} + +.add-node-btn-disabled { + background: var(--normal-btn-inactive); + color: var(--disabled-btn-text); + border: none; +} + +.add-node-btn-active { + background-color: var(--normal-btn-hover); + border: var(--normal-btn-border); + color: var(--text-primary-color); +} + +.node-name-input-field { + line-height: 24px; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 400; + border: 1px solid var(--input-field-border-color); + padding: 0.3rem 1rem 0.3rem 1rem; + color: var(--input-field-text-color); + background: var(--input-field-background-color); + width: 100%; +} + +.add-node-name-container{ + border: 1px solid var(--text-primary-color); + border-radius: 5px; +} + +.node-name-inner-container{ + padding: 2rem 3rem 1rem 3rem; + align-items: center; + justify-content: space-between; +} + +.node-name-form{ + width: fit-content; + min-width: 60%; +} + +.add-name-btn-container{ + margin-bottom: 1rem; + margin-right: 3rem; +} + +.add-name-error{ + font-size: 0.8rem; + line-height: 14px; + font-style: italic; + color: var(--error-text); + margin-left: 1rem; + display: flex; + justify-content: center; +} + +.node-icon { + height: 1.1rem; +} + +.update-profile-box{ + font-size: 13px; + display: block; + text-align: center; + background-color: var(--configure-profile-modal-background-color); + color: #1F2029; + font-weight: 500; + padding: 0.7rem; + border-radius: 3px; + width: 80%; +} + +@media only screen and (min-width: 800px) and (max-width: 1150px) { + .add-name-error { + font-size: 0.7rem !important; + } +} + +@media only screen and (max-width: 800px) { + .network { + padding: 1rem; + } + + .network-text{ + font-size: 0.75rem; + } + + .node-name-input-field { + padding: 0.2rem 0.5rem 0.2rem 0.5rem; + width: 100%; + font-size: 12px; + } + + .node-name-input-field::placeholder{ + font-size: 10px !important; + } + + .add-name-error { + margin-left: 0px !important; + height: 14px; + font-size: 0.6rem !important; + } + + .node-name-inner-container{ + display: flex; + flex-direction: column; + padding: 1rem 0rem 0rem 1rem; + width: 100%; + align-items: flex-start; + } + + .node-name-form{ + width: 90%; + } + + .add-name-btn-container{ + margin-right: 2rem; + } + + .update-profile-box{ + margin-top: 1rem; + } + +} \ No newline at end of file diff --git a/packages/messenger-widget/src/components/Preferences/Network/Network.tsx b/packages/messenger-widget/src/components/Preferences/Network/Network.tsx index c71cad5c2..38690c9de 100644 --- a/packages/messenger-widget/src/components/Preferences/Network/Network.tsx +++ b/packages/messenger-widget/src/components/Preferences/Network/Network.tsx @@ -1,15 +1,202 @@ +import './Network.css'; import { Heading } from '../Heading/Heading'; +import deleteIcon from '../../../assets/images/delete.svg'; +import activeNodeIcon from '../../../assets/images/active-node.svg'; +import inactiveNodeIcon from '../../../assets/images/inactive-node.svg'; +import { useContext, useEffect } from 'react'; +import { DM3UserProfileContext } from '../../../context/DM3UserProfileContext'; +import { ModalContext } from '../../../context/ModalContext'; +import { preferencesItems } from '../bl'; export function Network() { const heading = 'Network'; - const description = - 'Prevent spam from being sent to you by setting rules ' + - 'that senders must fulfill in order for messages to be accepted.'; + const description = 'Define how you will be connected with the dm3 network'; + + const { + error, + nodes, + deleteNode, + isModalOpenToAddNode, + setIsModalOpenToAddNode, + nodeName, + addNode, + handleNodeNameChange, + changeOrder, + isDm3NameConfigured, + isEnsNameConfigured, + } = useContext(DM3UserProfileContext); + + const { setPreferencesOptionSelected } = useContext(ModalContext); + + // input field modal should remain close on initial load of network screen + useEffect(() => { + setIsModalOpenToAddNode(false); + }, []); return (
-
+
+
+ Connected to delivery service nodes: +
+ +
+
+ {nodes.map((data, index) => { + return ( +
+
+ {index + 1}. + {data.dsName} +
+
+ {/* Delete node option is shown only when more than 1 nodes exists */} + {nodes.length > 1 && ( + remove { + deleteNode(index); + }} + /> + )} + + {/* Change order option is shown only when more than 1 nodes exists */} + {nodes.length > 1 && ( + node { + if (index) { + changeOrder(index); + } + }} + /> + )} +
+
+ ); + })} +
+ {nodes.filter( + (n) => + (isDm3NameConfigured && + (!n.dm3Name?.isActive || + !n.opName?.isActive)) || + (isEnsNameConfigured && + (!n.ensName?.isActive || + !n.gnosisName?.isActive)), + ).length >= 1 && ( +
+ +
+ {' '} + Your profile configuration needs to be + updated!{' '} +
+ + setPreferencesOptionSelected( + preferencesItems[1], + ) + } + > + Update your profiles now! + +
+
+ )} +
+ + {/* Button to add new Node */} +
+ +
+ + {/* Modal to add new NODE */} + {isModalOpenToAddNode && ( +
+
+ Add dm3 delivery service node +
+ +
+

+ DM3 delivery service node’s name: +

+
{ + e.preventDefault(); + addNode(); + }} + > + , + ) => handleNodeNameChange(e)} + /> +
+
+ + {/* Validation error */} +
+ {error ?? ''} +
+ + {/* Button to submit node name */} +
+ +
+
+ )} +
); } diff --git a/packages/messenger-widget/src/components/Preferences/NormalView.tsx b/packages/messenger-widget/src/components/Preferences/NormalView.tsx index decf6e4d2..726d68f0f 100644 --- a/packages/messenger-widget/src/components/Preferences/NormalView.tsx +++ b/packages/messenger-widget/src/components/Preferences/NormalView.tsx @@ -12,9 +12,11 @@ export function NormalView() { showProfileConfigurationModal, setShowProfileConfigurationModal, resetConfigureProfileModal, + preferencesOptionSelected, + setPreferencesOptionSelected, } = useContext(ModalContext); - const [optionChoosen, setOptionChoosen] = useState(null); + // const [optionChoosen, setOptionChoosen] = useState(null); /** * Opens DM3 profile configuration by default if user clicked @@ -22,16 +24,19 @@ export function NormalView() { */ useEffect(() => { if (showProfileConfigurationModal) { - setOptionChoosen(preferencesItems[1]); + setPreferencesOptionSelected(preferencesItems[1]); } }, []); // reset states of configure profile modal if any other component is loaded useEffect(() => { - if (optionChoosen && optionChoosen.name !== 'dm3 Profile') { + if ( + preferencesOptionSelected && + preferencesOptionSelected.name !== 'dm3 Profile' + ) { resetConfigureProfileModal(); } - }, [optionChoosen]); + }, [preferencesOptionSelected]); return (
@@ -53,7 +58,7 @@ export function NormalView() { - setOptionChoosen(item) + setPreferencesOptionSelected( + item, + ) } > {item.icon} @@ -99,8 +106,9 @@ export function NormalView() {
- {optionChoosen && optionChoosen.isEnabled ? ( - optionChoosen.component + {preferencesOptionSelected && + preferencesOptionSelected.isEnabled ? ( + preferencesOptionSelected.component ) : (
, - isEnabled: false, + isEnabled: true, }, { icon: ( diff --git a/packages/messenger-widget/src/context/DM3UserProfileContext.tsx b/packages/messenger-widget/src/context/DM3UserProfileContext.tsx new file mode 100644 index 000000000..8991eb75f --- /dev/null +++ b/packages/messenger-widget/src/context/DM3UserProfileContext.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { + INodeDetails, + useDm3UserProfile, +} from '../hooks/userProfile/useDm3UserProfile'; + +export type DM3UserProfileContextType = { + initialize: () => void; + updateProfileWithTransaction: (existingName: string) => void; + addNode: () => void; + deleteNode: (id: number) => void; + changeOrder: (index: number) => void; + nodes: INodeDetails[]; + isModalOpenToAddNode: boolean; + setIsModalOpenToAddNode: (action: boolean) => void; + error: string | null; + setError: (msg: string) => void; + nodeName: string; + handleNodeNameChange: (e: React.ChangeEvent) => void; + isDm3NameConfigured: boolean; + isEnsNameConfigured: boolean; +}; + +export const DM3UserProfileContext = + React.createContext({ + initialize: () => {}, + updateProfileWithTransaction: (existingName: string) => {}, + addNode: () => {}, + deleteNode: (id: number) => {}, + changeOrder: (index: number) => {}, + nodes: [], + isModalOpenToAddNode: false, + setIsModalOpenToAddNode: (action: boolean) => {}, + error: null, + setError: (msg: string) => {}, + nodeName: '', + handleNodeNameChange: (e: React.ChangeEvent) => {}, + isDm3NameConfigured: false, + isEnsNameConfigured: false, + }); + +export const DM3UserProfileContextProvider = ({ + children, +}: { + children?: any; +}) => { + const { + initialize, + addNode, + updateProfileWithTransaction, + deleteNode, + changeOrder, + nodes, + isModalOpenToAddNode, + setIsModalOpenToAddNode, + error, + setError, + nodeName, + handleNodeNameChange, + isDm3NameConfigured, + isEnsNameConfigured, + } = useDm3UserProfile(); + + return ( + + {children} + + ); +}; diff --git a/packages/messenger-widget/src/context/ModalContext.tsx b/packages/messenger-widget/src/context/ModalContext.tsx index 77e1bf7d6..57b143007 100644 --- a/packages/messenger-widget/src/context/ModalContext.tsx +++ b/packages/messenger-widget/src/context/ModalContext.tsx @@ -10,6 +10,7 @@ import { IOpenEmojiPopup, useModal, } from '../hooks/modals/useModal'; +import { preferencesItems } from '../components/Preferences/bl'; export type ModalContextType = { loaderContent: string; @@ -34,6 +35,18 @@ export type ModalContextType = { setConfigureProfileModal: (modal: IConfigureProfileModal) => void; resetConfigureProfileModal: () => void; resetModalStates: () => void; + preferencesOptionSelected: { + icon: JSX.Element; + name: string; + component: JSX.Element; + isEnabled: boolean; + }; + setPreferencesOptionSelected: (item: { + icon: JSX.Element; + name: string; + component: JSX.Element; + isEnabled: boolean; + }) => void; }; export const ModalContext = React.createContext({ @@ -66,6 +79,13 @@ export const ModalContext = React.createContext({ setConfigureProfileModal: (modal: IConfigureProfileModal) => {}, resetConfigureProfileModal: () => {}, resetModalStates: () => {}, + preferencesOptionSelected: preferencesItems[1], + setPreferencesOptionSelected: (item: { + icon: JSX.Element; + name: string; + component: JSX.Element; + isEnabled: boolean; + }) => {}, }); export const ModalContextProvider = ({ children }: { children?: any }) => { @@ -91,6 +111,8 @@ export const ModalContextProvider = ({ children }: { children?: any }) => { configureProfileModal, setConfigureProfileModal, resetConfigureProfileModal, + preferencesOptionSelected, + setPreferencesOptionSelected, resetModalStates, } = useModal(); @@ -119,6 +141,8 @@ export const ModalContextProvider = ({ children }: { children?: any }) => { configureProfileModal, setConfigureProfileModal, resetConfigureProfileModal, + preferencesOptionSelected, + setPreferencesOptionSelected, }} > {children} diff --git a/packages/messenger-widget/src/hooks/modals/useModal.ts b/packages/messenger-widget/src/hooks/modals/useModal.ts index 1af516b7e..0274e715e 100644 --- a/packages/messenger-widget/src/hooks/modals/useModal.ts +++ b/packages/messenger-widget/src/hooks/modals/useModal.ts @@ -6,6 +6,7 @@ import { ProfileScreenType, ProfileType, } from '../../utils/enum-type-utils'; +import { preferencesItems } from '../../components/Preferences/bl'; export interface IOpenEmojiPopup { action: boolean; @@ -55,6 +56,13 @@ export const useModal = () => { onScreen: ProfileScreenType.NONE, }); + const [preferencesOptionSelected, setPreferencesOptionSelected] = useState<{ + icon: JSX.Element; + name: string; + component: JSX.Element; + isEnabled: boolean; + }>(preferencesItems[1]); + const resetConfigureProfileModal = () => { setConfigureProfileModal({ profileOptionSelected: ProfileType.DM3_NAME, @@ -79,6 +87,7 @@ export const useModal = () => { profileOptionSelected: ProfileType.DM3_NAME, onScreen: ProfileScreenType.NONE, }); + setPreferencesOptionSelected(preferencesItems[1]); }; return { @@ -104,5 +113,7 @@ export const useModal = () => { configureProfileModal, setConfigureProfileModal, resetConfigureProfileModal, + preferencesOptionSelected, + setPreferencesOptionSelected, }; }; diff --git a/packages/messenger-widget/src/hooks/userProfile/useDm3UserProfile.tsx b/packages/messenger-widget/src/hooks/userProfile/useDm3UserProfile.tsx new file mode 100644 index 000000000..5bfa8e81d --- /dev/null +++ b/packages/messenger-widget/src/hooks/userProfile/useDm3UserProfile.tsx @@ -0,0 +1,688 @@ +import { useContext, useEffect, useState } from 'react'; +import { AuthContext } from '../../context/AuthContext'; +import { isValidName } from 'ethers/lib/utils'; +import { updateProfile } from './../../adapters/offchainResolverApi'; +import { DM3ConfigurationContext } from '../../context/DM3ConfigurationContext'; +import { ethers } from 'ethers'; +import { Account, getUserProfile, UserProfile } from '@dm3-org/dm3-lib-profile'; +import { useMainnetProvider } from '../mainnetprovider/useMainnetProvider'; +import { + fetchExistingDM3Name, + fetchExistingEnsName, + fetchExistingGnosisName, + fetchExistingOpName, +} from '../../utils/names'; +import { publishProfile } from '../../components/ConfigureProfile/dm3Names/optimismName/tx/publishProfile'; +import { registerOpName } from '../../components/ConfigureProfile/dm3Names/optimismName/tx/registerOpName'; +import { ModalContext } from '../../context/ModalContext'; +import { closeLoader, startLoader } from '../../components/Loader/Loader'; +import { submitGenomeNameTransaction } from '../../components/ConfigureProfile/chain/genome/bl'; +import { submitEnsNameTransaction } from '../../components/ConfigureProfile/chain/ens/bl'; +import { useChainId, useSwitchNetwork } from 'wagmi'; +import { + DM3_NAME_SERVICES, + fetchChainIdFromDM3ServiceName, + fetchChainIdFromServiceName, + NAME_SERVICES, +} from '../../components/ConfigureProfile/bl'; + +export interface INodeDetails { + dsName: string; + addrName: { + profile: null | UserProfile; + isActive: boolean; + } | null; + dm3Name: { + profile: null | UserProfile; + isActive: boolean; + } | null; + opName: { + profile: null | UserProfile; + isActive: boolean; + } | null; + gnosisName: { + profile: null | UserProfile; + isActive: boolean; + } | null; + ensName: { + profile: null | UserProfile; + isActive: boolean; + } | null; +} + +export const useDm3UserProfile = () => { + const connectedChainId = useChainId(); + + const { switchNetwork } = useSwitchNetwork({ + onSuccess: async () => { + await executeTransaction(); + }, + }); + + const mainnetProvider = useMainnetProvider(); + + const { account, ethAddress } = useContext(AuthContext); + + const { dm3Configuration } = useContext(DM3ConfigurationContext); + + const { setLoaderContent } = useContext(ModalContext); + + const [nodes, setNodes] = useState([]); + + const [isModalOpenToAddNode, setIsModalOpenToAddNode] = + useState(false); + + const [error, setError] = useState(null); + + const [nodeName, setNodeName] = useState(''); + + const [profileName, setProfileName] = useState(null); + + const [isDm3NameConfigured, setIsDm3NameConfigured] = + useState(false); + + const [isEnsNameConfigured, setIsEnsNameConfigured] = + useState(false); + + // adds DS nodes in local storage of browser + const setDsNodesInLocalStorage = (data: any) => { + const stringifiedData = JSON.stringify(data); + localStorage.setItem('ds_nodes', stringifiedData); + }; + + // retrieves DS nodes from local storage of browser + const getDsNodesFromLocalStorage = (): INodeDetails[] => { + const data = localStorage.getItem('ds_nodes'); + return data ? JSON.parse(data) : []; + }; + + const fetchUserProfileForAllNames = async ( + dsNodes: INodeDetails[], + isLocalStorageData: boolean, + ): Promise => { + if (ethAddress && dsNodes.length) { + // fetch ADDR name, DM3 name, OP name, GNO name & ENS name + const addressName = ethAddress.concat( + dm3Configuration.addressEnsSubdomain, + ); + const dm3Name = await fetchExistingDM3Name( + account as Account, + mainnetProvider, + dm3Configuration, + addressName, + ); + const opName = await fetchExistingOpName( + account as Account, + mainnetProvider, + dm3Configuration, + addressName, + ); + const ensName = await fetchExistingEnsName( + account as Account, + mainnetProvider, + dm3Configuration, + addressName, + ); + const gnosisName = await fetchExistingGnosisName( + account as Account, + mainnetProvider, + dm3Configuration, + addressName, + ); + + setIsDm3NameConfigured(dm3Name || opName ? true : false); + setIsEnsNameConfigured(ensName || gnosisName ? true : false); + + // fetch user profiles for ADDR name, DM3 name, OP name, GNO name & ENS name + const addressNameProfile = await getUserProfile( + mainnetProvider, + addressName, + ).catch((e) => null); + const dm3NameProfile = dm3Name + ? await getUserProfile(mainnetProvider, dm3Name).catch( + (e) => null, + ) + : null; + const opNameProfile = opName + ? await getUserProfile(mainnetProvider, opName).catch( + (e) => null, + ) + : null; + const ensNameProfile = ensName + ? await getUserProfile(mainnetProvider, ensName).catch( + (e) => null, + ) + : null; + const gnosisNameProfile = gnosisName + ? await getUserProfile(mainnetProvider, gnosisName).catch( + (e) => null, + ) + : null; + + let isAddressNameActive: boolean = false; + let isDm3NameActive: boolean = false; + let isOpNameActive: boolean = false; + let isEnsNameActive: boolean = false; + let isGnosisNameActive: boolean = false; + + // 1. local storage has ds node and login account profile matches with localsotrage profile + // 2. OR local storage is empty & DS from logged in profile has to be checked for updated profile + if ( + (isLocalStorageData && + dsNodes[dsNodes.length - 1].addrName?.profile === + addressNameProfile?.profile) || + (!isLocalStorageData && addressNameProfile) + ) { + isAddressNameActive = true; + } + + // 1. local storage has ds node and login account profile matches with localsotrage profile + // 2. OR local storage is empty & DS from logged in profile has to be checked for updated profile + if ( + (isLocalStorageData && + dm3NameProfile && + dsNodes[dsNodes.length - 1].dm3Name?.profile === + dm3NameProfile.profile) || + (!isLocalStorageData && dm3NameProfile) + ) { + isDm3NameActive = true; + } + + // 1. local storage has ds node and login account profile matches with localsotrage profile + // 2. OR local storage is empty & DS from logged in profile has to be checked for updated profile + if ( + (isLocalStorageData && + opNameProfile && + dsNodes[dsNodes.length - 1].opName?.profile === + opNameProfile.profile) || + (!isLocalStorageData && opNameProfile) + ) { + isOpNameActive = true; + } + + // 1. local storage has ds node and login account profile matches with localsotrage profile + // 2. OR local storage is empty & DS from logged in profile has to be checked for updated profile + if ( + (isLocalStorageData && + ensNameProfile && + dsNodes[dsNodes.length - 1].ensName?.profile === + ensNameProfile.profile) || + (!isLocalStorageData && ensNameProfile) + ) { + isEnsNameActive = true; + } + + // 1. local storage has ds node and login account profile matches with localsotrage profile + // 2. OR local storage is empty & DS from logged in profile has to be checked for updated profile + if ( + (isLocalStorageData && + gnosisNameProfile && + dsNodes[dsNodes.length - 1].gnosisName?.profile === + gnosisNameProfile.profile) || + (!isLocalStorageData && gnosisNameProfile) + ) { + isGnosisNameActive = true; + } + + const updatedProfile: INodeDetails[] = dsNodes.map((data) => { + return { + dsName: data.dsName, + addrName: isAddressNameActive + ? { + profile: + addressNameProfile?.profile as UserProfile, + isActive: true, + } + : data.addrName, + dm3Name: isDm3NameActive + ? { + profile: dm3NameProfile?.profile as UserProfile, + isActive: true, + } + : data.dm3Name, + opName: isOpNameActive + ? { + profile: opNameProfile?.profile as UserProfile, + isActive: true, + } + : data.opName, + gnosisName: isGnosisNameActive + ? { + profile: + gnosisNameProfile?.profile as UserProfile, + isActive: true, + } + : data.gnosisName, + ensName: isEnsNameActive + ? { + profile: ensNameProfile?.profile as UserProfile, + isActive: true, + } + : data.ensName, + }; + }); + + return updatedProfile; + } + return []; + }; + + // Initializes user delivery services to show on Network screen + const initialize = async () => { + if (account?.profile) { + // fetch DS nodes from profile + const profileData = account.profile; + const dsNodesFromProfile = profileData.deliveryServices.map( + (dsNode) => { + return { + dsName: dsNode, + addrName: null, + dm3Name: null, + opName: null, + gnosisName: null, + ensName: null, + }; + }, + ); + + // fetch DS nodes from local storage + const dsNodeFromLocalStorage = getDsNodesFromLocalStorage(); + + // filter out duplicate nodes + const profileDsNodes = dsNodeFromLocalStorage + ? dsNodeFromLocalStorage + : dsNodesFromProfile; + // const profileDsNodes = dsNodesFromProfile.filter((d) => !storageNodes.find((s) => s.dsName)); + + const updatedDsNodes = await fetchUserProfileForAllNames( + profileDsNodes, + dsNodeFromLocalStorage ? true : false, + ); + + // set nodes in the list to show on Network UI + setNodes(updatedDsNodes); + return; + } + + setNodes([]); + }; + + const updateProfileForDm3AndAddressName = async () => { + // extract array of delivery service nodes names + const newNodes = nodes.map((n) => n.dsName); + + // add those nodes to new profile + const newProfile = { ...account?.profile! }; + newProfile.deliveryServices = newNodes; + + // create new profile object + const signedUserProfile = { + profile: newProfile, + signature: account?.profileSignature!, + }; + + let response: { + addrName: { + profile: UserProfile | null; + isActive: boolean; + }; + dm3Name: { + profile: UserProfile | null; + isActive: boolean; + }; + opName: null; + gnosisName: null; + ensName: null; + } = { + addrName: { + profile: null, + isActive: false, + }, + dm3Name: { + profile: null, + isActive: false, + }, + opName: null, + gnosisName: null, + ensName: null, + }; + + // update profile for address name + const addrSubdomain = dm3Configuration.addressEnsSubdomain.substring(1); + response.addrName.isActive = await updateProfile( + ethAddress as string, + dm3Configuration.resolverBackendUrl, + addrSubdomain, + signedUserProfile, + ); + response.addrName.profile = newProfile as UserProfile; + + const addressName = ethAddress?.concat( + dm3Configuration.addressEnsSubdomain, + ); + const dm3Name = await fetchExistingDM3Name( + account as Account, + mainnetProvider, + dm3Configuration, + addressName as string, + ); + + // if DM3 name is not set, no need to update the profile for DM3 name + if (!dm3Name) { + response.dm3Name.isActive = false; + } + + if (dm3Name && dm3Name.endsWith(dm3Configuration.userEnsSubdomain)) { + //removes the leading . from the subdomain. + //This is necessary as the resolver does not support subdomains with leading dots + const dm3NameSubdomain = + dm3Configuration.userEnsSubdomain.substring(1); + response.dm3Name.isActive = await updateProfile( + ethAddress as string, + dm3Configuration.resolverBackendUrl, + dm3NameSubdomain, + signedUserProfile, + ); + response.dm3Name.profile = newProfile as UserProfile; + } + + return response; + }; + + const updateProfileWithTransaction = async ( + existingName: string | null, + ) => { + // extract array of delivery service nodes names + const newNodes = nodes.map((n) => n.dsName); + + // add those nodes to new profile + const newProfile: UserProfile = { ...account?.profile! }; + newProfile.deliveryServices = newNodes; + + // update account with new profile + const updatedAccount: Account = { ...account } as Account; + updatedAccount.profile = newProfile; + + // if no name found, don't update + if (!existingName) { + return; + } + + setProfileName(existingName); + + // check its OPTIMISM name + if (existingName.endsWith('.op.dm3.eth')) { + const chainId = fetchChainIdFromDM3ServiceName( + DM3_NAME_SERVICES.OPTIMISM, + dm3Configuration.chainId, + ); + + changeNetwork(chainId); + } + + // check its GNOSIS name + if ( + existingName?.endsWith('.gno') || + existingName?.endsWith('.gnosis.eth') + ) { + const chainId = fetchChainIdFromServiceName( + NAME_SERVICES.GENOME, + dm3Configuration.chainId, + ); + + changeNetwork(chainId); + } + + // check its ENS name + if (existingName?.endsWith('.eth')) { + const chainId = fetchChainIdFromServiceName( + NAME_SERVICES.ENS, + dm3Configuration.chainId, + ); + + changeNetwork(chainId); + } + }; + + const executeTransaction = async () => { + // extract array of delivery service nodes names + const newNodes = nodes.map((n) => n.dsName); + + // add those nodes to new profile + const newProfile: UserProfile = { ...account?.profile! }; + newProfile.deliveryServices = newNodes; + + // update account with new profile + const updatedAccount: Account = { ...account } as Account; + updatedAccount.profile = newProfile; + + const provider = new ethers.providers.Web3Provider( + window.ethereum as ethers.providers.ExternalProvider, + ); + + // if no name found, don't update + if (!profileName) { + return; + } + + // check its OPTIMISM name + if (profileName.endsWith('.op.dm3.eth')) { + // start loader + setLoaderContent('Updating profile...'); + startLoader(); + + try { + await registerOpName(provider, () => {}, profileName); + + // do transaction + await publishProfile(provider, updatedAccount, profileName); + + updateNodeList('OPTIMISM', newProfile); + setProfileName(null); + closeLoader(); + } catch (error) { + console.log('Failed to update profile : ', error); + closeLoader(); + } + } + + // check its GNOSIS name + if ( + profileName?.endsWith('.gno') || + profileName?.endsWith('.gnosis.eth') + ) { + // start loader + setLoaderContent('Updating profile...'); + startLoader(); + + try { + await submitGenomeNameTransaction( + provider, + account!, + setLoaderContent, + profileName, + ethAddress!, + () => {}, + () => {}, + ); + + updateNodeList('GNOSIS', newProfile); + setProfileName(null); + closeLoader(); + } catch (error) { + console.log('Failed to update profile : ', error); + closeLoader(); + } + } + + // check its ENS name + if (profileName?.endsWith('.eth')) { + // start loader + setLoaderContent('Updating profile...'); + startLoader(); + + try { + await submitEnsNameTransaction( + provider!, + account!, + ethAddress!, + setLoaderContent, + profileName, + () => {}, + () => {}, + ); + + updateNodeList('ENS', newProfile); + setProfileName(null); + closeLoader(); + } catch (error) { + console.log('Failed to update profile : ', error); + closeLoader(); + } + } + }; + + const updateNodeList = (name: string, newProfile: UserProfile) => { + // fetch DS nodes from local storage + const dsNodeFromLocalStorage = getDsNodesFromLocalStorage(); + + const profileObject = { + profile: newProfile, + isActive: true, + }; + + const updatedProperty = + name === 'ENS' + ? { ensName: profileObject } + : name === 'GNOSIS' + ? { gnosisName: profileObject } + : { opName: profileObject }; + + // update the DS nodes in local storage + const updatedNodes = dsNodeFromLocalStorage + ? dsNodeFromLocalStorage.map((d) => { + return { + ...d, + ...updatedProperty, + }; + }) + : []; + + setDsNodesInLocalStorage(updatedNodes); + + // update node list + setNodes((prev) => { + return prev.map((d) => { + return { + ...d, + ...updatedProperty, + }; + }); + }); + }; + + // Adds new node locally, whose profle has to be published + const addNode = async () => { + let result: boolean = true; + + // validate node name + if (!isValidName(nodeName)) { + result = false; + } + + // node name must include . and end with .eth + if (!nodeName.includes('.') || !nodeName.endsWith('.eth')) { + result = false; + } + + // set error if any validation fails + if (!result) { + setError('Invalid ENS name'); + return; + } + + // set error if node already exists + if (nodes.filter((data) => data.dsName === nodeName).length) { + setError('ENS name already exists'); + return; + } + + const namesResponse = await updateProfileForDm3AndAddressName(); + + // fetch DS nodes from local storage + const dsNodeFromLocalStorage = getDsNodesFromLocalStorage(); + + // update the DS nodes in local storage + const updatedNodes = dsNodeFromLocalStorage + ? [ + ...dsNodeFromLocalStorage, + { dsName: nodeName, ...namesResponse }, + ] + : [{ dsName: nodeName, ...namesResponse }]; + + setDsNodesInLocalStorage(updatedNodes); + + // update node list + setNodes((prev) => { + return [...prev, { dsName: nodeName, ...namesResponse }]; + }); + + // clear the input field + setNodeName(''); + // clear all errors + setError(null); + // close the input field window + setIsModalOpenToAddNode(false); + }; + + // TODO: Call backend function + const deleteNode = async (id: number) => { + const updatedNodes = nodes.filter((d, i) => i !== id); + setNodes(updatedNodes); + }; + + // TODO: Call backend function + const changeOrder = async (index: number) => { + setNodes((prevState) => { + const data = [...prevState]; + const temp = data[index - 1]; + data[index - 1] = data[index]; + data[index] = temp; + return data; + }); + }; + + const handleNodeNameChange = (e: React.ChangeEvent) => { + setError(null); + setNodeName(e.target.value); + }; + + const changeNetwork = (chainId: number) => { + if (chainId !== connectedChainId && switchNetwork) { + switchNetwork(chainId); + } + }; + + useEffect(() => { + initialize(); + }, [account?.ensName]); + + return { + initialize, + addNode, + updateProfileWithTransaction, + deleteNode, + changeOrder, + nodes, + isModalOpenToAddNode, + setIsModalOpenToAddNode, + error, + setError, + nodeName, + setNodeName, + handleNodeNameChange, + updateProfileForDm3AndAddressName, + isDm3NameConfigured, + isEnsNameConfigured, + }; +}; diff --git a/packages/messenger-widget/src/utils/names.ts b/packages/messenger-widget/src/utils/names.ts new file mode 100644 index 000000000..adf35990f --- /dev/null +++ b/packages/messenger-widget/src/utils/names.ts @@ -0,0 +1,109 @@ +import { Account } from '@dm3-org/dm3-lib-profile'; +import { Dm3Name } from '../hooks/topLevelAlias/nameService/Dm3Name'; +import { ethers } from 'ethers'; +import { DM3Configuration } from '../../lib/esm/interfaces/config'; +import { OptimismNames } from '../hooks/topLevelAlias/nameService/OptimismNames'; +import { Genome } from '../hooks/topLevelAlias/nameService/Genome'; +import { EthereumNameService } from '../hooks/topLevelAlias/nameService/EthereumNameService'; + +export const fetchExistingDM3Name = async ( + account: Account, + mainnetProvider: ethers.providers.JsonRpcProvider, + dm3Configuration: DM3Configuration, + addressName: string, +) => { + try { + if (account) { + const dm3NameService = new Dm3Name( + mainnetProvider, + dm3Configuration.addressEnsSubdomain, + dm3Configuration.userEnsSubdomain, + dm3Configuration.resolverBackendUrl, + ); + const dm3Name = await dm3NameService.resolveAliasToTLD(addressName); + // Not a DM3 name -> 0xa966.beta-addr.dm3.eth + // Its DM3 name -> bob.beta-user.dm3.eth + // Checks user sub domain for setting DM3 name + return dm3Name.endsWith(dm3Configuration.userEnsSubdomain) + ? dm3Name + : null; + } + return null; + } catch (error) { + console.log('dm3 name : ', error); + return null; + } +}; + +export const fetchExistingOpName = async ( + account: Account, + mainnetProvider: ethers.providers.JsonRpcProvider, + dm3Configuration: DM3Configuration, + addressName: string, +) => { + try { + if (account) { + const opNameService = new OptimismNames( + mainnetProvider, + dm3Configuration.addressEnsSubdomain, + ); + const opName = await opNameService.resolveAliasToTLD(addressName); + return opName.endsWith('.op.dm3.eth') ? opName : null; + } + return null; + } catch (error) { + console.log('OP name : ', error); + return null; + } +}; + +export const fetchExistingGnosisName = async ( + account: Account, + mainnetProvider: ethers.providers.JsonRpcProvider, + dm3Configuration: DM3Configuration, + addressName: string, +) => { + try { + if (account) { + const gnosisNameService = new Genome( + mainnetProvider, + dm3Configuration.addressEnsSubdomain, + ); + const gnosisName = await gnosisNameService.resolveAliasToTLD( + addressName, + ); + return gnosisName.endsWith('.gnosis.eth') || + gnosisName.endsWith('.gno') + ? gnosisName + : null; + } + return null; + } catch (error) { + console.log('Gnosis name : ', error); + return null; + } +}; + +export const fetchExistingEnsName = async ( + account: Account, + mainnetProvider: ethers.providers.JsonRpcProvider, + dm3Configuration: DM3Configuration, + addressName: string, +) => { + try { + if (account) { + const ensNameService = new EthereumNameService( + mainnetProvider, + dm3Configuration.addressEnsSubdomain, + dm3Configuration.userEnsSubdomain, + ); + const ensName = await ensNameService.resolveAliasToTLD(addressName); + // .dm3.eth means it is not ENS name + return ensName.endsWith('.dm3.eth') ? null : ensName; + } + return null; + } catch (error) { + console.log('ENS name : ', error); + return null; + } +}; diff --git a/packages/offchain-resolver/src/http/profile.ts b/packages/offchain-resolver/src/http/profile.ts index 22026a31b..7b53f803d 100644 --- a/packages/offchain-resolver/src/http/profile.ts +++ b/packages/offchain-resolver/src/http/profile.ts @@ -267,15 +267,18 @@ export function profile(web3Provider: ethers.providers.BaseProvider) { return res.status(400).send({ error: 'invalid profile' }); } - const hasAddressProfile = - !!(await req.app.locals.db.getProfileContainer(address)); + /** + * On update of a profile for adding new delivery service nodes can have same subdomain + */ + // const hasAddressProfile = + // !!(await req.app.locals.db.getProfileContainer(address)); //One address can only claim one subdomain - if (hasAddressProfile) { - return res.status(400).send({ - error: 'address has already claimed a subdomain', - }); - } + // if (hasAddressProfile) { + // return res.status(400).send({ + // error: 'address has already claimed a subdomain', + // }); + // } const name = `${address}.${subdomain}`; @@ -286,14 +289,17 @@ export function profile(web3Provider: ethers.providers.BaseProvider) { }); } - const profileExists = - !!(await req.app.locals.db.getProfileContainer(name)); - - if (profileExists) { - return res - .status(400) - .send({ error: 'subdomain already claimed' }); - } + /** + * On update of a profile for adding new delivery service nodes can have same subdomain + */ + // const profileExists = + // !!(await req.app.locals.db.getProfileContainer(name)); + + // if (profileExists) { + // return res + // .status(400) + // .send({ error: 'subdomain already claimed' }); + // } await req.app.locals.db.setUserProfile( name, From 8cbdc9b9f025ca52ab42afa35cd1764ceeafce7b Mon Sep 17 00:00:00 2001 From: Bhupesh-MS Date: Mon, 26 Aug 2024 21:18:55 +0530 Subject: [PATCH 11/49] fixed import statement --- packages/messenger-widget/src/utils/names.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/messenger-widget/src/utils/names.ts b/packages/messenger-widget/src/utils/names.ts index adf35990f..3fc72fe86 100644 --- a/packages/messenger-widget/src/utils/names.ts +++ b/packages/messenger-widget/src/utils/names.ts @@ -1,7 +1,7 @@ import { Account } from '@dm3-org/dm3-lib-profile'; import { Dm3Name } from '../hooks/topLevelAlias/nameService/Dm3Name'; import { ethers } from 'ethers'; -import { DM3Configuration } from '../../lib/esm/interfaces/config'; +import { DM3Configuration } from '../interfaces/config'; import { OptimismNames } from '../hooks/topLevelAlias/nameService/OptimismNames'; import { Genome } from '../hooks/topLevelAlias/nameService/Genome'; import { EthereumNameService } from '../hooks/topLevelAlias/nameService/EthereumNameService'; From d7c9734f7c7ff344faf9d50d914c8b3511c5c3e5 Mon Sep 17 00:00:00 2001 From: Bhupesh-MS Date: Tue, 27 Aug 2024 14:44:40 +0530 Subject: [PATCH 12/49] fixed broken tests --- .../src/components/Preferences/Preferences.test.tsx | 13 ------------- packages/offchain-resolver/src/http/profile.test.ts | 5 ++--- 2 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 packages/messenger-widget/src/components/Preferences/Preferences.test.tsx diff --git a/packages/messenger-widget/src/components/Preferences/Preferences.test.tsx b/packages/messenger-widget/src/components/Preferences/Preferences.test.tsx deleted file mode 100644 index 4fc46a390..000000000 --- a/packages/messenger-widget/src/components/Preferences/Preferences.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { render } from '@testing-library/react'; -import { Preferences } from './Preferences'; -import '@testing-library/jest-dom'; - -describe('Preferences test cases', () => { - it('Renders Preferences component', () => { - const { container } = render(); - const element = container.getElementsByClassName( - 'preferences-modal-content', - ); - expect(element[0]).toBeInTheDocument(); - }); -}); diff --git a/packages/offchain-resolver/src/http/profile.test.ts b/packages/offchain-resolver/src/http/profile.test.ts index 4e2293002..01e7c0089 100644 --- a/packages/offchain-resolver/src/http/profile.test.ts +++ b/packages/offchain-resolver/src/http/profile.test.ts @@ -260,7 +260,7 @@ describe('Profile', () => { expect(body.error).to.equal('invalid profile'); }); - it('Rejects if subdomain has already a profile', async () => { + it('Updates profile if subdomain has already a profile', async () => { app.use(profile(provider)); const offChainProfile1 = await getSignedUserProfile(); @@ -291,8 +291,7 @@ describe('Profile', () => { subdomain: 'beta-addr.dm3.eth', }); - expect(res2.status).to.equal(400); - expect(res2.body.error).to.eql('subdomain already claimed'); + expect(res2.status).to.equal(200); }); it('Rejects if subdomain is not supported', async () => { app.use(profile(provider)); From 58aac7244542f98e51aa0a4f975d74c0a0d6a10e Mon Sep 17 00:00:00 2001 From: Bhupesh-MS Date: Wed, 28 Aug 2024 19:58:17 +0530 Subject: [PATCH 13/49] added delete DS node functionality --- .../src/assets/images/active-node.svg | 9 - .../src/assets/images/inactive-node.svg | 9 - .../ConfigureProfile/MobileView.tsx | 37 +- .../ConfigureProfile/NormalView.tsx | 37 +- .../Preferences/Network/Network.tsx | 41 +- .../src/context/DM3UserProfileContext.tsx | 36 +- .../hooks/userProfile/useDm3UserProfile.tsx | 484 ++++++++---------- .../offchain-resolver/src/http/profile.ts | 25 - 8 files changed, 257 insertions(+), 421 deletions(-) delete mode 100644 packages/messenger-widget/src/assets/images/active-node.svg delete mode 100644 packages/messenger-widget/src/assets/images/inactive-node.svg diff --git a/packages/messenger-widget/src/assets/images/active-node.svg b/packages/messenger-widget/src/assets/images/active-node.svg deleted file mode 100644 index dc842380f..000000000 --- a/packages/messenger-widget/src/assets/images/active-node.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/packages/messenger-widget/src/assets/images/inactive-node.svg b/packages/messenger-widget/src/assets/images/inactive-node.svg deleted file mode 100644 index 1258893c3..000000000 --- a/packages/messenger-widget/src/assets/images/inactive-node.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx b/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx index 6eed98020..ee9673a7b 100644 --- a/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx +++ b/packages/messenger-widget/src/components/ConfigureProfile/MobileView.tsx @@ -44,10 +44,9 @@ export function MobileView() { const { dm3Configuration } = useContext(DM3ConfigurationContext); const { - nodes, updateProfileWithTransaction, - isDm3NameConfigured, - isEnsNameConfigured, + isProfileUpdatedForDm3Name, + isProfileUpdatedForEnsName, } = useContext(DM3UserProfileContext); const ensDomainName = @@ -58,26 +57,6 @@ export function MobileView() { : 'ENS')) ?? null; - const isProfileNotUpdatedForDm3Name = (): boolean => { - return nodes.filter( - (n) => - isDm3NameConfigured && - (!n.dm3Name?.isActive || !n.opName?.isActive), - ).length - ? true - : false; - }; - - const isProfileNotUpdatedForEnsName = (): boolean => { - return nodes.filter( - (n) => - isEnsNameConfigured && - (!n.ensName?.isActive || !n.gnosisName?.isActive), - ).length - ? true - : false; - }; - // handles ENS name and address useEffect(() => { getEnsName( @@ -145,7 +124,7 @@ export function MobileView() {

DM3 Name @@ -167,7 +146,7 @@ export function MobileView() {

- {isProfileNotUpdatedForDm3Name() && ( + {!isProfileUpdatedForDm3Name() && ( + )}
{/* Existing DM3 name */} @@ -165,9 +186,17 @@ export function MobileView() { {!isProfileUpdatedForDm3Name() && ( + )}
{/* Existing DM3 name */} @@ -168,13 +193,23 @@ export function NormalView() { {!isProfileUpdatedForDm3Name() && (
- {!isProfileUpdated() && ( + {!isProfileUpdated && (
diff --git a/packages/messenger-widget/src/context/DM3UserProfileContext.tsx b/packages/messenger-widget/src/context/DM3UserProfileContext.tsx index 5a8caca85..708a03e9e 100644 --- a/packages/messenger-widget/src/context/DM3UserProfileContext.tsx +++ b/packages/messenger-widget/src/context/DM3UserProfileContext.tsx @@ -17,10 +17,10 @@ export type DM3UserProfileContextType = { setError: (msg: string) => void; nodeName: string; handleNodeNameChange: (e: React.ChangeEvent) => void; - isProfileUpdated: () => boolean; - isProfileUpdatedForAddrName: () => boolean; - isProfileUpdatedForDm3Name: () => boolean; - isProfileUpdatedForEnsName: () => boolean; + isProfileUpdated: boolean; + isProfileUpdatedForAddrName: boolean; + isProfileUpdatedForDm3Name: boolean; + isProfileUpdatedForEnsName: boolean; }; export const DM3UserProfileContext = @@ -39,10 +39,10 @@ export const DM3UserProfileContext = setError: (msg: string) => {}, nodeName: '', handleNodeNameChange: (e: React.ChangeEvent) => {}, - isProfileUpdated: () => true, - isProfileUpdatedForAddrName: () => true, - isProfileUpdatedForDm3Name: () => true, - isProfileUpdatedForEnsName: () => true, + isProfileUpdated: true, + isProfileUpdatedForAddrName: true, + isProfileUpdatedForDm3Name: true, + isProfileUpdatedForEnsName: true, }); export const DM3UserProfileContextProvider = ({ diff --git a/packages/messenger-widget/src/hooks/userProfile/useDm3UserProfile.tsx b/packages/messenger-widget/src/hooks/userProfile/useDm3UserProfile.tsx index bdc178f24..74de4f04f 100644 --- a/packages/messenger-widget/src/hooks/userProfile/useDm3UserProfile.tsx +++ b/packages/messenger-widget/src/hooks/userProfile/useDm3UserProfile.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useEffect, useState } from 'react'; +import { useMemo, useContext, useEffect, useState } from 'react'; import { AuthContext } from '../../context/AuthContext'; import { isValidName } from 'ethers/lib/utils'; import { updateProfile } from './../../adapters/offchainResolverApi'; @@ -615,55 +615,10 @@ export const useDm3UserProfile = () => { setNodes(newNodes); }; - /** - * Check profile is updated for ADDR name or not. - * If its not updated return false. - * Check profile is updated for DM3 name or not. - * If its not updated return false. - * If DM3 name profile is updated, then check ENS name profile and return true/false. - */ - const isProfileUpdated = useCallback(() => { - return !isProfileUpdatedForAddrName() - ? false - : !isProfileUpdatedForDm3Name() - ? false - : isProfileUpdatedForEnsName(); - }, [nodes, namesWithProfile]); - - const handleNodeNameChange = (e: React.ChangeEvent) => { - setError(null); - setNodeName(e.target.value); - }; - - const changeNetwork = (chainId: number) => { - if (chainId !== connectedChainId && switchNetwork) { - switchNetwork(chainId); - } - }; - - /** - * Validates DS name is actually a delivery service or not - */ - const checkDsNameValidity = async (dsName: string): Promise => { - try { - const resolver = await mainnetProvider.getResolver(dsName); - if (resolver) { - const dsProfile = await resolver.getText( - 'network.dm3.deliveryService', - ); - return !!dsProfile; - } - return false; - } catch (error) { - console.log('Invalid DN node : ', error); - return false; - } - }; - /** * Checks the address name profile is updated or not */ - const isProfileUpdatedForAddrName = useCallback(() => { + const isProfileUpdatedForAddrName = useMemo(() => { const { addrName } = namesWithProfile; if ( addrName && @@ -680,7 +635,7 @@ export const useDm3UserProfile = () => { /** * Checks the DM3/OP name profile is updated or not */ - const isProfileUpdatedForDm3Name = useCallback(() => { + const isProfileUpdatedForDm3Name = useMemo(() => { const { dm3Name, opName } = namesWithProfile; if ( dm3Name && @@ -706,7 +661,7 @@ export const useDm3UserProfile = () => { /** * Checks the ENS/GNO name profile is updated or not */ - const isProfileUpdatedForEnsName = useCallback(() => { + const isProfileUpdatedForEnsName = useMemo(() => { const { ensName, gnosisName } = namesWithProfile; if ( ensName && @@ -729,6 +684,51 @@ export const useDm3UserProfile = () => { return true; }, [nodes, namesWithProfile]); + /** + * Check profile is updated for ADDR name or not. + * If its not updated return false. + * Check profile is updated for DM3 name or not. + * If its not updated return false. + * If DM3 name profile is updated, then check ENS name profile and return true/false. + */ + const isProfileUpdated = useMemo(() => { + return !isProfileUpdatedForAddrName + ? false + : !isProfileUpdatedForDm3Name + ? false + : isProfileUpdatedForEnsName; + }, [nodes, namesWithProfile]); + + const handleNodeNameChange = (e: React.ChangeEvent) => { + setError(null); + setNodeName(e.target.value); + }; + + const changeNetwork = (chainId: number) => { + if (chainId !== connectedChainId && switchNetwork) { + switchNetwork(chainId); + } + }; + + /** + * Validates DS name is actually a delivery service or not + */ + const checkDsNameValidity = async (dsName: string): Promise => { + try { + const resolver = await mainnetProvider.getResolver(dsName); + if (resolver) { + const dsProfile = await resolver.getText( + 'network.dm3.deliveryService', + ); + return !!dsProfile; + } + return false; + } catch (error) { + console.log('Invalid DN node : ', error); + return false; + } + }; + useEffect(() => { initialize(); }, [account?.ensName]); From e146cea9f9c6146dd8ec47527a3d21ae715f8b5f Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 5 Sep 2024 11:30:09 +0200 Subject: [PATCH 37/49] load metrics config --- .../config/getDeliveryServiceProperties.ts | 2 + packages/delivery-service/src/index.ts | 2 +- .../src/message/MessageProcessor.ts | 2 +- packages/delivery-service/src/metrics.ts | 10 +++-- .../src/persistence/getDatabase.ts | 20 ++++++++-- .../src/persistence/metrics/getMetrics.ts | 4 -- .../src/persistence/metrics/setMetrics.ts | 40 +++++++++---------- .../src/rpc/methods/handleSubmitMessage.ts | 6 ++- packages/lib/delivery/src/Delivery.ts | 2 + 9 files changed, 52 insertions(+), 36 deletions(-) diff --git a/packages/delivery-service/src/config/getDeliveryServiceProperties.ts b/packages/delivery-service/src/config/getDeliveryServiceProperties.ts index 110aa278f..e10393e95 100644 --- a/packages/delivery-service/src/config/getDeliveryServiceProperties.ts +++ b/packages/delivery-service/src/config/getDeliveryServiceProperties.ts @@ -10,6 +10,8 @@ const DEFAULT_DELIVERY_SERVICE_PROPERTIES: DeliveryServiceProperties = { //100Kb sizeLimit: 100000, notificationChannel: [], + metricsCollectionIntervalInSeconds: 60 * 60 * 24, // 1 day + metricsRetentionDurationInSeconds: 60 * 60 * 24 * 10, // 10 days }; export function getDeliveryServiceProperties( diff --git a/packages/delivery-service/src/index.ts b/packages/delivery-service/src/index.ts index 78523d3c0..1abcc46fa 100644 --- a/packages/delivery-service/src/index.ts +++ b/packages/delivery-service/src/index.ts @@ -140,7 +140,7 @@ app.use(bodyParser.json()); return res.status(200).send('Hello DM3'); }); - app.use('/metrics', Metrics(db)); + app.use('/metrics', Metrics(db, deliveryServiceProperties)); app.use('/auth', Auth(db, serverSecret, web3Provider)); app.use('/profile', Profile(db, web3Provider, serverSecret)); app.use('/delivery', Delivery(web3Provider, db, serverSecret)); diff --git a/packages/delivery-service/src/message/MessageProcessor.ts b/packages/delivery-service/src/message/MessageProcessor.ts index 81234f147..504462bc2 100644 --- a/packages/delivery-service/src/message/MessageProcessor.ts +++ b/packages/delivery-service/src/message/MessageProcessor.ts @@ -174,7 +174,7 @@ export class MessageProcessor { deliveryInformation, this.db.getUsersNotificationChannels, ); - await this.db.countNotification(); + await this.db.countNotification(this.deliveryServiceProperties); } catch (err) { console.log( 'Unable to send Notification. There might be an error in the config.yml. Message has been received regardless', diff --git a/packages/delivery-service/src/metrics.ts b/packages/delivery-service/src/metrics.ts index 50a1bb3d4..4a415f39e 100644 --- a/packages/delivery-service/src/metrics.ts +++ b/packages/delivery-service/src/metrics.ts @@ -1,17 +1,19 @@ +import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; import express from 'express'; import { IDatabase } from './persistence/getDatabase'; -import { stringifyMetrics } from './persistence/metrics/stringifyMetrics'; - /** * The metrics endpoint returns the metrics of the delivery service. * These can include the number of messages received, the cumulative size of messages received, etc., * over a certain period of time. * @param db The database object. */ -export default (db: IDatabase) => { +export default ( + db: IDatabase, + deliveryServiceProperties: DeliveryServiceProperties, +) => { const router = express.Router(); router.get('', async (req, res) => { - const metrics = await db.getMetrics(); + const metrics = await db.getMetrics(deliveryServiceProperties); return res.status(200).send(JSON.stringify(metrics)); }); diff --git a/packages/delivery-service/src/persistence/getDatabase.ts b/packages/delivery-service/src/persistence/getDatabase.ts index 5923e6d57..88e0c18d3 100644 --- a/packages/delivery-service/src/persistence/getDatabase.ts +++ b/packages/delivery-service/src/persistence/getDatabase.ts @@ -1,4 +1,9 @@ -import { IGlobalNotification, IOtp, Session } from '@dm3-org/dm3-lib-delivery'; +import { + DeliveryServiceProperties, + IGlobalNotification, + IOtp, + Session, +} from '@dm3-org/dm3-lib-delivery'; import { EncryptionEnvelop } from '@dm3-org/dm3-lib-messaging'; import { IAccountDatabase } from '@dm3-org/dm3-lib-server-side'; import { @@ -161,9 +166,16 @@ export interface IDatabase extends IAccountDatabase { ensName: string, channelType: NotificationChannelType, ) => Promise; - getMetrics: () => Promise; - countMessage: (messageSizeBytes: number) => Promise; - countNotification: () => Promise; + getMetrics: ( + deliveryServiceProperties: DeliveryServiceProperties, + ) => Promise; + countMessage: ( + messageSizeBytes: number, + deliveryServiceProperties: DeliveryServiceProperties, + ) => Promise; + countNotification: ( + deliveryServiceProperties: DeliveryServiceProperties, + ) => Promise; } export type Redis = Awaited>; diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.ts index d7b874cbd..cd24d2b63 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.ts @@ -1,10 +1,6 @@ import { Redis, RedisPrefix } from '../getDatabase'; import { MetricsObject, IntervalMetric } from './metricTypes'; -const INTERVAL_SECONDS = parseInt( - process.env.METRICS_INTERVAL_SECONDS || '86400', -); // 1 day - function getCurrentIntervalTimestamp(): number { return Math.floor(Date.now() / 1000 / INTERVAL_SECONDS) * INTERVAL_SECONDS; } diff --git a/packages/delivery-service/src/persistence/metrics/setMetrics.ts b/packages/delivery-service/src/persistence/metrics/setMetrics.ts index 9224b045b..e72e5f5d7 100644 --- a/packages/delivery-service/src/persistence/metrics/setMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/setMetrics.ts @@ -1,24 +1,22 @@ import { Redis, RedisPrefix } from '../getDatabase'; +import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; -// todo: add loader function like in server-side utils -const INTERVAL_SECONDS = parseInt( - process.env.METRICS_INTERVAL_SECONDS || '86400', -); // 1 day -const RETAIN_INTERVALS = parseInt( - process.env.METRICS_INTERVAL_RETENTION_COUNT || '10', -); - -function getKeyIntervalTimestamp(): string { +function getKeyIntervalTimestamp(collectionIntervalInSeconds: number): string { const currentDate = new Date(); const timestamp = - Math.floor(currentDate.getTime() / 1000 / INTERVAL_SECONDS) * - INTERVAL_SECONDS; + Math.floor(currentDate.getTime() / 1000 / collectionIntervalInSeconds) * + collectionIntervalInSeconds; return `${timestamp}`; } export function countMessage(redis: Redis) { - return async (messageSizeBytes: number) => { - const timestamp = getKeyIntervalTimestamp(); + return async ( + messageSizeBytes: number, + deliveryServiceProperties: DeliveryServiceProperties, + ) => { + const timestamp = getKeyIntervalTimestamp( + deliveryServiceProperties.metricsCollectionIntervalInSeconds, + ); console.log( 'countMessage at', @@ -36,14 +34,14 @@ export function countMessage(redis: Redis) { messageSizeBytes, ); - // Set expiration + // Set expiration. After this time the metrics are automatically deleted by redis. await redis.expire( `${RedisPrefix.MetricsMessageCount}${timestamp}`, - INTERVAL_SECONDS * RETAIN_INTERVALS, + deliveryServiceProperties.metricsRetentionDurationInSeconds, ); await redis.expire( `${RedisPrefix.MetricsMessageSize}${timestamp}`, - INTERVAL_SECONDS * RETAIN_INTERVALS, + deliveryServiceProperties.metricsRetentionDurationInSeconds, ); console.log('countMessage of size', messageSizeBytes, 'at', timestamp); @@ -51,8 +49,10 @@ export function countMessage(redis: Redis) { } export function countNotification(redis: Redis) { - return async () => { - const timestamp = getKeyIntervalTimestamp(); + return async (deliveryServiceProperties: DeliveryServiceProperties) => { + const timestamp = getKeyIntervalTimestamp( + deliveryServiceProperties.metricsCollectionIntervalInSeconds, + ); // Increment the notification count, starting at 0 if the key doesn't exist await redis.incrBy( @@ -60,10 +60,10 @@ export function countNotification(redis: Redis) { 1, ); - // Set expiration + // Set expiration. After this time the metrics are automatically deleted by redis. await redis.expire( `${RedisPrefix.MetricsNotificationCount}${timestamp}`, - INTERVAL_SECONDS * RETAIN_INTERVALS, + deliveryServiceProperties.metricsRetentionDurationInSeconds, ); console.log('countNotification at', timestamp); diff --git a/packages/delivery-service/src/rpc/methods/handleSubmitMessage.ts b/packages/delivery-service/src/rpc/methods/handleSubmitMessage.ts index 46a6c9507..2df50d000 100644 --- a/packages/delivery-service/src/rpc/methods/handleSubmitMessage.ts +++ b/packages/delivery-service/src/rpc/methods/handleSubmitMessage.ts @@ -16,7 +16,6 @@ import express from 'express'; import { Server } from 'socket.io'; import { MessageProcessor } from '../../message/MessageProcessor'; import { IDatabase } from '../../persistence/getDatabase'; -import { countMessage } from '../../persistence/metrics/setMetrics'; export async function handleSubmitMessage( req: express.Request, @@ -75,7 +74,10 @@ export async function handleSubmitMessage( try { await messageProcessor.processEnvelop(envelop); - await db.countMessage(getEnvelopSize(envelop)); + await db.countMessage( + getEnvelopSize(envelop), + deliveryServiceProperties, + ); res.sendStatus(200); } catch (error) { console.error('handle submit message error'); diff --git a/packages/lib/delivery/src/Delivery.ts b/packages/lib/delivery/src/Delivery.ts index 3a528dc05..96b146b35 100644 --- a/packages/lib/delivery/src/Delivery.ts +++ b/packages/lib/delivery/src/Delivery.ts @@ -5,4 +5,6 @@ export interface DeliveryServiceProperties { //Number of bytes an envelop object should not exceed sizeLimit: number; notificationChannel: NotificationChannel[]; + metricsCollectionIntervalInSeconds: number; + metricsRetentionDurationInSeconds: number; } From 4befb980e0f2c8adce1fe412b1d8b72939dba043 Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 5 Sep 2024 11:40:13 +0200 Subject: [PATCH 38/49] cleanup --- .../metrics/getCurrentIntervalTimestamp.ts | 16 +++++++++++ .../src/persistence/metrics/getMetrics.ts | 22 +++++++++------ .../src/persistence/metrics/index.ts | 2 -- .../src/persistence/metrics/setMetrics.ts | 13 ++------- .../persistence/metrics/stringifyMetrics.ts | 28 ------------------- 5 files changed, 32 insertions(+), 49 deletions(-) create mode 100644 packages/delivery-service/src/persistence/metrics/getCurrentIntervalTimestamp.ts delete mode 100644 packages/delivery-service/src/persistence/metrics/stringifyMetrics.ts diff --git a/packages/delivery-service/src/persistence/metrics/getCurrentIntervalTimestamp.ts b/packages/delivery-service/src/persistence/metrics/getCurrentIntervalTimestamp.ts new file mode 100644 index 000000000..c82df004b --- /dev/null +++ b/packages/delivery-service/src/persistence/metrics/getCurrentIntervalTimestamp.ts @@ -0,0 +1,16 @@ +/** + * Get the timestamp of the current metrics collection interval. + * In order to have reproducible interval names, we use the start of the interval. This is defined + * as the timestamp of the first second of the interval, when counting full intervals from unix epoch. + * @param collectionIntervalInSeconds the duration over which all metrics are summarized + * @returns the timestamp of the current interval + */ +export function getCurrentIntervalTimestamp( + collectionIntervalInSeconds: number, +): string { + const currentDate = new Date(); + const timestamp = + Math.floor(currentDate.getTime() / 1000 / collectionIntervalInSeconds) * + collectionIntervalInSeconds; + return `${timestamp}`; +} diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.ts index cd24d2b63..07f437a6f 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.ts @@ -1,18 +1,22 @@ +import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; import { Redis, RedisPrefix } from '../getDatabase'; -import { MetricsObject, IntervalMetric } from './metricTypes'; - -function getCurrentIntervalTimestamp(): number { - return Math.floor(Date.now() / 1000 / INTERVAL_SECONDS) * INTERVAL_SECONDS; -} - -export function getMetrics(redis: Redis): () => Promise { - return async () => { +import { getCurrentIntervalTimestamp } from './getCurrentIntervalTimestamp'; +import { MetricsObject } from './metricTypes'; + +export function getMetrics( + redis: Redis, +): ( + deliveryServiceProperties: DeliveryServiceProperties, +) => Promise { + return async (deliveryServiceProperties: DeliveryServiceProperties) => { const metrics: MetricsObject = {}; const messageCountKeys = await redis.keys( `${RedisPrefix.MetricsMessageCount}*`, ); - const currentTimestamp = getCurrentIntervalTimestamp(); + const currentTimestamp = getCurrentIntervalTimestamp( + deliveryServiceProperties.metricsCollectionIntervalInSeconds, + ); for (const key of messageCountKeys) { const timestamp = parseInt(key.split(':')[1], 10); diff --git a/packages/delivery-service/src/persistence/metrics/index.ts b/packages/delivery-service/src/persistence/metrics/index.ts index cd7573e21..52101ae8b 100644 --- a/packages/delivery-service/src/persistence/metrics/index.ts +++ b/packages/delivery-service/src/persistence/metrics/index.ts @@ -1,13 +1,11 @@ import { getMetrics } from './getMetrics'; import type { IntervalMetric, MetricsMap, MetricsObject } from './metricTypes'; import { countMessage, countNotification } from './setMetrics'; -import { stringifyMetrics } from './stringifyMetrics'; export default { getMetrics, countMessage, countNotification, - stringifyMetrics, }; export type { IntervalMetric, MetricsMap, MetricsObject }; diff --git a/packages/delivery-service/src/persistence/metrics/setMetrics.ts b/packages/delivery-service/src/persistence/metrics/setMetrics.ts index e72e5f5d7..5600fa14e 100644 --- a/packages/delivery-service/src/persistence/metrics/setMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/setMetrics.ts @@ -1,20 +1,13 @@ import { Redis, RedisPrefix } from '../getDatabase'; import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; - -function getKeyIntervalTimestamp(collectionIntervalInSeconds: number): string { - const currentDate = new Date(); - const timestamp = - Math.floor(currentDate.getTime() / 1000 / collectionIntervalInSeconds) * - collectionIntervalInSeconds; - return `${timestamp}`; -} +import { getCurrentIntervalTimestamp } from './getCurrentIntervalTimestamp'; export function countMessage(redis: Redis) { return async ( messageSizeBytes: number, deliveryServiceProperties: DeliveryServiceProperties, ) => { - const timestamp = getKeyIntervalTimestamp( + const timestamp = getCurrentIntervalTimestamp( deliveryServiceProperties.metricsCollectionIntervalInSeconds, ); @@ -50,7 +43,7 @@ export function countMessage(redis: Redis) { export function countNotification(redis: Redis) { return async (deliveryServiceProperties: DeliveryServiceProperties) => { - const timestamp = getKeyIntervalTimestamp( + const timestamp = getCurrentIntervalTimestamp( deliveryServiceProperties.metricsCollectionIntervalInSeconds, ); diff --git a/packages/delivery-service/src/persistence/metrics/stringifyMetrics.ts b/packages/delivery-service/src/persistence/metrics/stringifyMetrics.ts deleted file mode 100644 index ffeb3e9e0..000000000 --- a/packages/delivery-service/src/persistence/metrics/stringifyMetrics.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { MetricsMap } from './metricTypes'; - -// https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map - -// @ts-ignore -function replacer(key, value) { - if (value instanceof Map) { - return { - dataType: 'Map', - value: Array.from(value.entries()), // or with spread: value: [...value] - }; - } else { - return value; - } -} - -// function reviver(key, value) { -// if (typeof value === 'object' && value !== null) { -// if (value.dataType === 'Map') { -// return new Map(value.value); -// } -// } -// return value; -// } - -export function stringifyMetrics(metrics: MetricsMap) { - return JSON.stringify(metrics, replacer); -} From 7cc02d071eba958197d93c27280aa6b088f3b0bd Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 5 Sep 2024 12:19:00 +0200 Subject: [PATCH 39/49] fix metrics tests --- .../persistence/metrics/getMetrics.test.ts | 75 ++++++++++++------- .../persistence/metrics/setMetrics.test.ts | 33 ++++---- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts index 0885cc160..05705ecb0 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts @@ -4,8 +4,15 @@ import { MetricsObject, IntervalMetric } from './metricTypes'; describe('getMetrics', () => { let mockRedis: jest.Mocked; + const mockDate = new Date('2023-04-01T12:00:00Z'); + //@ts-ignore + const mockDeliveryServiceProperties: DeliveryServiceProperties = { + metricsCollectionIntervalInSeconds: 3600, + metricsRetentionDurationInSeconds: 864000, + }; beforeEach(() => { + jest.useFakeTimers().setSystemTime(mockDate); mockRedis = { keys: jest.fn(), get: jest.fn(), @@ -16,15 +23,16 @@ describe('getMetrics', () => { mockRedis.keys.mockResolvedValue([]); const getMetricsFunc = getMetrics(mockRedis); - const result = await getMetricsFunc(); + const result = await getMetricsFunc(mockDeliveryServiceProperties); expect(result).toEqual({}); }); - it('should correctly parse and return metrics', async () => { - const timestamp = Math.floor(Date.now() / 1000) - 86400; // Use yesterday's timestamp - const mockKeys = [`${RedisPrefix.MetricsMessageCount}${timestamp}`]; - + it('should return metrics for all available intervals', async () => { + const mockKeys = Array.from({ length: 24 }, (_, i) => { + const timestamp = 1680307200 - i * 3600; + return `${RedisPrefix.MetricsMessageCount}${timestamp}`; + }); mockRedis.keys.mockResolvedValue(mockKeys); mockRedis.get.mockImplementation((key: string) => { if (key.includes(RedisPrefix.MetricsMessageCount)) @@ -36,40 +44,53 @@ describe('getMetrics', () => { return Promise.resolve(null); }); - const getMetricsFunc = getMetrics(mockRedis); - const result = await getMetricsFunc(); - - expect(Object.keys(result).length).toBe(1); - - const expectedDate = new Date(timestamp * 1000).toISOString(); - const metric = result[expectedDate]; + const getMetricsFunction = getMetrics(mockRedis); + const result = await getMetricsFunction(mockDeliveryServiceProperties); - expect(metric).toEqual({ + expect(mockRedis.keys).toHaveBeenCalledWith( + `${RedisPrefix.MetricsMessageCount}*`, + ); + expect(Object.keys(result)).toHaveLength(24); + expect(result['2023-03-31T08:00:00.000Z']).toEqual({ messageCount: 10, messageSizeBytes: 1000, notificationCount: 5, }); }); - it('should handle missing metric values', async () => { - const timestamp = Math.floor(Date.now() / 1000) - 86400; // Use yesterday's timestamp - const mockKeys = [`${RedisPrefix.MetricsMessageCount}${timestamp}`]; - - mockRedis.keys.mockResolvedValue(mockKeys); - mockRedis.get.mockResolvedValue(null); + it('should handle missing data', async () => { + mockRedis.keys.mockResolvedValue([ + `${RedisPrefix.MetricsMessageCount}1680307200`, + ]); + //@ts-ignore + mockRedis.get.mockImplementation((key) => { + console.log('key', key); + //@ts-ignore + const [prefix] = key.split(':'); + switch (prefix + ':') { + case RedisPrefix.MetricsMessageCount: + return Promise.resolve('10'); + case RedisPrefix.MetricsMessageSize: + return Promise.resolve(null); + case RedisPrefix.MetricsNotificationCount: + return Promise.resolve('5'); + default: + return Promise.resolve(null); + } + }); - const getMetricsFunc = getMetrics(mockRedis); - const result = await getMetricsFunc(); + const getMetricsFunction = getMetrics(mockRedis); + const result = await getMetricsFunction(mockDeliveryServiceProperties); + console.log(result); - expect(Object.keys(result).length).toBe(1); + expect(Object.keys(result)).toHaveLength(1); - const expectedDate = new Date(timestamp * 1000).toISOString(); - const metric = result[expectedDate]; + expect(Object.keys(result)).toEqual(['2023-04-01T00:00:00.000Z']); - expect(metric).toEqual({ - messageCount: 0, + expect(result['2023-04-01T00:00:00.000Z']).toEqual({ + messageCount: 10, messageSizeBytes: 0, - notificationCount: 0, + notificationCount: 5, }); }); }); diff --git a/packages/delivery-service/src/persistence/metrics/setMetrics.test.ts b/packages/delivery-service/src/persistence/metrics/setMetrics.test.ts index 738d518f4..df5955ba0 100644 --- a/packages/delivery-service/src/persistence/metrics/setMetrics.test.ts +++ b/packages/delivery-service/src/persistence/metrics/setMetrics.test.ts @@ -1,20 +1,17 @@ import { Redis, RedisPrefix } from '../getDatabase'; import { countMessage, countNotification } from './setMetrics'; - -// these are currently not used, as the environment variables are loaded -// before the tests are run -// const intervalSeconds = 3600 * 48; -// const intervalRetentionCount = 10; -// process.env.METRICS_INTERVAL_SECONDS = `${intervalSeconds}`; -// process.env.METRICS_INTERVAL_RETENTION_COUNT = `${intervalRetentionCount}`; +import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; describe('setMetrics', () => { let mockRedis: jest.Mocked; const mockDate = new Date('2023-04-01T12:00:00Z'); + //@ts-ignore + const mockDeliveryServiceProperties: DeliveryServiceProperties = { + metricsCollectionIntervalInSeconds: 3600, + metricsRetentionDurationInSeconds: 864000, + }; - // const mockExpireDate = new Date( - // mockDate.getTime() + intervalSeconds * intervalRetentionCount * 1000, - // ); + const expectedTimestamp = '1680350400'; beforeEach(() => { jest.useFakeTimers().setSystemTime(mockDate); @@ -31,11 +28,13 @@ describe('setMetrics', () => { describe('countMessage', () => { it('should increment message count and size, and set expiration', async () => { const messageSizeBytes = 100; - const expectedTimestamp = '1680307200'; const countMessageFunction = countMessage(mockRedis); - await countMessageFunction(messageSizeBytes); + await countMessageFunction( + messageSizeBytes, + mockDeliveryServiceProperties, + ); expect(mockRedis.incrBy).toHaveBeenCalledTimes(2); expect(mockRedis.incrBy).toHaveBeenCalledWith( @@ -50,22 +49,20 @@ describe('setMetrics', () => { expect(mockRedis.expire).toHaveBeenCalledTimes(2); expect(mockRedis.expire).toHaveBeenCalledWith( `${RedisPrefix.MetricsMessageCount}${expectedTimestamp}`, - 864000, + mockDeliveryServiceProperties.metricsRetentionDurationInSeconds, ); expect(mockRedis.expire).toHaveBeenCalledWith( `${RedisPrefix.MetricsMessageSize}${expectedTimestamp}`, - 864000, + mockDeliveryServiceProperties.metricsRetentionDurationInSeconds, ); }); }); describe('countNotification', () => { it('should increment notification count and set expiration', async () => { - const expectedTimestamp = '1680307200'; - const countNotificationFunction = countNotification(mockRedis); - await countNotificationFunction(); + await countNotificationFunction(mockDeliveryServiceProperties); expect(mockRedis.incrBy).toHaveBeenCalledTimes(1); expect(mockRedis.incrBy).toHaveBeenCalledWith( @@ -76,7 +73,7 @@ describe('setMetrics', () => { expect(mockRedis.expire).toHaveBeenCalledTimes(1); expect(mockRedis.expire).toHaveBeenCalledWith( `${RedisPrefix.MetricsNotificationCount}${expectedTimestamp}`, - 864000, + mockDeliveryServiceProperties.metricsRetentionDurationInSeconds, ); }); }); From 9552bf54ea34587418632a58c8d54686843b8cb1 Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 5 Sep 2024 12:31:22 +0200 Subject: [PATCH 40/49] fix test --- .../getDeliveryServiceProperties.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/delivery-service/src/config/getDeliveryServiceProperties.test.ts b/packages/delivery-service/src/config/getDeliveryServiceProperties.test.ts index 80a9575d8..6de40b37f 100644 --- a/packages/delivery-service/src/config/getDeliveryServiceProperties.test.ts +++ b/packages/delivery-service/src/config/getDeliveryServiceProperties.test.ts @@ -21,12 +21,16 @@ describe('ReadDeliveryServiceProperties', () => { messageTTL: 12345, sizeLimit: 456, notificationChannel: [], + metricsCollectionIntervalInSeconds: 600, + metricsRetentionDurationInSeconds: 172800, }); expect(config).toStrictEqual({ messageTTL: 12345, sizeLimit: 456, notificationChannel: [], + metricsCollectionIntervalInSeconds: 600, + metricsRetentionDurationInSeconds: 172800, }); }); @@ -37,6 +41,8 @@ describe('ReadDeliveryServiceProperties', () => { messageTTL: 12345, sizeLimit: 456, notificationChannel: [], + metricsCollectionIntervalInSeconds: 900, + metricsRetentionDurationInSeconds: 259200, }), { encoding: 'utf-8' }, ); @@ -46,8 +52,11 @@ describe('ReadDeliveryServiceProperties', () => { messageTTL: 12345, sizeLimit: 456, notificationChannel: [], + metricsCollectionIntervalInSeconds: 900, + metricsRetentionDurationInSeconds: 259200, }); }); + it('Adds default properties if config.yml is not fully specified', () => { writeFileSync( path, @@ -68,6 +77,7 @@ describe('ReadDeliveryServiceProperties', () => { }, }, ], + metricsCollectionIntervalInSeconds: 1200, }), { encoding: 'utf-8' }, ); @@ -91,8 +101,11 @@ describe('ReadDeliveryServiceProperties', () => { }, }, ], + metricsCollectionIntervalInSeconds: 1200, + metricsRetentionDurationInSeconds: 60 * 60 * 24 * 10, }); }); + it('Adds email channel from config.yml file & rest from default properties', () => { writeFileSync( path, @@ -135,6 +148,8 @@ describe('ReadDeliveryServiceProperties', () => { }, }, ], + metricsCollectionIntervalInSeconds: 60 * 60 * 24, + metricsRetentionDurationInSeconds: 60 * 60 * 24 * 10, }); }); @@ -170,6 +185,28 @@ describe('ReadDeliveryServiceProperties', () => { }, }, ], + metricsCollectionIntervalInSeconds: 60 * 60 * 24, + metricsRetentionDurationInSeconds: 60 * 60 * 24 * 10, + }); + }); + + it('Uses default values for metrics properties if not specified', () => { + writeFileSync( + path, + stringify({ + messageTTL: 54321, + sizeLimit: 789, + }), + { encoding: 'utf-8' }, + ); + const config = getDeliveryServiceProperties(path); + + expect(config).toStrictEqual({ + messageTTL: 54321, + sizeLimit: 789, + notificationChannel: [], + metricsCollectionIntervalInSeconds: 60 * 60 * 24, + metricsRetentionDurationInSeconds: 60 * 60 * 24 * 10, }); }); }); From f596318c2f9c63cea586ebe674ca6c6818afa1ff Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 5 Sep 2024 12:50:32 +0200 Subject: [PATCH 41/49] enable metrics censoring and add test --- .../src/persistence/metrics/getMetrics.test.ts | 17 +++++++++++++++++ .../src/persistence/metrics/getMetrics.ts | 14 +++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts index 05705ecb0..afcdcc3e1 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts @@ -93,4 +93,21 @@ describe('getMetrics', () => { notificationCount: 5, }); }); + + it.only('should censor the current interval', async () => { + const currentTimestamp = Math.floor(mockDate.getTime() / 1000); + const mockKeys = [ + `${RedisPrefix.MetricsMessageCount}${currentTimestamp}`, + `${RedisPrefix.MetricsMessageCount}${currentTimestamp - 3600}`, + ]; + mockRedis.keys.mockResolvedValue(mockKeys); + mockRedis.get.mockResolvedValue('10'); + + const getMetricsFunction = getMetrics(mockRedis); + const result = await getMetricsFunction(mockDeliveryServiceProperties); + + expect(Object.keys(result)).toHaveLength(1); + expect(Object.keys(result)[0]).toBe('2023-04-01T11:00:00.000Z'); + expect(result).not.toHaveProperty(mockDate.toISOString()); + }); }); diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.ts index 07f437a6f..8b03c8a03 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.ts @@ -3,6 +3,14 @@ import { Redis, RedisPrefix } from '../getDatabase'; import { getCurrentIntervalTimestamp } from './getCurrentIntervalTimestamp'; import { MetricsObject } from './metricTypes'; +/** + * Get the metrics from the database, excluding the current interval. + * (The current interval is excluded because monitoring it closely allows + * third parties to gain too much information about the current activity on the + * DM3 network.) + * @param redis - The Redis instance. + * @returns The metrics object. + */ export function getMetrics( redis: Redis, ): ( @@ -22,9 +30,9 @@ export function getMetrics( const timestamp = parseInt(key.split(':')[1], 10); // Skip the current interval - // if (timestamp === currentTimestamp) { - // continue; - // } + if (String(timestamp) === currentTimestamp) { + continue; + } const date = new Date(timestamp * 1000); const dateString = date.toISOString(); From 86f2ba7c1d9396ac958cc725d3c36c321e977045 Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 5 Sep 2024 12:50:48 +0200 Subject: [PATCH 42/49] enable tests --- .../delivery-service/src/persistence/metrics/getMetrics.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts index afcdcc3e1..7243cf6ae 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts @@ -94,7 +94,7 @@ describe('getMetrics', () => { }); }); - it.only('should censor the current interval', async () => { + it('should censor the current interval', async () => { const currentTimestamp = Math.floor(mockDate.getTime() / 1000); const mockKeys = [ `${RedisPrefix.MetricsMessageCount}${currentTimestamp}`, From fd24448c2be9b1081cfc3f9f8ed86f1f2e5b5fc4 Mon Sep 17 00:00:00 2001 From: malteish Date: Thu, 5 Sep 2024 13:12:57 +0200 Subject: [PATCH 43/49] cleanup --- packages/backend/package.json | 2 +- .../getDeliveryServiceProperties.test.ts | 175 ------------------ .../config/getDeliveryServiceProperties.ts | 60 ------ 3 files changed, 1 insertion(+), 236 deletions(-) delete mode 100644 packages/backend/src/config/getDeliveryServiceProperties.test.ts delete mode 100644 packages/backend/src/config/getDeliveryServiceProperties.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index db961a72e..5b6cbbea7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -31,7 +31,7 @@ "start": "yarn prisma-init && node ./dist/index.js", "start-inspect": "node --inspect=0.0.0.0:9229 ./dist/index.js", "test": "yarn run before:tests && DATABASE_URL='postgresql://prisma:prisma@localhost:5433/tests?schema=public' yarn jest --coverage --runInBand --transformIgnorePatterns 'node_modules/(?!(dm3-lib-\\w*)/)'", - "build": "yarn prisma generate && yarn tsc && cp ./config.yml ./dist/config.yml | true", + "build": "yarn prisma generate && yarn tsc | true", "build:schema": "sh ./schemas.sh", "createDeliveryServiceProfile": "node --no-warnings ./cli.js", "before:tests": "docker compose -f docker-compose.test.yml up -d && DATABASE_URL='postgresql://prisma:prisma@localhost:5433/tests?schema=public' yarn prisma-init" diff --git a/packages/backend/src/config/getDeliveryServiceProperties.test.ts b/packages/backend/src/config/getDeliveryServiceProperties.test.ts deleted file mode 100644 index 80a9575d8..000000000 --- a/packages/backend/src/config/getDeliveryServiceProperties.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { existsSync, unlinkSync, writeFileSync } from 'fs'; -import { resolve } from 'path'; -import { stringify } from 'yaml'; -import { getDeliveryServiceProperties } from './getDeliveryServiceProperties'; -import { NotificationChannelType } from '@dm3-org/dm3-lib-shared'; - -describe('ReadDeliveryServiceProperties', () => { - let path: string; - beforeEach(() => { - path = resolve(__dirname, './config.test.yml'); - }); - - afterEach(() => { - if (existsSync(path)) { - unlinkSync(path); - } - }); - - it('Returns default DeliveryServiceProfile if config file is undefined', () => { - const config = getDeliveryServiceProperties('/unknown-path', { - messageTTL: 12345, - sizeLimit: 456, - notificationChannel: [], - }); - - expect(config).toStrictEqual({ - messageTTL: 12345, - sizeLimit: 456, - notificationChannel: [], - }); - }); - - it('Returns Config from path', () => { - writeFileSync( - path, - stringify({ - messageTTL: 12345, - sizeLimit: 456, - notificationChannel: [], - }), - { encoding: 'utf-8' }, - ); - const config = getDeliveryServiceProperties(path); - - expect(config).toStrictEqual({ - messageTTL: 12345, - sizeLimit: 456, - notificationChannel: [], - }); - }); - it('Adds default properties if config.yml is not fully specified', () => { - writeFileSync( - path, - stringify({ - messageTTL: 12345, - notificationChannel: [ - { - type: NotificationChannelType.EMAIL, - config: { - host: 'mail.alice.com', - port: 465, - secure: true, - auth: { - user: 'foo', - pass: 'bar', - }, - senderAddress: 'mail@dm3.io', - }, - }, - ], - }), - { encoding: 'utf-8' }, - ); - const config = getDeliveryServiceProperties(path); - - expect(config).toStrictEqual({ - messageTTL: 12345, - sizeLimit: 100000, - notificationChannel: [ - { - type: NotificationChannelType.EMAIL, - config: { - host: 'mail.alice.com', - port: 465, - secure: true, - auth: { - user: 'foo', - pass: 'bar', - }, - senderAddress: 'mail@dm3.io', - }, - }, - ], - }); - }); - it('Adds email channel from config.yml file & rest from default properties', () => { - writeFileSync( - path, - stringify({ - notificationChannel: [ - { - type: NotificationChannelType.EMAIL, - config: { - host: 'mail.alice.com', - port: 465, - secure: true, - auth: { - user: 'foo', - pass: 'bar', - }, - senderAddress: 'mail@dm3.io', - }, - }, - ], - }), - { encoding: 'utf-8' }, - ); - const config = getDeliveryServiceProperties(path); - - expect(config).toStrictEqual({ - messageTTL: 0, - sizeLimit: 100000, - notificationChannel: [ - { - type: NotificationChannelType.EMAIL, - config: { - host: 'mail.alice.com', - port: 465, - secure: true, - auth: { - user: 'foo', - pass: 'bar', - }, - senderAddress: 'mail@dm3.io', - }, - }, - ], - }); - }); - - it('Adds push notification channel from config.yml file & rest from default properties', () => { - writeFileSync( - path, - stringify({ - notificationChannel: [ - { - type: NotificationChannelType.PUSH, - config: { - vapidEmailId: 'test@gmail.com', - publicVapidKey: 'dbiwqeqwewqosa', - privateVapidKey: 'wqieyiwqeqwnsd', - }, - }, - ], - }), - { encoding: 'utf-8' }, - ); - const config = getDeliveryServiceProperties(path); - - expect(config).toStrictEqual({ - messageTTL: 0, - sizeLimit: 100000, - notificationChannel: [ - { - type: NotificationChannelType.PUSH, - config: { - vapidEmailId: 'test@gmail.com', - publicVapidKey: 'dbiwqeqwewqosa', - privateVapidKey: 'wqieyiwqeqwnsd', - }, - }, - ], - }); - }); -}); diff --git a/packages/backend/src/config/getDeliveryServiceProperties.ts b/packages/backend/src/config/getDeliveryServiceProperties.ts deleted file mode 100644 index cc420c553..000000000 --- a/packages/backend/src/config/getDeliveryServiceProperties.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { parse } from 'yaml'; -import { existsSync, readFileSync } from 'fs'; -import { resolve } from 'path'; -import { logInfo, validateSchema } from '@dm3-org/dm3-lib-shared'; -import { schema, DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; - -const DEFAULT_CONFIG_FILE_PATH = resolve(__dirname, './../config.yml'); -const DEFAULT_DELIVERY_SERVICE_PROPERTIES: DeliveryServiceProperties = { - messageTTL: 0, - //100Kb - sizeLimit: 100000, - notificationChannel: [], -}; - -export function getDeliveryServiceProperties( - path: string = DEFAULT_CONFIG_FILE_PATH, - defaultDeliveryServiceProperties: DeliveryServiceProperties = DEFAULT_DELIVERY_SERVICE_PROPERTIES, -): DeliveryServiceProperties { - console.log('resolve dir', __dirname); - console.log('looking for config.yml in ', path); - console.log('resolve root: ', resolve(__dirname, './config.yml')); - console.log( - 'can find config ', - existsSync(resolve(__dirname, './config.yml')), - ); - if (!existsSync(path)) { - logInfo('Config file not found. Default Config is used'); - return defaultDeliveryServiceProperties; - } - - const yamlString = readFileSync(path, { encoding: 'utf-8' }); - - const deliveryServiceProfile = parse(yamlString); - - const isSchemaValid = validateSchema( - // eslint-disable-next-line max-len - //The interface DeliveryServiceProperties requires all properties to be non-null. But since we are accepting a partially filled config.yml we are overwriting the required fields so basically no property is required at all. This can be done because every missing property is replaced by a default property - { - ...schema.DeliveryServiceProperties, - definitions: { - ...schema.DeliveryServiceProperties.definitions, - DeliveryServiceProperties: { - ...schema.DeliveryServiceProperties.definitions - .DeliveryServiceProperties, - required: [], - }, - }, - }, - deliveryServiceProfile, - ); - - if (!isSchemaValid) { - throw Error('Invalid config.yml'); - } - - return { - ...defaultDeliveryServiceProperties, - ...parse(yamlString), - } as DeliveryServiceProperties; -} From d1c7edfc7ad7dd8f5967a5e9ea2d316d31532e4c Mon Sep 17 00:00:00 2001 From: malteish Date: Tue, 10 Sep 2024 11:38:25 +0200 Subject: [PATCH 44/49] output metrics as array --- .../src/persistence/metrics/getMetrics.ts | 15 +++++++-------- .../src/persistence/metrics/metricTypes.ts | 10 ++-------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.ts index 8b03c8a03..036f37bab 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.ts @@ -1,7 +1,6 @@ import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; import { Redis, RedisPrefix } from '../getDatabase'; import { getCurrentIntervalTimestamp } from './getCurrentIntervalTimestamp'; -import { MetricsObject } from './metricTypes'; /** * Get the metrics from the database, excluding the current interval. @@ -15,9 +14,9 @@ export function getMetrics( redis: Redis, ): ( deliveryServiceProperties: DeliveryServiceProperties, -) => Promise { +) => Promise { return async (deliveryServiceProperties: DeliveryServiceProperties) => { - const metrics: MetricsObject = {}; + const metrics: IntervalMetric[] = []; const messageCountKeys = await redis.keys( `${RedisPrefix.MetricsMessageCount}*`, ); @@ -34,9 +33,6 @@ export function getMetrics( continue; } - const date = new Date(timestamp * 1000); - const dateString = date.toISOString(); - const messageCount = await redis.get(key); const messageSizeBytes = await redis.get( `${RedisPrefix.MetricsMessageSize}${timestamp}`, @@ -45,11 +41,14 @@ export function getMetrics( `${RedisPrefix.MetricsNotificationCount}${timestamp}`, ); - metrics[dateString] = { + metrics.push({ + timestamp_start: timestamp, + duration_seconds: + deliveryServiceProperties.metricsCollectionIntervalInSeconds, messageCount: parseInt(messageCount || '0', 10), messageSizeBytes: parseInt(messageSizeBytes || '0', 10), notificationCount: parseInt(notificationCount || '0', 10), - }; + }); } return metrics; diff --git a/packages/delivery-service/src/persistence/metrics/metricTypes.ts b/packages/delivery-service/src/persistence/metrics/metricTypes.ts index 7c6885667..532d7ae1b 100644 --- a/packages/delivery-service/src/persistence/metrics/metricTypes.ts +++ b/packages/delivery-service/src/persistence/metrics/metricTypes.ts @@ -1,13 +1,7 @@ type IntervalMetric = { + timestamp_start: number; + duration_seconds: number; messageCount: number; messageSizeBytes: number; notificationCount: number; }; - -type MetricsMap = Map; - -export type { MetricsMap, IntervalMetric }; - -export interface MetricsObject { - [date: string]: IntervalMetric; -} From d04361bdc76835dc0f702b4d4faf3e63e3d33189 Mon Sep 17 00:00:00 2001 From: malteish Date: Tue, 10 Sep 2024 11:43:17 +0200 Subject: [PATCH 45/49] fix types --- packages/delivery-service/src/persistence/getDatabase.ts | 4 ++-- .../delivery-service/src/persistence/metrics/getMetrics.ts | 1 + packages/delivery-service/src/persistence/metrics/index.ts | 4 ++-- .../delivery-service/src/persistence/metrics/metricTypes.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/delivery-service/src/persistence/getDatabase.ts b/packages/delivery-service/src/persistence/getDatabase.ts index 88e0c18d3..24c23a738 100644 --- a/packages/delivery-service/src/persistence/getDatabase.ts +++ b/packages/delivery-service/src/persistence/getDatabase.ts @@ -15,7 +15,7 @@ import Account from './account'; import { getIdEnsName } from './getIdEnsName'; import Messages from './messages'; import { syncAcknowledge } from './messages/syncAcknowledge'; -import type { MetricsObject } from './metrics'; +import type { IntervalMetric } from './metrics'; import Metrics from './metrics'; import Notification from './notification'; import Otp from './otp'; @@ -168,7 +168,7 @@ export interface IDatabase extends IAccountDatabase { ) => Promise; getMetrics: ( deliveryServiceProperties: DeliveryServiceProperties, - ) => Promise; + ) => Promise; countMessage: ( messageSizeBytes: number, deliveryServiceProperties: DeliveryServiceProperties, diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.ts index 036f37bab..6bdff3d2f 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.ts @@ -1,6 +1,7 @@ import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; import { Redis, RedisPrefix } from '../getDatabase'; import { getCurrentIntervalTimestamp } from './getCurrentIntervalTimestamp'; +import { IntervalMetric } from './metricTypes'; /** * Get the metrics from the database, excluding the current interval. diff --git a/packages/delivery-service/src/persistence/metrics/index.ts b/packages/delivery-service/src/persistence/metrics/index.ts index 52101ae8b..769910d0a 100644 --- a/packages/delivery-service/src/persistence/metrics/index.ts +++ b/packages/delivery-service/src/persistence/metrics/index.ts @@ -1,5 +1,5 @@ import { getMetrics } from './getMetrics'; -import type { IntervalMetric, MetricsMap, MetricsObject } from './metricTypes'; +import type { IntervalMetric } from './metricTypes'; import { countMessage, countNotification } from './setMetrics'; export default { @@ -8,4 +8,4 @@ export default { countNotification, }; -export type { IntervalMetric, MetricsMap, MetricsObject }; +export type { IntervalMetric }; diff --git a/packages/delivery-service/src/persistence/metrics/metricTypes.ts b/packages/delivery-service/src/persistence/metrics/metricTypes.ts index 532d7ae1b..755f0ad73 100644 --- a/packages/delivery-service/src/persistence/metrics/metricTypes.ts +++ b/packages/delivery-service/src/persistence/metrics/metricTypes.ts @@ -1,4 +1,4 @@ -type IntervalMetric = { +export type IntervalMetric = { timestamp_start: number; duration_seconds: number; messageCount: number; From 5909efe8f2c4e7b64cdcdfbf039cf04ebd43a73b Mon Sep 17 00:00:00 2001 From: malteish Date: Tue, 10 Sep 2024 12:03:44 +0200 Subject: [PATCH 46/49] fix test --- .../persistence/metrics/getMetrics.test.ts | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts index 7243cf6ae..9026fcd3b 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts @@ -1,6 +1,5 @@ import { getMetrics } from './getMetrics'; import { Redis, RedisPrefix } from '../getDatabase'; -import { MetricsObject, IntervalMetric } from './metricTypes'; describe('getMetrics', () => { let mockRedis: jest.Mocked; @@ -19,13 +18,13 @@ describe('getMetrics', () => { } as unknown as jest.Mocked; }); - it('should return an empty object when no metrics are found', async () => { + it('should return an empty array when no metrics are found', async () => { mockRedis.keys.mockResolvedValue([]); const getMetricsFunc = getMetrics(mockRedis); const result = await getMetricsFunc(mockDeliveryServiceProperties); - expect(result).toEqual({}); + expect(result).toEqual([]); }); it('should return metrics for all available intervals', async () => { @@ -50,8 +49,10 @@ describe('getMetrics', () => { expect(mockRedis.keys).toHaveBeenCalledWith( `${RedisPrefix.MetricsMessageCount}*`, ); - expect(Object.keys(result)).toHaveLength(24); - expect(result['2023-03-31T08:00:00.000Z']).toEqual({ + expect(result).toHaveLength(24); + expect(result[0]).toEqual({ + timestamp_start: 1680307200, + duration_seconds: 3600, messageCount: 10, messageSizeBytes: 1000, notificationCount: 5, @@ -83,11 +84,10 @@ describe('getMetrics', () => { const result = await getMetricsFunction(mockDeliveryServiceProperties); console.log(result); - expect(Object.keys(result)).toHaveLength(1); - - expect(Object.keys(result)).toEqual(['2023-04-01T00:00:00.000Z']); - - expect(result['2023-04-01T00:00:00.000Z']).toEqual({ + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + timestamp_start: 1680307200, + duration_seconds: 3600, messageCount: 10, messageSizeBytes: 0, notificationCount: 5, @@ -106,8 +106,14 @@ describe('getMetrics', () => { const getMetricsFunction = getMetrics(mockRedis); const result = await getMetricsFunction(mockDeliveryServiceProperties); - expect(Object.keys(result)).toHaveLength(1); - expect(Object.keys(result)[0]).toBe('2023-04-01T11:00:00.000Z'); - expect(result).not.toHaveProperty(mockDate.toISOString()); + expect(result).toHaveLength(1); + expect(result[0].timestamp_start).toBe(currentTimestamp - 3600); // 2023-04-01T11:00:00.000Z + expect( + result.every( + (metric) => + metric.timestamp_start !== + Math.floor(mockDate.getTime() / 1000), + ), + ).toBe(true); }); }); From 5fa5ada2add325cb90ce6671851d8d9482b69fd4 Mon Sep 17 00:00:00 2001 From: malteish Date: Wed, 11 Sep 2024 10:19:15 +0200 Subject: [PATCH 47/49] unify capitalization in metrics --- .../src/persistence/metrics/getMetrics.test.ts | 12 ++++++------ .../src/persistence/metrics/getMetrics.ts | 4 ++-- .../src/persistence/metrics/metricTypes.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts index 9026fcd3b..9641fa518 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.test.ts @@ -51,8 +51,8 @@ describe('getMetrics', () => { ); expect(result).toHaveLength(24); expect(result[0]).toEqual({ - timestamp_start: 1680307200, - duration_seconds: 3600, + timestampStart: 1680307200, + durationSeconds: 3600, messageCount: 10, messageSizeBytes: 1000, notificationCount: 5, @@ -86,8 +86,8 @@ describe('getMetrics', () => { expect(result).toHaveLength(1); expect(result[0]).toEqual({ - timestamp_start: 1680307200, - duration_seconds: 3600, + timestampStart: 1680307200, + durationSeconds: 3600, messageCount: 10, messageSizeBytes: 0, notificationCount: 5, @@ -107,11 +107,11 @@ describe('getMetrics', () => { const result = await getMetricsFunction(mockDeliveryServiceProperties); expect(result).toHaveLength(1); - expect(result[0].timestamp_start).toBe(currentTimestamp - 3600); // 2023-04-01T11:00:00.000Z + expect(result[0].timestampStart).toBe(currentTimestamp - 3600); // 2023-04-01T11:00:00.000Z expect( result.every( (metric) => - metric.timestamp_start !== + metric.timestampStart !== Math.floor(mockDate.getTime() / 1000), ), ).toBe(true); diff --git a/packages/delivery-service/src/persistence/metrics/getMetrics.ts b/packages/delivery-service/src/persistence/metrics/getMetrics.ts index 6bdff3d2f..c54344ece 100644 --- a/packages/delivery-service/src/persistence/metrics/getMetrics.ts +++ b/packages/delivery-service/src/persistence/metrics/getMetrics.ts @@ -43,8 +43,8 @@ export function getMetrics( ); metrics.push({ - timestamp_start: timestamp, - duration_seconds: + timestampStart: timestamp, + durationSeconds: deliveryServiceProperties.metricsCollectionIntervalInSeconds, messageCount: parseInt(messageCount || '0', 10), messageSizeBytes: parseInt(messageSizeBytes || '0', 10), diff --git a/packages/delivery-service/src/persistence/metrics/metricTypes.ts b/packages/delivery-service/src/persistence/metrics/metricTypes.ts index 755f0ad73..7c11cb1df 100644 --- a/packages/delivery-service/src/persistence/metrics/metricTypes.ts +++ b/packages/delivery-service/src/persistence/metrics/metricTypes.ts @@ -1,6 +1,6 @@ export type IntervalMetric = { - timestamp_start: number; - duration_seconds: number; + timestampStart: number; + durationSeconds: number; messageCount: number; messageSizeBytes: number; notificationCount: number; From e608cca7c1a66dfc3379ca1065585fa89df736ad Mon Sep 17 00:00:00 2001 From: malteish Date: Wed, 11 Sep 2024 10:29:21 +0200 Subject: [PATCH 48/49] output csv instead of json on metrics endpoint --- packages/delivery-service/src/metrics.ts | 27 ++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/delivery-service/src/metrics.ts b/packages/delivery-service/src/metrics.ts index 4a415f39e..9fa2705d2 100644 --- a/packages/delivery-service/src/metrics.ts +++ b/packages/delivery-service/src/metrics.ts @@ -1,6 +1,8 @@ import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery'; import express from 'express'; import { IDatabase } from './persistence/getDatabase'; +import { IntervalMetric } from './persistence/metrics/metricTypes'; + /** * The metrics endpoint returns the metrics of the delivery service. * These can include the number of messages received, the cumulative size of messages received, etc., @@ -13,9 +15,30 @@ export default ( ) => { const router = express.Router(); router.get('', async (req, res) => { - const metrics = await db.getMetrics(deliveryServiceProperties); + const metrics: IntervalMetric[] = await db.getMetrics( + deliveryServiceProperties, + ); + + if (metrics.length === 0) { + return res.status(204).send('No metrics data available'); + } + + // Convert metrics to CSV format + const headers = Object.keys(metrics[0]); + const csvRows = metrics.map((metric) => + headers + .map((header) => metric[header as keyof IntervalMetric]) + .join(','), + ); + + const csvData = [headers.join(','), ...csvRows].join('\n'); - return res.status(200).send(JSON.stringify(metrics)); + res.setHeader('Content-Type', 'text/csv'); + res.setHeader( + 'Content-Disposition', + 'attachment; filename=metrics.csv', + ); + return res.status(200).send(csvData); }); return router; From 9bb0fb7ea18fdf1b6d5ce10109ad43b928ffe8a7 Mon Sep 17 00:00:00 2001 From: malteish Date: Wed, 11 Sep 2024 10:35:32 +0200 Subject: [PATCH 49/49] remove the file download header --- packages/delivery-service/src/metrics.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/delivery-service/src/metrics.ts b/packages/delivery-service/src/metrics.ts index 9fa2705d2..b86b0dbad 100644 --- a/packages/delivery-service/src/metrics.ts +++ b/packages/delivery-service/src/metrics.ts @@ -34,10 +34,10 @@ export default ( const csvData = [headers.join(','), ...csvRows].join('\n'); res.setHeader('Content-Type', 'text/csv'); - res.setHeader( - 'Content-Disposition', - 'attachment; filename=metrics.csv', - ); + // res.setHeader( + // 'Content-Disposition', + // 'attachment; filename=metrics.csv', + // ); return res.status(200).send(csvData); });