From 3a9529a79d574f165d349307b3b7942d7038973e Mon Sep 17 00:00:00 2001 From: Isla Koenigsknecht Date: Thu, 4 Apr 2024 11:57:05 -0400 Subject: [PATCH] Fix: Not initializing on reconnection when general channel was deleted (#2334) (#2400) * Fix issue with users joining a community where the general channel was deleted while they were offline * Update e2e tests to include case for this bug * Update CHANGELOG.md --- CHANGELOG.md | 1 + .../src/nest/storage/storage.service.ts | 14 ++-- .../src/tests/multipleClients.test.ts | 84 +++++++++++++++++-- .../channelDeletionResponse.saga.ts | 2 +- .../channelsReplicated.saga.ts | 27 +++--- 5 files changed, 101 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed1ba5b24..59c0741d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ # Fixes * Allow JPEG and GIF files as profile photos ([#2332](https://github.com/TryQuiet/quiet/issues/2332)) +* Fixes issues with recreating general channel when deleted while offline ([#2334](https://github.com/TryQuiet/quiet/issues/2334)) # New features diff --git a/packages/backend/src/nest/storage/storage.service.ts b/packages/backend/src/nest/storage/storage.service.ts index 6ff620570d..54d7a65b0a 100644 --- a/packages/backend/src/nest/storage/storage.service.ts +++ b/packages/backend/src/nest/storage/storage.service.ts @@ -620,15 +620,15 @@ export class StorageService extends EventEmitter { } } await repo.db.load() - const allEntries = this.getAllEventLogRawEntries(repo.db) + // const allEntries = this.getAllEventLogRawEntries(repo.db) await repo.db.close() await repo.db.drop() - const hashes = allEntries.map(e => CID.parse(e.hash)) - const files = allEntries - .map(e => { - return e.payload.value.media - }) - .filter(isDefined) + // const hashes = allEntries.map(e => CID.parse(e.hash)) + // const files = allEntries + // .map(e => { + // return e.payload.value.media + // }) + // .filter(isDefined) // await this.deleteChannelFiles(files) // await this.deleteChannelMessages(hashes) this.publicChannelsRepos.delete(channelId) diff --git a/packages/e2e-tests/src/tests/multipleClients.test.ts b/packages/e2e-tests/src/tests/multipleClients.test.ts index 5ec4b301aa..8da0cade98 100644 --- a/packages/e2e-tests/src/tests/multipleClients.test.ts +++ b/packages/e2e-tests/src/tests/multipleClients.test.ts @@ -25,6 +25,8 @@ describe('Multiple Clients', () => { let secondChannelUser1: Channel + let thirdChannelOwner: Channel + let channelContextMenuOwner: ChannelContextMenu let invitationCode: string @@ -37,6 +39,7 @@ describe('Multiple Clients', () => { const communityName = 'testcommunity' const displayedCommunityName = 'Testcommunity' const newChannelName = 'mid-night-club' + const thirdChannelName = 'delete-this' const sleep = async (time = 1000) => { await new Promise(resolve => @@ -342,16 +345,42 @@ describe('Multiple Clients', () => { const channels = await sidebarOwner.getChannelList() expect(channels.length).toEqual(2) }) + + it('Channel deletion - Owner creates third channel', async () => { + await sidebarOwner.addNewChannel(thirdChannelName) + await sidebarOwner.switchChannel(thirdChannelName) + thirdChannelOwner = new Channel(users.owner.app.driver, thirdChannelName) + const messages = await thirdChannelOwner.getUserMessages(users.owner.username) + expect(messages.length).toEqual(1) + await new Promise(resolve => + setTimeout(() => { + resolve() + }, 2000) + ) + const channels = await sidebarUser1.getChannelList() + expect(channels.length).toEqual(3) + }) + // End of tests for Windows if (process.platform !== 'win32') { - it('Leave community', async () => { - console.log('TEST 2') - const settingsModal = await new Sidebar(users.user1.app.driver).openSettings() - const isSettingsModal = await settingsModal.element.isDisplayed() - expect(isSettingsModal).toBeTruthy() - await settingsModal.openLeaveCommunityModal() - await settingsModal.leaveCommunityButton() + it('User 1 closes app', async () => { + console.log('User 1 closes app') + await users.user1.app?.close() }) + + // Delete third channel while guest is absent + it('Channel deletion - Owner deletes third channel', async () => { + console.log('TEST 2.5') + await new Promise(resolve => setTimeout(() => resolve(), 10000)) + const isThirdChannel = await thirdChannelOwner.messageInput.isDisplayed() + expect(isThirdChannel).toBeTruthy() + await channelContextMenuOwner.openMenu() + await channelContextMenuOwner.openDeletionChannelModal() + await channelContextMenuOwner.deleteChannel() + const channels = await sidebarOwner.getChannelList() + expect(channels.length).toEqual(2) + }) + // Delete general channel while guest is absent it('Channel deletion - Owner recreates general channel', async () => { console.log('TEST 3') @@ -365,6 +394,47 @@ describe('Multiple Clients', () => { expect(channels.length).toEqual(2) }) + it('User 1 re-opens app', async () => { + console.log('User 1 re-opens app') + await users.user1.app?.open() + await new Promise(resolve => setTimeout(() => resolve(), 30000)) + }) + + // Check correct channels replication + it('Channel deletion - User sees information about recreation general channel and see correct amount of messages (#2334)', async () => { + console.log('TESTING - ISSUE 2334') + generalChannelUser1 = new Channel(users.user1.app.driver, 'general') + await generalChannelUser1.element.isDisplayed() + console.timeEnd(`[${users.user1.app.name}] '${users.user2.username}' joining community time`) + await new Promise(resolve => + setTimeout(() => { + resolve() + }, 10000) + ) + + await generalChannelUser1.waitForUserMessage( + users.owner.username, + `@${users.owner.username} deleted all messages in #general` + ) + await generalChannelUser1.waitForUserMessage( + users.owner.username, + `@${users.owner.username} deleted #${thirdChannelName}` + ) + await generalChannelUser1.waitForUserMessage( + users.owner.username, + `@${users.user2.username} has joined Testcommunity! 🎉` + ) + }) + + it('Leave community', async () => { + console.log('TEST 2') + const settingsModal = await new Sidebar(users.user1.app.driver).openSettings() + const isSettingsModal = await settingsModal.element.isDisplayed() + expect(isSettingsModal).toBeTruthy() + await settingsModal.openLeaveCommunityModal() + await settingsModal.leaveCommunityButton() + }) + it('Leave community - Guest re-join to community successfully', async () => { console.log('TEST 4') const debugModal = new DebugModeModal(users.user1.app.driver) diff --git a/packages/state-manager/src/sagas/publicChannels/channelDeletionResponse/channelDeletionResponse.saga.ts b/packages/state-manager/src/sagas/publicChannels/channelDeletionResponse/channelDeletionResponse.saga.ts index 9e53eab2b2..b0d90d0182 100644 --- a/packages/state-manager/src/sagas/publicChannels/channelDeletionResponse/channelDeletionResponse.saga.ts +++ b/packages/state-manager/src/sagas/publicChannels/channelDeletionResponse/channelDeletionResponse.saga.ts @@ -60,7 +60,7 @@ export function* channelDeletionResponseSaga( let newGeneralChannel: PublicChannelStorage | undefined = yield* select(publicChannelsSelectors.generalChannel) while (!newGeneralChannel) { log('General channel has not been replicated yet') - yield* delay(500) + yield* delay(1000) newGeneralChannel = yield* select(publicChannelsSelectors.generalChannel) } yield* put(publicChannelsActions.setCurrentChannel({ channelId: newGeneralChannel.id })) diff --git a/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.ts b/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.ts index 4b431b4fc6..ebf15f8f16 100644 --- a/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.ts +++ b/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.ts @@ -13,7 +13,8 @@ const log = logger('channels') export function* channelsReplicatedSaga( action: PayloadAction['payload']> ): Generator { - log('Syncing channels') + // TODO: Refactor to use QuietLogger + log(`Syncing channels: ${JSON.stringify(action.payload, null, 2)}`) const { channels } = action.payload const _locallyStoredChannels = yield* select(publicChannelsSelectors.publicChannels) const locallyStoredChannels = _locallyStoredChannels.map(channel => channel.id) @@ -24,20 +25,10 @@ export function* channelsReplicatedSaga( const databaseStoredChannelsIds = databaseStoredChannels.map(channel => channel.id) console.log({ locallyStoredChannels, databaseStoredChannelsIds }) - // Removing channels from store - if (databaseStoredChannelsIds.length > 0) { - for (const channelId of locallyStoredChannels) { - if (!databaseStoredChannelsIds.includes(channelId)) { - log(`Removing #${channelId} from store`) - yield* put(publicChannelsActions.deleteChannel({ channelId })) - yield* take(publicChannelsActions.completeChannelDeletion) - } - } - } - // Upserting channels to local storage for (const channel of databaseStoredChannels) { if (!locallyStoredChannels.includes(channel.id)) { + // TODO: Refactor to use QuietLogger log(`Adding #${channel.name} to store`) yield* put( publicChannelsActions.addChannel({ @@ -52,6 +43,18 @@ export function* channelsReplicatedSaga( } } + // Removing channels from store + if (databaseStoredChannelsIds.length > 0) { + for (const channelId of locallyStoredChannels) { + if (!databaseStoredChannelsIds.includes(channelId)) { + // TODO: Refactor to use QuietLogger + log(`Removing #${channelId} from store`) + yield* put(publicChannelsActions.deleteChannel({ channelId })) + yield* take(publicChannelsActions.completeChannelDeletion) + } + } + } + const currentChannelCache = yield* select(publicChannelsSelectors.currentChannelMessages) const currentChannelRepository = yield* select(messagesSelectors.currentPublicChannelMessagesEntries)