From e1e1b4f2bd0e8f7a379406b4f5dc724f9492217f Mon Sep 17 00:00:00 2001 From: AlexNi245 Date: Wed, 31 Jul 2024 11:43:01 +0200 Subject: [PATCH 01/17] add encryptedContactTLDName field to prisma schema --- .../migrations/20240731092314_/migration.sql | 8 ++++++++ packages/backend/schema.prisma | 19 ++++++++++--------- .../backend/src/persistence/getDatabase.ts | 1 + .../storage/postgres/addConversation.ts | 14 ++++++++++++-- .../postgres/dto/ConversationRecord.ts | 2 ++ .../storage/postgres/getConversationList.ts | 1 + .../postgres/utils/getOrCreateConversation.ts | 2 ++ 7 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 packages/backend/migrations/20240731092314_/migration.sql diff --git a/packages/backend/migrations/20240731092314_/migration.sql b/packages/backend/migrations/20240731092314_/migration.sql new file mode 100644 index 000000000..80a699bc6 --- /dev/null +++ b/packages/backend/migrations/20240731092314_/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `encryptedContactTLDName` to the `Conversation` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Conversation" ADD COLUMN "encryptedContactTLDName" TEXT NOT NULL; diff --git a/packages/backend/schema.prisma b/packages/backend/schema.prisma index 38bb48aaa..7835c1e04 100644 --- a/packages/backend/schema.prisma +++ b/packages/backend/schema.prisma @@ -1,7 +1,7 @@ datasource db { //Use this URL for local development - //url = "postgresql://prisma:prisma@localhost:5433/tests" - url = env("DATABASE_URL") + url = "postgresql://prisma:prisma@localhost:5433/tests" + //url = env("DATABASE_URL") provider = "postgresql" } @@ -22,13 +22,14 @@ model EncryptedMessage { } model Conversation { - id String @id @default(uuid()) - updatedAt DateTime @default(now()) - encryptedContactName String - Message EncryptedMessage[] - Account Account @relation(fields: [accountId], references: [id]) - accountId String - isHidden Boolean @default(false) + id String @id @default(uuid()) + updatedAt DateTime @default(now()) + encryptedContactName String + encryptedContactTLDName String + Message EncryptedMessage[] + Account Account @relation(fields: [accountId], references: [id]) + accountId String + isHidden Boolean @default(false) } model Account { diff --git a/packages/backend/src/persistence/getDatabase.ts b/packages/backend/src/persistence/getDatabase.ts index 6832e7671..57120aaf8 100644 --- a/packages/backend/src/persistence/getDatabase.ts +++ b/packages/backend/src/persistence/getDatabase.ts @@ -97,6 +97,7 @@ export interface IDatabase extends IAccountDatabase { addConversation: ( ensName: string, encryptedContactName: string, + encryptedContactTLDNameL: string, ) => Promise; getConversationList: ( ensName: string, diff --git a/packages/backend/src/persistence/storage/postgres/addConversation.ts b/packages/backend/src/persistence/storage/postgres/addConversation.ts index b9fcf4612..b6b8c7559 100644 --- a/packages/backend/src/persistence/storage/postgres/addConversation.ts +++ b/packages/backend/src/persistence/storage/postgres/addConversation.ts @@ -2,10 +2,20 @@ import { PrismaClient } from '@prisma/client'; import { getOrCreateAccount } from './utils/getOrCreateAccount'; import { getOrCreateConversation } from './utils/getOrCreateConversation'; export const addConversation = - (db: PrismaClient) => async (ensName: string, contactName: string) => { + (db: PrismaClient) => + async ( + ensName: string, + contactName: string, + encryptedContactTLDName: string, + ) => { try { const account = await getOrCreateAccount(db, ensName); - await getOrCreateConversation(db, account.id, contactName); + await getOrCreateConversation( + db, + account.id, + contactName, + encryptedContactTLDName, + ); return true; } catch (e) { console.log('addConversation error ', e); diff --git a/packages/backend/src/persistence/storage/postgres/dto/ConversationRecord.ts b/packages/backend/src/persistence/storage/postgres/dto/ConversationRecord.ts index 4c358ad09..a4da36203 100644 --- a/packages/backend/src/persistence/storage/postgres/dto/ConversationRecord.ts +++ b/packages/backend/src/persistence/storage/postgres/dto/ConversationRecord.ts @@ -5,4 +5,6 @@ export type ConversationRecord = { previewMessage: string | null; //The time the conversation was last updated updatedAt: Date; + //This field can be used by the client to store information about the contacts TLD name + encryptedContactTLDName: string; }; diff --git a/packages/backend/src/persistence/storage/postgres/getConversationList.ts b/packages/backend/src/persistence/storage/postgres/getConversationList.ts index ddb965f7c..18ed8cb96 100644 --- a/packages/backend/src/persistence/storage/postgres/getConversationList.ts +++ b/packages/backend/src/persistence/storage/postgres/getConversationList.ts @@ -53,5 +53,6 @@ export const getConversationList = previewMessage: previewMessages[idx]?.encryptedEnvelopContainer ?? null, updatedAt: c.updatedAt, + encryptedContactTLDName: c.encryptedContactTLDName, })); }; diff --git a/packages/backend/src/persistence/storage/postgres/utils/getOrCreateConversation.ts b/packages/backend/src/persistence/storage/postgres/utils/getOrCreateConversation.ts index 2c73acc7c..7930347f7 100644 --- a/packages/backend/src/persistence/storage/postgres/utils/getOrCreateConversation.ts +++ b/packages/backend/src/persistence/storage/postgres/utils/getOrCreateConversation.ts @@ -4,6 +4,7 @@ export const getOrCreateConversation = async ( db: PrismaClient, accountId: string, encryptedContactName: string, + encryptedContactTLDName: string, ) => { //Check if conversation already exists const conversation = await db.conversation.findFirst({ @@ -20,6 +21,7 @@ export const getOrCreateConversation = async ( return await db.conversation.create({ data: { accountId, + encryptedContactTLDName, encryptedContactName, //Internal field to order conversations properly //Will set whenever a conversation is created or a message is added From 3e383481830286c9cd9e3626926efff5599d367c Mon Sep 17 00:00:00 2001 From: AlexNi245 Date: Wed, 31 Jul 2024 13:00:10 +0200 Subject: [PATCH 02/17] use default arg for getOrCreateConversation --- .../postgres/utils/getOrCreateConversation.ts | 2 +- packages/backend/src/storage.test.ts | 26 +++++++++++++++++++ packages/backend/src/storage.ts | 6 ++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/persistence/storage/postgres/utils/getOrCreateConversation.ts b/packages/backend/src/persistence/storage/postgres/utils/getOrCreateConversation.ts index 7930347f7..b42ce635c 100644 --- a/packages/backend/src/persistence/storage/postgres/utils/getOrCreateConversation.ts +++ b/packages/backend/src/persistence/storage/postgres/utils/getOrCreateConversation.ts @@ -4,7 +4,7 @@ export const getOrCreateConversation = async ( db: PrismaClient, accountId: string, encryptedContactName: string, - encryptedContactTLDName: string, + encryptedContactTLDName: string = '', ) => { //Check if conversation already exists const conversation = await db.conversation.findFirst({ diff --git a/packages/backend/src/storage.test.ts b/packages/backend/src/storage.test.ts index 3635eeba9..f3b35d341 100644 --- a/packages/backend/src/storage.test.ts +++ b/packages/backend/src/storage.test.ts @@ -150,6 +150,32 @@ describe('Storage', () => { expect(body[0].contact).toEqual(aliceId); expect(body.length).toBe(1); }); + it('can add conversation with encryptedContactTLDName', async () => { + const aliceId = 'alice.eth'; + + const { status } = await request(app) + .post(`/new/bob.eth/addConversation`) + .set({ + authorization: 'Bearer ' + token, + }) + .send({ + encryptedContactName: aliceId, + encryptedContactTLDName: '123', + }); + expect(status).toBe(200); + + const { body } = await request(app) + .get(`/new/bob.eth/getConversations`) + .set({ + authorization: 'Bearer ' + token, + }) + .send(); + + expect(status).toBe(200); + expect(body[0].contact).toEqual(aliceId); + expect(body[0].encryptedContactTLDName).toEqual('123'); + expect(body.length).toBe(1); + }); it('handle duplicates add conversation', async () => { const aliceId = 'alice.eth'; const ronId = 'ron.eth'; diff --git a/packages/backend/src/storage.ts b/packages/backend/src/storage.ts index 5f0f34839..c656f2fea 100644 --- a/packages/backend/src/storage.ts +++ b/packages/backend/src/storage.ts @@ -196,16 +196,20 @@ export default ( ); router.post('/new/:ensName/addConversation', async (req, res, next) => { - const { encryptedContactName } = req.body; + const { encryptedContactName, encryptedContactTLDName } = req.body; if (!encryptedContactName) { res.status(400).send('invalid schema'); return; } + + //Param encryptedContactTLDName is optional, hence the default value is an empty string + const _encryptedContactTLDName = encryptedContactTLDName || ''; try { const ensName = normalizeEnsName(req.params.ensName); const success = await db.addConversation( ensName, encryptedContactName, + _encryptedContactTLDName, ); if (success) { return res.send(); From 242d0a8fa8f9f84c2b72843af06e5d7560c9ef9d Mon Sep 17 00:00:00 2001 From: AlexNi245 Date: Wed, 31 Jul 2024 13:00:33 +0200 Subject: [PATCH 03/17] get rid of some logging in Session.ts --- packages/lib/delivery/src/Session.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/lib/delivery/src/Session.ts b/packages/lib/delivery/src/Session.ts index 196f65a88..3e68f82cd 100644 --- a/packages/lib/delivery/src/Session.ts +++ b/packages/lib/delivery/src/Session.ts @@ -45,9 +45,6 @@ export async function checkToken( return false; } - console.debug('checkToken - ensName', ensName); - console.debug('checkToken - session', session); - // check jwt for validity try { // will throw if signature is invalid or exp is in the past @@ -55,8 +52,6 @@ export async function checkToken( algorithms: ['HS256'], }); - console.debug('checkToken - jwtPayload', jwtPayload); - // check if payload is well formed if ( typeof jwtPayload === 'string' || From eaee6787ffce9e27c9e60d08f5d857e9a7582fa5 Mon Sep 17 00:00:00 2001 From: AlexNi245 Date: Thu, 1 Aug 2024 16:40:00 +0200 Subject: [PATCH 04/17] use DATABASE_URL in prisma --- packages/backend/schema.prisma | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/schema.prisma b/packages/backend/schema.prisma index 7835c1e04..51144e04d 100644 --- a/packages/backend/schema.prisma +++ b/packages/backend/schema.prisma @@ -1,7 +1,7 @@ datasource db { //Use this URL for local development - url = "postgresql://prisma:prisma@localhost:5433/tests" - //url = env("DATABASE_URL") + //url = "postgresql://prisma:prisma@localhost:5433/tests" + url = env("DATABASE_URL") provider = "postgresql" } From e1de39addd6064cc7497374c61945c87d7538f7f Mon Sep 17 00:00:00 2001 From: AlexNi245 Date: Thu, 1 Aug 2024 18:07:19 +0200 Subject: [PATCH 05/17] addConversation store tldAliasNAme in storage --- packages/lib/shared/src/IBackendConnector.ts | 7 +++- .../src/new/cloudStorage/getCloudStorage.ts | 21 +++++++++++- packages/lib/storage/src/new/types.ts | 9 ++++-- .../AddConversation/AddConversation.tsx | 28 ++++++++-------- .../ContactMenu/ContactMenu.test.tsx | 1 + .../src/context/BackendContext.tsx | 7 +++- .../src/context/ConversationContext.tsx | 4 +-- .../src/context/StorageContext.tsx | 2 +- .../getMockedConversationContext.ts | 2 +- .../testHelper/getMockedStorageContext.ts | 1 + .../src/hooks/conversation/hydrateContact.ts | 2 ++ .../conversation/useConversation.test.tsx | 18 +++++++++++ .../hooks/conversation/useConversation.tsx | 20 ++++++++---- .../handleMessagesFromDeliveryService.ts | 9 +++--- .../sources/handleMessagesFromWebSocket.ts | 9 ++++-- .../src/hooks/messages/useMessage.test.tsx | 32 ++++++++++++++++--- .../src/hooks/server-side/BackendConnector.ts | 2 ++ .../src/hooks/server-side/useBackend.ts | 12 +++++-- .../src/hooks/storage/useStorage.tsx | 12 +++++-- .../messenger-widget/src/interfaces/utils.ts | 3 ++ 20 files changed, 156 insertions(+), 45 deletions(-) diff --git a/packages/lib/shared/src/IBackendConnector.ts b/packages/lib/shared/src/IBackendConnector.ts index d62627860..32d2d3f7c 100644 --- a/packages/lib/shared/src/IBackendConnector.ts +++ b/packages/lib/shared/src/IBackendConnector.ts @@ -1,5 +1,9 @@ export interface IBackendConnector { - addConversation(ensName: string, encryptedContactName: string): void; + addConversation( + ensName: string, + encryptedContactName: string, + encryptedContactTLDName: string, + ): void; getConversations( ensName: string, size: number, @@ -7,6 +11,7 @@ export interface IBackendConnector { ): Promise< { contact: string; + encryptedContactTLDName: string; previewMessage: string; updatedAt: Date; }[] diff --git a/packages/lib/storage/src/new/cloudStorage/getCloudStorage.ts b/packages/lib/storage/src/new/cloudStorage/getCloudStorage.ts index 580d39ac3..88934ccb5 100644 --- a/packages/lib/storage/src/new/cloudStorage/getCloudStorage.ts +++ b/packages/lib/storage/src/new/cloudStorage/getCloudStorage.ts @@ -8,13 +8,23 @@ export const getCloudStorage = ( ensName: string, encryption: Encryption, ): StorageAPI => { - const _addConversation = async (contactEnsName: string) => { + const _addConversation = async ( + contactEnsName: string, + contactTldNames: string[], + ) => { const encryptedContactName = await encryption.encryptSync( contactEnsName, ); + + const encryptedContactTLDName = await encryption.encryptSync( + JSON.stringify(contactTldNames), + ); + + console.log('add contact ', contactEnsName, contactTldNames); return await backendConnector.addConversation( ensName, encryptedContactName, + encryptedContactTLDName, ); }; @@ -29,14 +39,23 @@ export const getCloudStorage = ( conversations.map( async ({ contact, + encryptedContactTLDName, previewMessage, updatedAt, }: { contact: string; + encryptedContactTLDName: string; previewMessage: string | null; updatedAt: Date; }) => ({ contactEnsName: await encryption.decryptSync(contact), + contactTldNames: encryptedContactTLDName + ? JSON.parse( + await encryption.decryptSync( + encryptedContactTLDName, + ), + ) + : [], isHidden: false, messageCounter: 0, previewMessage: previewMessage diff --git a/packages/lib/storage/src/new/types.ts b/packages/lib/storage/src/new/types.ts index 5c7401267..2b769e6eb 100644 --- a/packages/lib/storage/src/new/types.ts +++ b/packages/lib/storage/src/new/types.ts @@ -22,7 +22,10 @@ export interface StorageAPI { ) => Promise; getNumberOfMessages: (contactEnsName: string) => Promise; getNumberOfConverations: () => Promise; - addConversation: (contactEnsName: string) => Promise; + addConversation: ( + contactEnsName: string, + contactTldNames: string[], + ) => Promise; addMessage: ( contactEnsName: string, envelop: StorageEnvelopContainer, @@ -40,8 +43,10 @@ export interface StorageEnvelopContainer { } export interface Conversation { - //the contactEnsName is the ensName of the contact + //the contactEnsName is the ensName of the contact used as the id of the conversation contactEnsName: string; + //The contact might have certain tld associated with it + contactTldNames: string[]; //the previewMessage is the last message of the conversation previewMessage?: StorageEnvelopContainer; //isHidden is a flag to hide the conversation from the conversation list diff --git a/packages/messenger-widget/src/components/AddConversation/AddConversation.tsx b/packages/messenger-widget/src/components/AddConversation/AddConversation.tsx index c019e844f..9a943d78e 100644 --- a/packages/messenger-widget/src/components/AddConversation/AddConversation.tsx +++ b/packages/messenger-widget/src/components/AddConversation/AddConversation.tsx @@ -31,7 +31,7 @@ export default function AddConversation() { setAddConversation, } = useContext(ModalContext); - const [name, setName] = useState(''); + const [tldName, setName] = useState(''); const [showError, setShowError] = useState(false); const [errorMsg, setErrorMsg] = useState(''); const [inputClass, setInputClass] = useState(INPUT_FIELD_CLASS); @@ -39,28 +39,30 @@ export default function AddConversation() { // handles new contact submission const submit = async (e: React.FormEvent) => { e.preventDefault(); - setName(name.trim()); - if (name.length) { + setName(tldName.trim()); + if (tldName.length) { // start loader setLoaderContent('Adding contact...'); startLoader(); const ensNameIsInvalid = ethAddress && - name.split('.')[0] && - ethAddress.toLowerCase() === name.split('.')[0].toLowerCase(); + tldName.split('.')[0] && + ethAddress.toLowerCase() === + tldName.split('.')[0].toLowerCase(); if (ensNameIsInvalid) { setErrorMsg('Please enter valid ENS name'); setShowError(true); return; } - //Checks wether the name entered, is an tld name. If yes, the TLD is substituded with the alias name - const aliasName = await resolveTLDtoAlias(name); + + //TODO resolve TLD name + const newContact = await addConversation(tldName); const addConversationData = { active: true, - ensName: aliasName, + ensName: newContact?.contactDetails.account.ensName, processed: false, }; @@ -72,8 +74,6 @@ export default function AddConversation() { // set right view to chat setSelectedRightView(RightViewSelected.Chat); - - const newContact = await addConversation(aliasName); if (!newContact) { //Maybe show a message that its not possible to add the users address as a contact setShowAddConversationModal(false); @@ -171,7 +171,7 @@ export default function AddConversation() { )} type="text" placeholder="Enter the name or address of the contact" - value={name} + value={tldName} onChange={( e: React.ChangeEvent, ) => handleNameChange(e)} @@ -188,10 +188,12 @@ export default function AddConversation() {