diff --git a/packages/js-sdk/src/Dm3.ts b/packages/js-sdk/src/Dm3.ts index 2e630e9b3..380f65eec 100644 --- a/packages/js-sdk/src/Dm3.ts +++ b/packages/js-sdk/src/Dm3.ts @@ -1,11 +1,11 @@ import { Conversations } from './conversation/Conversations'; -import { Tld } from './tld/Tld'; +import { ITLDResolver } from './tld/nameService/ITLDResolver'; export class Dm3 { public readonly conversations: Conversations; - public readonly tld: Tld; + public readonly tld: ITLDResolver; - constructor(conversations: Conversations, tld: Tld) { + constructor(conversations: Conversations, tld: ITLDResolver) { this.conversations = conversations; this.tld = tld; } diff --git a/packages/js-sdk/src/Dm3Sdk.test.ts b/packages/js-sdk/src/Dm3Sdk.test.ts index a7fc9d892..432dfc418 100644 --- a/packages/js-sdk/src/Dm3Sdk.test.ts +++ b/packages/js-sdk/src/Dm3Sdk.test.ts @@ -9,9 +9,11 @@ import { Dm3Sdk, Dm3SdkConfig } from './Dm3Sdk'; import MockAdapter from 'axios-mock-adapter'; import { normalizeEnsName } from '@dm3-org/dm3-lib-profile'; +import { ITLDResolver } from './tld/nameService/ITLDResolver'; describe('Dm3Sdk', () => { let alice: MockedUserProfile; + let bob: MockedUserProfile; //Axios mock to mock the http requests let axiosMock; @@ -22,9 +24,12 @@ describe('Dm3Sdk', () => { 'alice.up', ['test.io'], ); + bob = await mockUserProfile(ethers.Wallet.createRandom(), 'bob.up', [ + 'test.io', + ]); }); - it('test', async () => { + it('can add a conversaton to the contact list', async () => { axiosMock = new MockAdapter(axios); //Mock BackendConnector HttpRequests //Mock profileExistsOnDeliveryService @@ -52,15 +57,24 @@ describe('Dm3Sdk', () => { ) .reply(200, 'mock-challenge'); + const mockTldResolver = { + resolveTLDtoAlias: async () => + `${normalizeEnsName(bob.address)}.addr.test`, + resolveAliasToTLD: async () => 'bob.eth', + } as unknown as ITLDResolver; + const mockConfig: Dm3SdkConfig = { mainnetProvider: {} as ethers.providers.JsonRpcProvider, - storageApi: {} as StorageAPI, + storageApi: { + addConversation: async () => {}, + } as unknown as StorageAPI, nonce: '1', defaultDeliveryService: 'test.io', addressEnsSubdomain: '.addr.test', userEnsSubdomain: '.user.test', resolverBackendUrl: 'resolver.io', backendUrl: 'http://localhost:4060', + _tld: mockTldResolver, }; const dm3 = await new Dm3Sdk(mockConfig).login({ @@ -69,10 +83,10 @@ describe('Dm3Sdk', () => { accountAddress: alice.address, }); - console.log(dm3.conversations.conversations); - - /* await dm3.conversations.addConversation('karl.eth'); - const c = dm3.conversations.conversations; - const karl = c[0]; */ + await dm3.conversations.addConversation('bob.eth'); + const c = dm3.conversations.list; + console.log(c); + expect(c.length).toBe(1); + expect(c[0].contact.name).toBe('bob.eth'); }); }); diff --git a/packages/js-sdk/src/Dm3Sdk.ts b/packages/js-sdk/src/Dm3Sdk.ts index ab04c1c15..3ce98b8a2 100644 --- a/packages/js-sdk/src/Dm3Sdk.ts +++ b/packages/js-sdk/src/Dm3Sdk.ts @@ -13,6 +13,7 @@ import { StorageAPI } from '@dm3-org/dm3-lib-storage'; import { ethers } from 'ethers'; import { Tld } from './tld/Tld'; import { Dm3 } from './Dm3'; +import { ITLDResolver } from './tld/nameService/ITLDResolver'; /** * DM3SDK @@ -36,7 +37,7 @@ export interface Dm3SdkConfig { userEnsSubdomain: string; resolverBackendUrl: string; backendUrl: string; - lukso?: ethers.providers.ExternalProvider; + _tld?: ITLDResolver; } export class Dm3Sdk { @@ -69,6 +70,11 @@ export class Dm3Sdk { */ public conversations: Conversations; + /** + * DM3 TLD + */ + private _tld?: ITLDResolver; + constructor(config: Dm3SdkConfig) { //TODO keep ethers v5 for know but extract into common interface later this.mainnetProvider = config.mainnetProvider; @@ -80,6 +86,7 @@ export class Dm3Sdk { this.resolverBackendUrl = config.resolverBackendUrl; this.backendUrl = config.backendUrl; this.storageApi = config.storageApi; + this._tld = config._tld; } /** * login can be used to login with a profile regardles the connector. Its also great for testing @@ -93,12 +100,14 @@ export class Dm3Sdk { profile: SignedUserProfile; accountAddress: string; }) { - const tld = new Tld( - this.mainnetProvider, - this.addressEnsSubdomain, - this.userEnsSubdomain, - this.resolverBackendUrl, - ); + const tld = + this._tld ?? + new Tld( + this.mainnetProvider, + this.addressEnsSubdomain, + this.userEnsSubdomain, + this.resolverBackendUrl, + ); this.profileKeys = profileKeys; this.profile = profile; diff --git a/packages/js-sdk/src/conversation/Conversations.ts b/packages/js-sdk/src/conversation/Conversations.ts index 5d232da93..1b1085ebb 100644 --- a/packages/js-sdk/src/conversation/Conversations.ts +++ b/packages/js-sdk/src/conversation/Conversations.ts @@ -13,19 +13,20 @@ import { Contact, Conversation, getEmptyContact } from './types'; import { Tld } from '../tld/Tld'; import { hydrateContract as hydrateContact } from './hydrate/hydrateContact'; import { ethers } from 'ethers'; +import { ITLDResolver } from '../tld/nameService/ITLDResolver'; export class Conversations { private readonly provider: ethers.providers.JsonRpcProvider; private readonly storageApi: StorageAPI; - private readonly tld: Tld; + private readonly tld: ITLDResolver; private readonly addressEnsSubdomain: string; private readonly account: Account; - public conversations: Conversation[]; + public list: Conversation[]; constructor( storageApi: StorageAPI, - tld: Tld, + tld: ITLDResolver, mainnetProvider: ethers.providers.JsonRpcProvider, account: Account, addressEnsSubdomain: string, @@ -35,7 +36,7 @@ export class Conversations { this.account = account; this.provider = mainnetProvider; this.addressEnsSubdomain = addressEnsSubdomain; - this.conversations = []; + this.list = []; } public async addConversation(_ensName: string) { @@ -49,6 +50,7 @@ export class Conversations { updatedAt: new Date().getTime(), previewMessage: undefined, }; + const conversationPreview = this._addConversation(newConversation); //Add the contact to the storage in the background this.storageApi.addConversation(aliasName, [contactTldName]); @@ -64,7 +66,7 @@ export class Conversations { if (isOwnContact) { return; } - const alreadyAddedContact = this.conversations.find( + const alreadyAddedContact = this.list.find( (existingContact) => existingContact.contact.account.ensName === ensName, ); @@ -101,7 +103,7 @@ export class Conversations { const hydratedContact = await hydrateContact( this.provider, conversation, - this.tld.resolveTLDtoAlias, + this.tld.resolveAliasToTLD, this.addressEnsSubdomain, ); @@ -109,8 +111,13 @@ export class Conversations { messages: undefined as any, contact: hydratedContact, }; - this.conversations.push(hydratedConversation); - + //find existing contact and replace it with the hydrated one + this.list = this.list.map((existingContact) => { + if (existingContact.contact.account.ensName === ensName) { + return hydratedConversation; + } + return existingContact; + }); //Return the new onhydrated contact return hydratedConversation; } @@ -126,14 +133,14 @@ export class Conversations { const hydratedContact = await hydrateContact( this.provider, conversation, - this.tld.resolveTLDtoAlias, + this.tld.resolveAliasToTLD, this.addressEnsSubdomain, ); const hydratedConversation: Conversation = { messages: undefined as any, contact: hydratedContact, }; - this.conversations.push(hydratedConversation); + this.list.push(hydratedConversation); return hydratedConversation; }; @@ -142,12 +149,12 @@ export class Conversations { //Dont add duplicates const uniqueContacts = newConversations.filter( (newContact) => - !this.conversations.some( + !this.list.some( (existingContact) => existingContact.contact.account.ensName === newContact.contact.account.ensName, ), ); - this.conversations = [...this.conversations, ...uniqueContacts]; + this.list = [...this.list, ...uniqueContacts]; } } diff --git a/packages/js-sdk/src/conversation/hydrate/fetchDsProfiles.ts b/packages/js-sdk/src/conversation/hydrate/fetchDsProfiles.ts index 852285f63..8540a57e6 100644 --- a/packages/js-sdk/src/conversation/hydrate/fetchDsProfiles.ts +++ b/packages/js-sdk/src/conversation/hydrate/fetchDsProfiles.ts @@ -18,9 +18,9 @@ export const fetchDsProfiles = async ( ): Promise => { const deliveryServiceEnsNames = account.profile?.deliveryServices ?? []; if (deliveryServiceEnsNames.length === 0) { - //If there is nop DS profile the message will be storaged at the client side until they recipient has createed an account + //If there is no DS profile the message will be stored at the client side until the recipient has created an account console.debug( - '[fetchDeliverServicePorfile] Cant resolve deliveryServiceEnsName', + '[fetchDeliveryServiceProfile] account has no delivery-service profile', ); return { account, diff --git a/packages/js-sdk/src/message/Messages.ts b/packages/js-sdk/src/message/Messages.ts index 8ea3a018d..5eebdaff0 100644 --- a/packages/js-sdk/src/message/Messages.ts +++ b/packages/js-sdk/src/message/Messages.ts @@ -77,7 +77,7 @@ export class Messages { } //Find the recipient of the message in the contact list - const recipient = this.conversations.conversations.find( + const recipient = this.conversations.list.find( (c) => c.contact.account.ensName === contact, ); /** @@ -99,7 +99,7 @@ export class Messages { //There are cases were a messages is already to be send even though the contract hydration is not finished yet. //This happens if a message has been picked up from the delivery service and the clients sends READ_RECEIVE or READ_OPENED acknowledgements //In that case we've to check again to the if the user is a DM3 user, before we decide to keep the message - const potentialReceiver = this.conversations.conversations.find( + const potentialReceiver = this.conversations.list.find( (c) => c.contact.account.ensName === contact, ); diff --git a/packages/js-sdk/src/tld/Tld.ts b/packages/js-sdk/src/tld/Tld.ts index d4fa1b70d..23097b4bb 100644 --- a/packages/js-sdk/src/tld/Tld.ts +++ b/packages/js-sdk/src/tld/Tld.ts @@ -27,7 +27,7 @@ const SUPPORTED_NAMESERVICES = ( new EthAddressResolver(addressEnsSubdomain), ]; -export class Tld { +export class Tld implements ITLDResolver { private aliasTldCache: { [ensName: string]: string }; private tldAliasCache: { [ensName: string]: string }; private readonly mainnetProvider: ethers.providers.JsonRpcProvider; @@ -48,6 +48,17 @@ export class Tld { this.userEnsSubdomain = userEnsSubdomain; this.resolverBackendUrl = resolverBackendUrl; } + isResolverForTldName(ensName: string): Promise { + //Since its the root resolver its always capable of resolving + return Promise.resolve(true); + } + isResolverForAliasName( + ensName: string, + foreignTldName?: string, + ): Promise { + //Since its the root resolver its always capable of resolving + return Promise.resolve(true); + } //e.g. 0x1234.gnosis.eth -> 0x1234.gno resolveAliasToTLD = async (ensName: string, foreignTldName?: string) => { if (this.aliasTldCache[ensName]) {