From 8c4a1e402f60fab18af120d8b9a8e04899ace964 Mon Sep 17 00:00:00 2001 From: Lucas Leblow Date: Thu, 21 Mar 2024 09:17:21 -0700 Subject: [PATCH] fix: Call createNetwork from registerUsername to prevent UI delay --- .../CreateCommunity/CreateCommunity.tsx | 6 +- .../JoinCommunity/JoinCommunity.tsx | 6 +- .../CreateUsername/CreateUsername.tsx | 7 +- .../invitation/customProtocol.saga.test.ts | 18 +-- .../sagas/invitation/customProtocol.saga.ts | 6 +- .../src/rtl-tests/community.create.test.tsx | 5 +- .../src/rtl-tests/community.join.test.tsx | 5 +- .../src/rtl-tests/deep.linking.test.tsx | 3 +- .../src/rtl-tests/loadingPanel.test.tsx | 16 +++ .../src/integrationTests/appActions.ts | 14 +-- .../src/testUtils/actions.ts | 12 +- .../CreateCommunity.screen.tsx | 6 +- .../JoinCommunity/JoinCommunity.screen.tsx | 6 +- .../store/init/deepLink/deepLink.saga.test.ts | 10 +- .../src/store/init/deepLink/deepLink.saga.ts | 6 +- .../addCommunity/addCommunity.saga.test.ts | 114 ++++++++++++++++++ .../addCommunity/addCommunity.saga.ts | 53 ++++++++ .../communities/communities.master.saga.ts | 2 + .../sagas/communities/communities.slice.ts | 11 +- .../sagas/communities/communities.types.ts | 2 +- .../createNetwork/createNetwork.saga.test.ts | 101 +++++----------- .../createNetwork/createNetwork.saga.ts | 50 ++------ .../registerUsername/registerUsername.saga.ts | 3 + .../src/utils/cryptography/cryptography.ts | 9 -- packages/types/src/community.ts | 2 +- 25 files changed, 295 insertions(+), 178 deletions(-) create mode 100644 packages/state-manager/src/sagas/communities/addCommunity/addCommunity.saga.test.ts create mode 100644 packages/state-manager/src/sagas/communities/addCommunity/addCommunity.saga.ts diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.tsx index 006c3add8e..13aafd6af9 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { socketSelectors } from '../../../sagas/socket/socket.selectors' import { communities, identity } from '@quiet/state-manager' -import { CommunityOwnership, CreateNetworkPayload } from '@quiet/types' +import { CommunityOwnership, AddCommunityPayload } from '@quiet/types' import PerformCommunityActionComponent from '../PerformCommunityActionComponent' import { ModalName } from '../../../sagas/modals/modals.types' import { useModal } from '../../../containers/hooks' @@ -25,11 +25,11 @@ const CreateCommunity = () => { }, [currentCommunity]) const handleCommunityAction = (name: string) => { - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.Owner, name: name, } - dispatch(communities.actions.createNetwork(payload)) + dispatch(communities.actions.addCommunity(payload)) } // From 'You can join a community instead' link diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx index d4a63352ab..128d1afc7c 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { socketSelectors } from '../../../sagas/socket/socket.selectors' -import { CommunityOwnership, CreateNetworkPayload, InvitationData, InvitationPair } from '@quiet/types' +import { CommunityOwnership, AddCommunityPayload, InvitationData, InvitationPair } from '@quiet/types' import { communities, identity, connection, network } from '@quiet/state-manager' import PerformCommunityActionComponent from '../../../components/CreateJoinCommunity/PerformCommunityActionComponent' import { ModalName } from '../../../sagas/modals/modals.types' @@ -39,13 +39,13 @@ const JoinCommunity = () => { }, [currentCommunity]) const handleCommunityAction = (data: InvitationData) => { - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.User, peers: data.pairs, psk: data.psk, ownerOrbitDbIdentity: data.ownerOrbitDbIdentity, } - dispatch(communities.actions.createNetwork(payload)) + dispatch(communities.actions.addCommunity(payload)) } // From 'You can create a new community instead' link diff --git a/packages/desktop/src/renderer/components/CreateUsername/CreateUsername.tsx b/packages/desktop/src/renderer/components/CreateUsername/CreateUsername.tsx index 87f5fe18ee..b4085f1670 100644 --- a/packages/desktop/src/renderer/components/CreateUsername/CreateUsername.tsx +++ b/packages/desktop/src/renderer/components/CreateUsername/CreateUsername.tsx @@ -25,12 +25,7 @@ const CreateUsername = () => { }, [currentIdentity, currentCommunity]) const registerUsername = (nickname: string) => { - dispatch( - identity.actions.registerUsername({ - nickname, - }) - ) - + dispatch(identity.actions.registerUsername({ nickname })) dispatch(network.actions.setLoadingPanelType(LoadingPanelType.Joining)) loadingPanelModal.handleOpen() } diff --git a/packages/desktop/src/renderer/sagas/invitation/customProtocol.saga.test.ts b/packages/desktop/src/renderer/sagas/invitation/customProtocol.saga.test.ts index ac732ee519..ae98e4cec5 100644 --- a/packages/desktop/src/renderer/sagas/invitation/customProtocol.saga.test.ts +++ b/packages/desktop/src/renderer/sagas/invitation/customProtocol.saga.test.ts @@ -1,5 +1,5 @@ import { communities, getFactory, Store } from '@quiet/state-manager' -import { Community, CommunityOwnership, CreateNetworkPayload, InvitationData } from '@quiet/types' +import { Community, CommunityOwnership, AddCommunityPayload, InvitationData } from '@quiet/types' import { FactoryGirl } from 'factory-girl' import { expectSaga } from 'redux-saga-test-plan' import { customProtocolSaga } from './customProtocol.saga' @@ -32,7 +32,7 @@ describe('Handle invitation code', () => { }) it('creates network if code is valid', async () => { - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.User, peers: validInvitationData.pairs, psk: validInvitationData.psk, @@ -40,13 +40,13 @@ describe('Handle invitation code', () => { } await expectSaga(customProtocolSaga, communities.actions.customProtocol(validInvitationData)) .withState(store.getState()) - .put(communities.actions.createNetwork(payload)) + .put(communities.actions.addCommunity(payload)) .run() }) it('does not try to create network if user is already in community', async () => { community = await factory.create['payload']>('Community') - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.User, peers: validInvitationData.pairs, psk: validInvitationData.psk, @@ -63,12 +63,12 @@ describe('Handle invitation code', () => { }, }) ) - .not.put(communities.actions.createNetwork(payload)) + .not.put(communities.actions.addCommunity(payload)) .run() }) it('does not try to create network if code is missing addresses', async () => { - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.User, peers: [], } @@ -92,12 +92,12 @@ describe('Handle invitation code', () => { }, }) ) - .not.put(communities.actions.createNetwork(payload)) + .not.put(communities.actions.addCommunity(payload)) .run() }) it('does not try to create network if code is missing psk', async () => { - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.User, peers: [], } @@ -121,7 +121,7 @@ describe('Handle invitation code', () => { }, }) ) - .not.put(communities.actions.createNetwork(payload)) + .not.put(communities.actions.addCommunity(payload)) .run() }) }) diff --git a/packages/desktop/src/renderer/sagas/invitation/customProtocol.saga.ts b/packages/desktop/src/renderer/sagas/invitation/customProtocol.saga.ts index 98a26e3035..85af17be6b 100644 --- a/packages/desktop/src/renderer/sagas/invitation/customProtocol.saga.ts +++ b/packages/desktop/src/renderer/sagas/invitation/customProtocol.saga.ts @@ -1,6 +1,6 @@ import { PayloadAction } from '@reduxjs/toolkit' import { select, put, delay } from 'typed-redux-saga' -import { CommunityOwnership, CreateNetworkPayload } from '@quiet/types' +import { CommunityOwnership, AddCommunityPayload } from '@quiet/types' import { communities } from '@quiet/state-manager' import { socketSelectors } from '../socket/socket.selectors' import { ModalName } from '../modals/modals.types' @@ -32,13 +32,13 @@ export function* customProtocolSaga( } const invitationData = action.payload if (invitationData && invitationData.pairs.length > 0 && invitationData.psk && invitationData.ownerOrbitDbIdentity) { - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.User, peers: invitationData.pairs, psk: invitationData.psk, ownerOrbitDbIdentity: invitationData.ownerOrbitDbIdentity, } - yield* put(communities.actions.createNetwork(payload)) + yield* put(communities.actions.addCommunity(payload)) } else { yield* put(communities.actions.clearInvitationCodes()) yield* put( diff --git a/packages/desktop/src/rtl-tests/community.create.test.tsx b/packages/desktop/src/rtl-tests/community.create.test.tsx index 446409f0e2..29983cd254 100644 --- a/packages/desktop/src/rtl-tests/community.create.test.tsx +++ b/packages/desktop/src/rtl-tests/community.create.test.tsx @@ -154,13 +154,14 @@ describe('User', () => { expect(channelPage).toBeVisible() expect(actions).toMatchInlineSnapshot(` Array [ - "Communities/createNetwork", + "Communities/addCommunity", "Communities/addNewCommunity", "Communities/setCurrentCommunity", - "Identity/addNewIdentity", "Modals/closeModal", "Modals/openModal", "Identity/registerUsername", + "Communities/createNetwork", + "Identity/addNewIdentity", "Network/setLoadingPanelType", "Modals/openModal", "Identity/registerCertificate", diff --git a/packages/desktop/src/rtl-tests/community.join.test.tsx b/packages/desktop/src/rtl-tests/community.join.test.tsx index 0d6f4c1af9..4aba6fbbfd 100644 --- a/packages/desktop/src/rtl-tests/community.join.test.tsx +++ b/packages/desktop/src/rtl-tests/community.join.test.tsx @@ -169,16 +169,17 @@ describe('User', () => { expect(actions).toMatchInlineSnapshot(` Array [ - "Communities/createNetwork", + "Communities/addCommunity", "Communities/addNewCommunity", "Communities/setCurrentCommunity", "Communities/setInvitationCodes", - "Identity/addNewIdentity", "Modals/closeModal", "Modals/openModal", "Identity/registerUsername", + "Communities/createNetwork", "Network/setLoadingPanelType", "Modals/openModal", + "Identity/addNewIdentity", "Identity/registerCertificate", "Communities/launchCommunity", "Files/checkForMissingFiles", diff --git a/packages/desktop/src/rtl-tests/deep.linking.test.tsx b/packages/desktop/src/rtl-tests/deep.linking.test.tsx index 653029d1b1..cedfe2cb97 100644 --- a/packages/desktop/src/rtl-tests/deep.linking.test.tsx +++ b/packages/desktop/src/rtl-tests/deep.linking.test.tsx @@ -50,11 +50,12 @@ describe('Deep linking', () => { expect(actions).toMatchInlineSnapshot(` Array [ "Communities/customProtocol", - "Communities/createNetwork", + "Communities/addCommunity", "Communities/addNewCommunity", "Communities/setCurrentCommunity", "Communities/setInvitationCodes", "Communities/customProtocol", + "Modals/openModal", ] `) }) diff --git a/packages/desktop/src/rtl-tests/loadingPanel.test.tsx b/packages/desktop/src/rtl-tests/loadingPanel.test.tsx index 5730507f97..6d86306fb0 100644 --- a/packages/desktop/src/rtl-tests/loadingPanel.test.tsx +++ b/packages/desktop/src/rtl-tests/loadingPanel.test.tsx @@ -34,8 +34,24 @@ window.Notification = notification describe('Loading panel', () => { let socket: MockedSocket + const hiddenService = { + onionAddress: 'testOnionAddress', + privateKey: 'testPrivateKey', + } + + const peerId = { + id: 'testPeerId', + } + + const mockNetwork = { + hiddenService, + peerId, + } + beforeEach(() => { socket = new MockedSocket() + // @ts-ignore + socket.emitWithAck = jest.fn(() => mockNetwork) ioMock.mockImplementation(() => socket) window.ResizeObserver = jest.fn().mockImplementation(() => ({ observe: jest.fn(), diff --git a/packages/integration-tests/src/integrationTests/appActions.ts b/packages/integration-tests/src/integrationTests/appActions.ts index dd789afdb7..90a3098cb4 100644 --- a/packages/integration-tests/src/integrationTests/appActions.ts +++ b/packages/integration-tests/src/integrationTests/appActions.ts @@ -7,7 +7,7 @@ import { connection, publicChannels, RegisterCertificatePayload, - CreateNetworkPayload, + AddCommunityPayload, CommunityOwnership, TestStore, ChannelMessage, @@ -76,12 +76,12 @@ export async function createCommunity({ userName, store }: CreateCommunity) { const timeout = 20_000 const communityName = 'CommunityName' - const createNetworkPayload: CreateNetworkPayload = { + const addCommunityPayload: AddCommunityPayload = { ownership: CommunityOwnership.Owner, name: communityName, } - store.dispatch(communities.actions.createNetwork(createNetworkPayload)) + store.dispatch(communities.actions.addCommunity(addCommunityPayload)) await waitForExpect(() => { expect(store.getState().Identity.identities.ids).toHaveLength(1) @@ -126,11 +126,11 @@ export async function registerUsername(payload: Register) { // Give it a huge timeout, it should never fail, but sometimes takes more time, depending on tor. const timeout = 600_000 - const createNetworkPayload: CreateNetworkPayload = { + const addCommunityPayload: AddCommunityPayload = { ownership: CommunityOwnership.User, } - store.dispatch(communities.actions.createNetwork(createNetworkPayload)) + store.dispatch(communities.actions.addCommunity(addCommunityPayload)) await waitForExpect(() => { expect(store.getState().Identity.identities.ids).toHaveLength(1) @@ -265,11 +265,11 @@ export const sendRegistrationRequest = async (payload: SendRegistrationRequest) const timeout = 600_000 - const createNetworkPayload: CreateNetworkPayload = { + const addCommunityPayload: AddCommunityPayload = { ownership: CommunityOwnership.User, } - store.dispatch(communities.actions.createNetwork(createNetworkPayload)) + store.dispatch(communities.actions.addCommunity(addCommunityPayload)) await waitForExpect(() => { expect(store.getState().Identity.identities.ids).toHaveLength(1) diff --git a/packages/integration-tests/src/testUtils/actions.ts b/packages/integration-tests/src/testUtils/actions.ts index 5a621c0c02..7edaf505e1 100644 --- a/packages/integration-tests/src/testUtils/actions.ts +++ b/packages/integration-tests/src/testUtils/actions.ts @@ -2,7 +2,7 @@ import { ChannelMessage, communities, CommunityOwnership, - CreateNetworkPayload, + AddCommunityPayload, identity, publicChannels, messages, @@ -19,11 +19,11 @@ const timeout = 120_000 export async function registerUsername(payload: Register) { const { userName, store } = payload - const createNetworkPayload: CreateNetworkPayload = { + const addCommunityPayload: AddCommunityPayload = { ownership: CommunityOwnership.User, } - log(`User ${userName} starts creating network`) - store.dispatch(communities.actions.createNetwork(createNetworkPayload)) + log(`User ${userName} starts adding community`) + store.dispatch(communities.actions.addCommunity(addCommunityPayload)) await waitForExpect(() => { assert.equal(store.getState().Identity.identities.ids.length, 1) @@ -46,12 +46,12 @@ export async function registerUsername(payload: Register) { } export const createCommunity = async ({ username, communityName, store }): Promise => { - const createNetworkPayload: CreateNetworkPayload = { + const addCommunityPayload: AddCommunityPayload = { ownership: CommunityOwnership.Owner, name: communityName, } - store.dispatch(communities.actions.createNetwork(createNetworkPayload)) + store.dispatch(communities.actions.addCommunity(addCommunityPayload)) await waitForExpect(() => { assert.strictEqual(store.getState().Identity.identities.ids.length, 1) }, timeout) diff --git a/packages/mobile/src/screens/CreateCommunity/CreateCommunity.screen.tsx b/packages/mobile/src/screens/CreateCommunity/CreateCommunity.screen.tsx index 35ba42cb20..0c2e1d642a 100644 --- a/packages/mobile/src/screens/CreateCommunity/CreateCommunity.screen.tsx +++ b/packages/mobile/src/screens/CreateCommunity/CreateCommunity.screen.tsx @@ -1,7 +1,7 @@ import React, { FC, useCallback } from 'react' import { useDispatch, useSelector } from 'react-redux' import { identity, communities } from '@quiet/state-manager' -import { CommunityOwnership, CreateNetworkPayload } from '@quiet/types' +import { CommunityOwnership, AddCommunityPayload } from '@quiet/types' import { initSelectors } from '../../store/init/init.selectors' import { navigationActions } from '../../store/navigation/navigation.slice' import { ScreenNames } from '../../const/ScreenNames.enum' @@ -19,11 +19,11 @@ export const CreateCommunityScreen: FC = () => { const createCommunityAction = useCallback( (name: string) => { - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.Owner, name, } - dispatch(communities.actions.createNetwork(payload)) + dispatch(communities.actions.addCommunity(payload)) dispatch( navigationActions.navigation({ screen: ScreenNames.UsernameRegistrationScreen, diff --git a/packages/mobile/src/screens/JoinCommunity/JoinCommunity.screen.tsx b/packages/mobile/src/screens/JoinCommunity/JoinCommunity.screen.tsx index 381d6831af..1c63ca75ac 100644 --- a/packages/mobile/src/screens/JoinCommunity/JoinCommunity.screen.tsx +++ b/packages/mobile/src/screens/JoinCommunity/JoinCommunity.screen.tsx @@ -2,7 +2,7 @@ import React, { FC, useCallback, useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { identity, communities } from '@quiet/state-manager' -import { CommunityOwnership, CreateNetworkPayload, InvitationData, InvitationPair } from '@quiet/types' +import { CommunityOwnership, AddCommunityPayload, InvitationData, InvitationPair } from '@quiet/types' import { JoinCommunity } from '../../components/JoinCommunity/JoinCommunity.component' import { navigationActions } from '../../store/navigation/navigation.slice' import { ScreenNames } from '../../const/ScreenNames.enum' @@ -36,13 +36,13 @@ export const JoinCommunityScreen: FC = ({ route }) => const joinCommunityAction = useCallback( (data: InvitationData) => { - const payload: CreateNetworkPayload = { + const payload: AddCommunityPayload = { ownership: CommunityOwnership.User, peers: data.pairs, psk: data.psk, ownerOrbitDbIdentity: data.ownerOrbitDbIdentity, } - dispatch(communities.actions.createNetwork(payload)) + dispatch(communities.actions.addCommunity(payload)) dispatch( navigationActions.navigation({ screen: ScreenNames.UsernameRegistrationScreen, diff --git a/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts b/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts index 80d718cd7e..1c6a6222d1 100644 --- a/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts +++ b/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts @@ -59,7 +59,7 @@ describe('deepLinkSaga', () => { .withState(store.getState()) .put(initActions.resetDeepLink()) .put( - communities.actions.createNetwork({ + communities.actions.addCommunity({ ownership: CommunityOwnership.User, peers: validData.pairs, psk: validData.psk, @@ -103,7 +103,7 @@ describe('deepLinkSaga', () => { .withReducer(reducer) .withState(store.getState()) .not.put( - communities.actions.createNetwork({ + communities.actions.addCommunity({ ownership: CommunityOwnership.User, peers: validData.pairs, psk: validData.psk, @@ -147,7 +147,7 @@ describe('deepLinkSaga', () => { }, }) .not.put( - communities.actions.createNetwork({ + communities.actions.addCommunity({ ownership: CommunityOwnership.User, peers: validData.pairs, psk: validData.psk, @@ -190,7 +190,7 @@ describe('deepLinkSaga', () => { }) .put.like({ action: { - type: communities.actions.createNetwork.type, + type: communities.actions.addCommunity.type, payload: { ownership: CommunityOwnership.User, peers: validData.pairs, @@ -236,7 +236,7 @@ describe('deepLinkSaga', () => { }, }) .not.put( - communities.actions.createNetwork({ + communities.actions.addCommunity({ ownership: CommunityOwnership.User, peers: validData.pairs, psk: validData.psk, diff --git a/packages/mobile/src/store/init/deepLink/deepLink.saga.ts b/packages/mobile/src/store/init/deepLink/deepLink.saga.ts index 4c98593786..6ce86d8f81 100644 --- a/packages/mobile/src/store/init/deepLink/deepLink.saga.ts +++ b/packages/mobile/src/store/init/deepLink/deepLink.saga.ts @@ -7,7 +7,7 @@ import { initSelectors } from '../init.selectors' import { initActions } from '../init.slice' import { appImages } from '../../../assets' import { replaceScreen } from '../../../RootNavigation' -import { CommunityOwnership, CreateNetworkPayload, InvitationData } from '@quiet/types' +import { CommunityOwnership, AddCommunityPayload, InvitationData } from '@quiet/types' import { areObjectsEqual } from '../../../utils/functions/areObjectsEqual/areObjectsEqual' export function* deepLinkSaga(action: PayloadAction['payload']>): Generator { @@ -127,14 +127,14 @@ export function* deepLinkSaga(action: PayloadAction { + it('add community for joining user', async () => { + setupCrypto() + + const socket = { + emit: jest.fn(), + emitWithAck: jest.fn(() => { + return {} + }), + on: jest.fn(), + } as unknown as Socket + + const store = prepareStore().store + + const peers = [{ peerId: 'peerId', onionAddress: 'address' }] + const psk = '12345' + + const community: Community = { + id: '1', + name: undefined, + CA: null, + rootCa: undefined, + psk, + ownerOrbitDbIdentity: undefined, + } + + const reducer = combineReducers(reducers) + await expectSaga( + addCommunitySaga, + socket, + communitiesActions.addCommunity({ + ownership: CommunityOwnership.User, + peers, + psk, + }) + ) + .withReducer(reducer) + .withState(store.getState()) + .provide([[call.fn(generateId), community.id]]) + .not.call(createRootCA) + .call(generateId) + .put(communitiesActions.addNewCommunity(community)) + .put(communitiesActions.setCurrentCommunity(community.id)) + .put(communitiesActions.setInvitationCodes(peers)) + .run() + }) + + it('add community for owner', async () => { + setupCrypto() + + const socket = { + emit: jest.fn(), + emitWithAck: jest.fn(() => { + return {} + }), + on: jest.fn(), + } as unknown as Socket + + const store = prepareStore().store + + const CA = { + rootCertString: 'rootCertString', + rootKeyString: 'rootKeyString', + } + + const community: Community = { + id: '1', + name: 'rockets', + CA, + rootCa: CA.rootCertString, + psk: undefined, + ownerOrbitDbIdentity: undefined, + } + + const reducer = combineReducers(reducers) + await expectSaga( + addCommunitySaga, + socket, + communitiesActions.addCommunity({ + ownership: CommunityOwnership.Owner, + name: community.name, + }) + ) + .withReducer(reducer) + .withState(store.getState()) + .provide([ + [call.fn(createRootCA), CA], + [call.fn(generateId), community.id], + ]) + .call( + createRootCA, + new Time({ type: 0, value: new Date(Date.UTC(2010, 11, 28, 10, 10, 10)) }), + new Time({ type: 0, value: new Date(Date.UTC(2030, 11, 28, 10, 10, 10)) }), + community.name + ) + .call(generateId) + .put(communitiesActions.addNewCommunity(community)) + .put(communitiesActions.setCurrentCommunity(community.id)) + .run() + }) +}) diff --git a/packages/state-manager/src/sagas/communities/addCommunity/addCommunity.saga.ts b/packages/state-manager/src/sagas/communities/addCommunity/addCommunity.saga.ts new file mode 100644 index 0000000000..4094126557 --- /dev/null +++ b/packages/state-manager/src/sagas/communities/addCommunity/addCommunity.saga.ts @@ -0,0 +1,53 @@ +import { PayloadAction } from '@reduxjs/toolkit' +import { call, put } from 'typed-redux-saga' +import { Time } from 'pkijs' +import { generateId } from '../../../utils/cryptography/cryptography' +import { communitiesActions } from '../communities.slice' +import { identityActions } from '../../identity/identity.slice' +import { createRootCA } from '@quiet/identity' +import { type Community, CommunityOwnership, type Identity, SocketActionTypes } from '@quiet/types' +import { Socket } from '../../../types' + +/** + * Generate root CA cert and add initial Community data to Redux Store + */ +export function* addCommunitySaga( + socket: Socket, + action: PayloadAction['payload']> +) { + // TODO: Move CA generation to backend when creating Community + let CA: null | { + rootCertString: string + rootKeyString: string + } = null + + if (action.payload.ownership === CommunityOwnership.Owner) { + const notBeforeDate = new Date(Date.UTC(2010, 11, 28, 10, 10, 10)) + const notAfterDate = new Date(Date.UTC(2030, 11, 28, 10, 10, 10)) + + CA = yield* call( + createRootCA, + new Time({ type: 0, value: notBeforeDate }), + new Time({ type: 0, value: notAfterDate }), + action.payload.name + ) + } + + const id = yield* call(generateId) + const community: Community = { + id, + name: action.payload.name, + CA, + rootCa: CA?.rootCertString, + psk: action.payload.psk, + ownerOrbitDbIdentity: action.payload.ownerOrbitDbIdentity, + } + + yield* put(communitiesActions.addNewCommunity(community)) + yield* put(communitiesActions.setCurrentCommunity(id)) + + const invitationPeers = action.payload.peers + if (invitationPeers) { + yield* put(communitiesActions.setInvitationCodes(invitationPeers)) + } +} diff --git a/packages/state-manager/src/sagas/communities/communities.master.saga.ts b/packages/state-manager/src/sagas/communities/communities.master.saga.ts index c5e1e1a275..8e52823e90 100644 --- a/packages/state-manager/src/sagas/communities/communities.master.saga.ts +++ b/packages/state-manager/src/sagas/communities/communities.master.saga.ts @@ -2,12 +2,14 @@ import { type Socket } from '../../types' import { all, takeEvery } from 'typed-redux-saga' import { communitiesActions } from './communities.slice' import { connectionActions } from '../appConnection/connection.slice' +import { addCommunitySaga } from './addCommunity/addCommunity.saga' import { createCommunitySaga } from './createCommunity/createCommunity.saga' import { initCommunities, launchCommunitySaga } from './launchCommunity/launchCommunity.saga' import { createNetworkSaga } from './createNetwork/createNetwork.saga' export function* communitiesMasterSaga(socket: Socket): Generator { yield all([ + takeEvery(communitiesActions.addCommunity.type, addCommunitySaga, socket), takeEvery(communitiesActions.createNetwork.type, createNetworkSaga, socket), takeEvery(connectionActions.torBootstrapped.type, initCommunities), takeEvery(communitiesActions.createCommunity.type, createCommunitySaga, socket), diff --git a/packages/state-manager/src/sagas/communities/communities.slice.ts b/packages/state-manager/src/sagas/communities/communities.slice.ts index dae53ca209..a2170127c4 100644 --- a/packages/state-manager/src/sagas/communities/communities.slice.ts +++ b/packages/state-manager/src/sagas/communities/communities.slice.ts @@ -5,7 +5,7 @@ import { InvitationPair, type AddOwnerCertificatePayload, type Community, - type CreateNetworkPayload, + type AddCommunityPayload, type StorePeerListPayload, CommunityMetadata, InvitationData, @@ -21,12 +21,13 @@ export const communitiesSlice = createSlice({ initialState: { ...new CommunitiesState() }, name: StoreKeys.Communities, reducers: { - setCurrentCommunity: (state, action: PayloadAction) => { - state.currentCommunity = action.payload - }, + addCommunity: (state, _action: PayloadAction) => state, addNewCommunity: (state, action: PayloadAction) => { communitiesAdapter.addOne(state.communities, action.payload) }, + setCurrentCommunity: (state, action: PayloadAction) => { + state.currentCommunity = action.payload + }, updateCommunityData: (state, action: PayloadAction) => { communitiesAdapter.updateOne(state.communities, { id: action.payload.id, @@ -35,7 +36,7 @@ export const communitiesSlice = createSlice({ }, }) }, - createNetwork: (state, _action: PayloadAction) => state, + createNetwork: state => state, resetApp: (state, _action) => state, createCommunity: (state, _action: PayloadAction) => state, launchCommunity: (state, _action: PayloadAction) => state, diff --git a/packages/state-manager/src/sagas/communities/communities.types.ts b/packages/state-manager/src/sagas/communities/communities.types.ts index 1789c00362..91dea96a3f 100644 --- a/packages/state-manager/src/sagas/communities/communities.types.ts +++ b/packages/state-manager/src/sagas/communities/communities.types.ts @@ -7,7 +7,7 @@ export enum CommunityOwnership { User = 'user', } -export interface CreateNetworkPayload { +export interface AddCommunityPayload { ownership: CommunityOwnership name?: string } diff --git a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.test.ts b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.test.ts index 301db9617c..b2b4f417a0 100644 --- a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.test.ts +++ b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.test.ts @@ -4,22 +4,34 @@ import { call } from 'redux-saga-test-plan/matchers' import { Time } from 'pkijs' import { prepareStore } from '../../../utils/tests/prepareStore' import { communitiesActions } from '../communities.slice' +import { identityActions } from '../../identity/identity.slice' import { createRootCA, setupCrypto } from '@quiet/identity' import { reducers } from '../../reducers' import { createNetworkSaga } from './createNetwork.saga' -import { generateId } from '../../../utils/cryptography/cryptography' -import { type Community, CommunityOwnership } from '@quiet/types' -import { Socket } from '../../../types' +import { type Community, CommunityOwnership, SocketActionTypes } from '@quiet/types' +import { Socket, applyEmitParams } from '../../../types' describe('createNetwork', () => { - it('create network for joining user', async () => { + it('creates network', async () => { setupCrypto() + const hiddenService = { + onionAddress: 'testOnionAddress', + privateKey: 'testPrivateKey', + } + + const peerId = { + id: 'testPeerId', + } + + const network = { + hiddenService, + peerId, + } + const socket = { emit: jest.fn(), - emitWithAck: jest.fn(() => { - return {} - }), + emitWithAck: jest.fn(() => network), on: jest.fn(), } as unknown as Socket @@ -27,77 +39,30 @@ describe('createNetwork', () => { const community: Community = { id: '1', - name: undefined, + name: 'test', CA: null, rootCa: undefined, } - const reducer = combineReducers(reducers) - await expectSaga( - createNetworkSaga, - socket, - communitiesActions.createNetwork({ - ownership: CommunityOwnership.User, - peers: [{ peerId: 'peerId', onionAddress: 'address' }], - psk: '12345', - }) - ) - .withReducer(reducer) - .withState(store.getState()) - .provide([[call.fn(generateId), community.id]]) - .not.call(createRootCA) - .call(generateId) - .run() - }) - - it('create network for owner', async () => { - setupCrypto() - - const socket = { - emit: jest.fn(), - emitWithAck: jest.fn(() => { - return {} - }), - on: jest.fn(), - } as unknown as Socket + store.dispatch(communitiesActions.addNewCommunity(community)) + store.dispatch(communitiesActions.setCurrentCommunity(community.id)) - const store = prepareStore().store - - const CA = { - rootCertString: 'rootCertString', - rootKeyString: 'rootKeyString', - } - - const community: Community = { - id: '1', - name: 'rockets', - CA, - rootCa: CA.rootCertString, + const identity = { + id: community.id, + nickname: '', + hiddenService: network.hiddenService, + peerId: network.peerId, + userCsr: null, + userCertificate: null, + joinTimestamp: null, } const reducer = combineReducers(reducers) - await expectSaga( - createNetworkSaga, - socket, - communitiesActions.createNetwork({ - ownership: CommunityOwnership.Owner, - name: 'rockets', - psk: '12345', - }) - ) + await expectSaga(createNetworkSaga, socket, communitiesActions.createNetwork()) .withReducer(reducer) .withState(store.getState()) - .provide([ - [call.fn(createRootCA), CA], - [call.fn(generateId), community.id], - ]) - .call( - createRootCA, - new Time({ type: 0, value: new Date(Date.UTC(2010, 11, 28, 10, 10, 10)) }), - new Time({ type: 0, value: new Date(Date.UTC(2030, 11, 28, 10, 10, 10)) }), - 'rockets' - ) - .call(generateId) + .apply(socket, socket.emitWithAck, applyEmitParams(SocketActionTypes.CREATE_NETWORK, community.id)) + .put(identityActions.addNewIdentity(identity)) .run() }) }) diff --git a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts index 33003d6df5..fb1938ee0d 100644 --- a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts +++ b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts @@ -1,8 +1,9 @@ import { PayloadAction } from '@reduxjs/toolkit' -import { apply, call, put } from 'typed-redux-saga' +import { apply, call, put, select } from 'typed-redux-saga' import { Time } from 'pkijs' import { generateId } from '../../../utils/cryptography/cryptography' import { communitiesActions } from '../communities.slice' +import { communitiesSelectors } from '../../communities/communities.selectors' import { identityActions } from '../../identity/identity.slice' import { createRootCA } from '@quiet/identity' import { type Community, CommunityOwnership, type Identity, SocketActionTypes } from '@quiet/types' @@ -12,47 +13,20 @@ export function* createNetworkSaga( socket: Socket, action: PayloadAction['payload']> ) { - console.log('create network saga') + console.log('Creating network for community') - // Community IDs are only local identifiers - const id = yield* call(generateId) + const community = yield* select(communitiesSelectors.currentCommunity) - const network = yield* apply(socket, socket.emitWithAck, applyEmitParams(SocketActionTypes.CREATE_NETWORK, id)) - - // TODO: Move CA generation to backend when creating Community - let CA: null | { - rootCertString: string - rootKeyString: string - } = null - - if (action.payload.ownership === CommunityOwnership.Owner) { - const notBeforeDate = new Date(Date.UTC(2010, 11, 28, 10, 10, 10)) - const notAfterDate = new Date(Date.UTC(2030, 11, 28, 10, 10, 10)) - - CA = yield* call( - createRootCA, - new Time({ type: 0, value: notBeforeDate }), - new Time({ type: 0, value: notAfterDate }), - action.payload.name - ) + if (!community) { + console.error('Could not create network, no community') + return } - const community: Community = { - id, - name: action.payload.name, - CA, - rootCa: CA?.rootCertString, - psk: action.payload.psk, - ownerOrbitDbIdentity: action.payload.ownerOrbitDbIdentity, - } - - yield* put(communitiesActions.addNewCommunity(community)) - yield* put(communitiesActions.setCurrentCommunity(id)) - - const invitationPeers = action.payload.peers - if (invitationPeers) { - yield* put(communitiesActions.setInvitationCodes(invitationPeers)) - } + const network = yield* apply( + socket, + socket.emitWithAck, + applyEmitParams(SocketActionTypes.CREATE_NETWORK, community.id) + ) // Identities are tied to communities for now const identity: Identity = { diff --git a/packages/state-manager/src/sagas/identity/registerUsername/registerUsername.saga.ts b/packages/state-manager/src/sagas/identity/registerUsername/registerUsername.saga.ts index 4622539f7f..f0c2ebd09d 100644 --- a/packages/state-manager/src/sagas/identity/registerUsername/registerUsername.saga.ts +++ b/packages/state-manager/src/sagas/identity/registerUsername/registerUsername.saga.ts @@ -5,6 +5,7 @@ import { identitySelectors } from '../identity.selectors' import { identityActions } from '../identity.slice' import { config } from '../../users/const/certFieldTypes' import { Socket } from '../../../types' +import { communitiesActions } from '../../communities/communities.slice' import { communitiesSelectors } from '../../communities/communities.selectors' import { CreateUserCsrPayload, RegisterCertificatePayload, Community } from '@quiet/types' @@ -23,6 +24,8 @@ export function* registerUsernameSaga( return } + yield* put(communitiesActions.createNetwork()) + let identity = yield* select(identitySelectors.currentIdentity) if (!identity) { diff --git a/packages/state-manager/src/utils/cryptography/cryptography.ts b/packages/state-manager/src/utils/cryptography/cryptography.ts index f5b6e9081c..c338edf3f3 100644 --- a/packages/state-manager/src/utils/cryptography/cryptography.ts +++ b/packages/state-manager/src/utils/cryptography/cryptography.ts @@ -7,12 +7,3 @@ export const generateId = () => { .map(() => Math.random().toString(36).charAt(2)) .join('') } - -export const generateDmKeyPair = () => { - const dh = crypto.createDiffieHellman(constants.prime, 'hex', constants.generator, 'hex') - dh.generateKeys() - const privateKey = dh.getPrivateKey('hex') - const publicKey = dh.getPublicKey('hex') - - return { publicKey, privateKey } -} diff --git a/packages/types/src/community.ts b/packages/types/src/community.ts index 607a00fe1a..ff9bdb8d5a 100644 --- a/packages/types/src/community.ts +++ b/packages/types/src/community.ts @@ -21,7 +21,7 @@ export enum CommunityOwnership { User = 'user', } -export interface CreateNetworkPayload { +export interface AddCommunityPayload { ownership: CommunityOwnership name?: string peers?: InvitationPair[]