Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dsfe 2 send messages to all ds #1018

Merged
merged 6 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/lib/messaging/src/Envelop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export interface Envelop {
id?: string;
}

// And envelop that has been encrypted and is ready to be sent
export type DispatchableEnvelop = {
encryptedEnvelop: EncryptionEnvelop;
envelop: Envelop;
deliveryServiceUrl: string;
};

export interface DeliveryInformation {
to: string;
from: string;
Expand Down Expand Up @@ -111,7 +118,7 @@ export async function buildEnvelop(
encryptAsymmetric: EncryptAsymmetric,
{ to, from, deliverServiceProfile, keys }: SendDependencies,
preEncryptedMessage?: string,
): Promise<{ encryptedEnvelop: EncryptionEnvelop; envelop: Envelop }> {
): Promise<DispatchableEnvelop> {
if (!to.profile) {
throw Error('Contact has no profile');
}
Expand Down Expand Up @@ -168,6 +175,9 @@ export async function buildEnvelop(
message,
metadata: { ...metadata, deliveryInformation },
},
//Add the deliveryServiceUrl to the envelop, so it becomes clear where the encrypted message should be sent.
//Important when having different deliveryServices because and enevlop sent to the wrong url will be rejected
deliveryServiceUrl: deliverServiceProfile.url,
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/lib/messaging/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type {
DispatchableEnvelop,
DeliveryInformation,
EncryptionEnvelop,
Envelop,
Expand Down
44 changes: 27 additions & 17 deletions packages/messenger-widget/src/hooks/conversation/hydrateContact.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import {
Account,
DeliveryServiceProfile,
getDeliveryServiceProfile,
getUserProfile,
normalizeEnsName,
Expand All @@ -12,32 +12,31 @@ import { Contact } from '../../interfaces/context';
import { ContactPreview } from '../../interfaces/utils';
import { getAvatarProfilePic } from '../../utils/ens-utils';
import { fetchMessageSizeLimit } from '../messages/sizeLimit/fetchSizeLimit';
import { DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery';

export const hydrateContract = async (
provider: ethers.providers.JsonRpcProvider,
conversatoinManifest: Conversation,
resolveAliasToTLD: (alias: string) => Promise<string>,
addrEnsSubdomain: string,
deliveryServiceProperties: DeliveryServiceProperties[],
) => {
//If the profile property of the account is defined the user has already used DM3 previously
const account = await fetchAccount(
provider,
conversatoinManifest.contactEnsName,
);
//Has to become fetchMultipleDsProfiles
const contact = await fetchDsProfile(provider, account);
//Fetch the message size limit of the receivers delivery service must be fetched for every message
const messageSizeLimit = await fetchMessageSizeLimit(
deliveryServiceProperties,
const contact = await fetchDsProfiles(provider, account);

//get the maximum size limit by looking for the smallest size limit of every ds
const maximumSizeLimit = await fetchMessageSizeLimit(
contact.deliveryServiceProfiles,
);
const contactPreview = await fetchPreview(
provider,
conversatoinManifest,
contact,
resolveAliasToTLD,
messageSizeLimit,
maximumSizeLimit,
addrEnsSubdomain,
);
return contactPreview;
Expand Down Expand Up @@ -98,29 +97,40 @@ const fetchAccount = async (
}
};

const fetchDsProfile = async (
const fetchDsProfiles = async (
provider: ethers.providers.JsonRpcProvider,
account: Account,
): Promise<Contact> => {
const deliveryServiceEnsName = account.profile?.deliveryServices[0];
if (!deliveryServiceEnsName) {
const deliveryServiceEnsNames = account.profile?.deliveryServices ?? [];
if (deliveryServiceEnsNames.length === 0) {
//If there is now DS profile the message will be storaged at the client side until they recipient has createed an account
console.log(
console.debug(
'[fetchDeliverServicePorfile] Cant resolve deliveryServiceEnsName',
);
return {
account,
deliveryServiceProfiles: [],
};
}

const deliveryServiceProfile = await getDeliveryServiceProfile(
deliveryServiceEnsName,
provider!,
async (url: string) => (await axios.get(url)).data,
//Resolve every ds profile in the contacts profile
const dsProfilesWithUnknowns = await Promise.all(
deliveryServiceEnsNames.map((deliveryServiceEnsName: string) => {
console.debug('fetch ds profile of', deliveryServiceEnsName);
return getDeliveryServiceProfile(
deliveryServiceEnsName,
provider!,
async (url: string) => (await axios.get(url)).data,
);
}),
);
//filter unknown profiles. A profile if unknown if the profile could not be fetched. We don't want to deal with them in the UI
const deliveryServiceProfiles = dsProfilesWithUnknowns.filter(
(profile): profile is DeliveryServiceProfile => profile !== undefined,
);

return {
account,
deliveryServiceProfile,
deliveryServiceProfiles,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,49 @@ import { getMockedStorageContext } from '../../context/testHelper/getMockedStora
import { getMockedTldContext } from '../../context/testHelper/getMockedTldContext';
import { DM3Configuration } from '../../widget';
import { useConversation } from './useConversation';
import {
MainnetProviderContext,
MainnetProviderContextType,
} from '../../context/ProviderContext';
import { getMockedMainnetProviderContext } from '../../context/testHelper/getMockedMainnetProviderContext';
import { ethers } from 'ethers';
import {
MockDeliveryServiceProfile,
MockedUserProfile,
getMockDeliveryServiceProfile,
mockUserProfile,
} from '@dm3-org/dm3-lib-test-helper';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';

describe('useConversation hook test cases', () => {
let sender: MockedUserProfile;
let receiver: MockedUserProfile;
let ds1: MockDeliveryServiceProfile;
let ds2: MockDeliveryServiceProfile;

let axiosMock: MockAdapter;

beforeEach(async () => {
sender = await mockUserProfile(
ethers.Wallet.createRandom(),
'alice.eth',
['ds1.eth', 'ds2.eth'],
);
receiver = await mockUserProfile(
ethers.Wallet.createRandom(),
'bob.eth',
['ds1.eth'],
);
ds1 = await getMockDeliveryServiceProfile(
ethers.Wallet.createRandom(),
'http://ds1.api',
);
ds2 = await getMockDeliveryServiceProfile(
ethers.Wallet.createRandom(),
'http://ds2.api',
);
});
const CONTACT_NAME = 'user.dm3.eth';

const configurationContext = getMockedDm3Configuration({
Expand Down Expand Up @@ -752,4 +793,195 @@ describe('useConversation hook test cases', () => {
);
});
});

describe('hydrate contact', () => {
it('fetches all deliveryService profiles of one contact', async () => {
const authContext: AuthContextType = getMockedAuthContext({
account: {
ensName: receiver.account.ensName,
profile: receiver.signedUserProfile.profile,
},
});

const storageContext: StorageContextType = getMockedStorageContext({
getConversations: function (
page: number,
): Promise<Conversation[]> {
return Promise.resolve([
{
contactEnsName: sender.account.ensName,
isHidden: false,
messageCounter: 1,
},
]);
},
initialized: true,
});
const deliveryServiceContext: DeliveryServiceContextType =
getMockedDeliveryServiceContext({
fetchIncommingMessages: function (ensName: string) {
return Promise.resolve([]);
},
getDeliveryServiceProperties: function (): Promise<any[]> {
return Promise.resolve([{ sizeLimit: 0 }]);
},
isInitialized: true,
});
const mockProvider = {
resolveName: () => {
return Promise.resolve(sender.address);
},
getResolver: (ensName: string) => {
if (ensName === sender.account.ensName) {
return {
getText: () => sender.stringified,
} as unknown as ethers.providers.Resolver;
}
if (ensName === 'ds1.eth') {
return {
getText: () => ds1.stringified,
} as unknown as ethers.providers.Resolver;
}
if (ensName === 'ds2.eth') {
return {
getText: () => ds2.stringified,
} as unknown as ethers.providers.Resolver;
}

throw new Error(`mock provider unknown ensName ${ensName}`);
},
} as any as ethers.providers.JsonRpcProvider;

const mainnetProvderContext: MainnetProviderContextType =
getMockedMainnetProviderContext({
provider: mockProvider,
});

const wrapper = ({ children }: { children: any }) => (
<>
<MainnetProviderContext.Provider
value={mainnetProvderContext}
>
<AuthContext.Provider value={authContext}>
<StorageContext.Provider value={storageContext}>
<DeliveryServiceContext.Provider
value={deliveryServiceContext}
>
{children}
</DeliveryServiceContext.Provider>
</StorageContext.Provider>
</AuthContext.Provider>
</MainnetProviderContext.Provider>
</>
);

const { result } = renderHook(() => useConversation(config), {
wrapper,
});
await waitFor(() => expect(result.current.initialized).toBe(true));

expect(
result.current.contacts[0].contactDetails
.deliveryServiceProfiles[0],
).toStrictEqual(ds1.deliveryServiceProfile);

expect(
result.current.contacts[0].contactDetails
.deliveryServiceProfiles[1],
).toStrictEqual(ds2.deliveryServiceProfile);
});
it('fetches the sizeLimit of every deliveryService', async () => {
axiosMock = new MockAdapter(axios);
axiosMock.onPost('http://ds1.api/rpc').reply(200, {
result: 1000,
});
axiosMock.onPost('http://ds2.api/rpc').reply(200, {
result: 2000,
});

const authContext: AuthContextType = getMockedAuthContext({
account: {
ensName: receiver.account.ensName,
profile: receiver.signedUserProfile.profile,
},
});

const storageContext: StorageContextType = getMockedStorageContext({
getConversations: function (
page: number,
): Promise<Conversation[]> {
return Promise.resolve([
{
contactEnsName: sender.account.ensName,
isHidden: false,
messageCounter: 1,
},
]);
},
initialized: true,
});
const deliveryServiceContext: DeliveryServiceContextType =
getMockedDeliveryServiceContext({
fetchIncommingMessages: function (ensName: string) {
return Promise.resolve([]);
},
isInitialized: true,
});
const mockProvider = {
resolveName: () => {
return Promise.resolve(sender.address);
},
getResolver: (ensName: string) => {
if (ensName === sender.account.ensName) {
return {
getText: () => sender.stringified,
} as unknown as ethers.providers.Resolver;
}
if (ensName === 'ds1.eth') {
return {
getText: () => ds1.stringified,
} as unknown as ethers.providers.Resolver;
}
if (ensName === 'ds2.eth') {
return {
getText: () => ds2.stringified,
} as unknown as ethers.providers.Resolver;
}

throw new Error(`mock provider unknown ensName ${ensName}`);
},
} as any as ethers.providers.JsonRpcProvider;

const mainnetProvderContext: MainnetProviderContextType =
getMockedMainnetProviderContext({
provider: mockProvider,
});

const wrapper = ({ children }: { children: any }) => (
<>
<MainnetProviderContext.Provider
value={mainnetProvderContext}
>
<AuthContext.Provider value={authContext}>
<StorageContext.Provider value={storageContext}>
<DeliveryServiceContext.Provider
value={deliveryServiceContext}
>
{children}
</DeliveryServiceContext.Provider>
</StorageContext.Provider>
</AuthContext.Provider>
</MainnetProviderContext.Provider>
</>
);

const { result } = renderHook(() => useConversation(config), {
wrapper,
});
await waitFor(() => expect(result.current.initialized).toBe(true));

//1000 is the sizelimit of the DS with the loweset tolerance. This should be set as the messageSizeLimit
expect(result.current.contacts[0].messageSizeLimit).toEqual(1000);
});
});
});
Loading
Loading