From 4d9cc7a1be3031a9ab432130ee442d1f2ca48067 Mon Sep 17 00:00:00 2001 From: Przemyslaw Jozwik Date: Mon, 15 Jul 2024 14:49:34 +0200 Subject: [PATCH] feat: Improve event builder --- .../conversation/ConversationRepository.ts | 70 +++++++++++++++++-- .../Proteus/ProteusStateHandler.test.ts | 6 +- src/script/conversation/EventBuilder.test.ts | 12 +++- src/script/conversation/EventBuilder.ts | 19 ----- src/script/conversation/EventNew.ts | 57 +++++++++++++++ .../preprocessor/ServiceMiddleware.test.ts | 16 ++++- .../FederationEventProcessor.ts | 41 ++++++++--- 7 files changed, 181 insertions(+), 40 deletions(-) create mode 100644 src/script/conversation/EventNew.ts diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 4272c2677a9..c41cc1209eb 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -110,6 +110,7 @@ import { OnConversationVerificationStateChange, } from './ConversationVerificationStateHandler/shared'; import {EventMapper} from './EventMapper'; +import {createBaseEvent} from './EventNew'; import {MessageRepository} from './MessageRepository'; import {NOTIFICATION_STATE} from './NotificationSetting'; @@ -122,6 +123,7 @@ import {ConnectionEntity} from '../connection/ConnectionEntity'; import {ConnectionRepository} from '../connection/ConnectionRepository'; import {ConnectionState} from '../connection/ConnectionState'; import { + AllVerifiedEvent, AssetAddEvent, ButtonActionConfirmationEvent, ClientConversationEvent, @@ -151,6 +153,7 @@ import {isMemberMessage} from '../guards/Message'; import * as LegalHoldEvaluator from '../legal-hold/LegalHoldEvaluator'; import {MessageCategory} from '../message/MessageCategory'; import {SystemMessageType} from '../message/SystemMessageType'; +import {VerificationMessageType} from '../message/VerificationMessageType'; import {addOtherSelfClientsToMLSConversation} from '../mls'; import {PropertiesRepository} from '../properties/PropertiesRepository'; import {SelfRepository} from '../self/SelfRepository'; @@ -926,11 +929,51 @@ export class ConversationRepository { conversationEntity.withAllTeamMembers(allTeamMembersParticipate); } - const creationEvent = conversationEntity.isGroup() - ? EventBuilder.buildGroupCreation(conversationEntity, isTemporaryGuest, timestamp) - : EventBuilder.build1to1Creation(conversationEntity); + let creationEventNew; - await this.eventRepository.injectEvent(creationEvent, eventSource); + if (conversationEntity.isGroup()) { + const selfUser = conversationEntity.selfUser(); + + if (!selfUser) { + this.logger.error('Failed to get self user'); + return; + } + + const {creator: creatorId} = conversationEntity; + const selfUserId = selfUser.id; + + const userIds = conversationEntity.participating_user_ids().slice(); + const createdBySelf = creatorId === selfUserId || isTemporaryGuest; + if (!createdBySelf) { + userIds.push(selfUser.qualifiedId); + } + + creationEventNew = createBaseEvent({ + conversation: conversationEntity, + eventType: ClientEvent.CONVERSATION.ONE2ONE_CREATION, + additionalData: { + userIds, + name: conversationEntity.name(), + allTeamMembers: conversationEntity.withAllTeamMembers(), + }, + from: isTemporaryGuest ? selfUserId : creatorId, + timestamp: 0, + }); + } else { + creationEventNew = createBaseEvent({ + conversation: conversationEntity, + eventType: ClientEvent.CONVERSATION.ONE2ONE_CREATION, + additionalData: {userIds: conversationEntity.participating_user_ids()}, + from: conversationEntity.creator, + timestamp: 0, + }); + } + + // const creationEvent = conversationEntity.isGroup() + // ? EventBuilder.buildGroupCreation(conversationEntity, isTemporaryGuest, timestamp) + // : EventBuilder.build1to1Creation(conversationEntity); + + await this.eventRepository.injectEvent(creationEventNew, eventSource); } /** @@ -2304,7 +2347,12 @@ export class ConversationRepository { }) => { switch (conversationVerificationState) { case ConversationVerificationState.VERIFIED: - const allVerifiedEvent = EventBuilder.buildAllVerified(conversationEntity); + // const allVerifiedEvent = EventBuilder.buildAllVerified(conversationEntity); + const allVerifiedEvent = createBaseEvent({ + conversation: conversationEntity, + eventType: ClientEvent.CONVERSATION.VERIFICATION, + additionalData: {type: VerificationMessageType.VERIFIED}, + }); await this.eventRepository.injectEvent(allVerifiedEvent); break; case ConversationVerificationState.DEGRADED: @@ -2640,9 +2688,19 @@ export class ConversationRepository { // TODO: Can this even have a response? in the API Client it look like it always returns `void` const hasResponse = response?.event; const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); + // const event = hasResponse + // ? response.event + // : EventBuilder.buildMemberLeave(conversationEntity, [user], this.userState.self().id, currentTimestamp); + const event = hasResponse ? response.event - : EventBuilder.buildMemberLeave(conversationEntity, [user], this.userState.self().id, currentTimestamp); + : createBaseEvent({ + conversation: conversationEntity, + eventType: CONVERSATION_EVENT.MEMBER_LEAVE, + additionalData: {qualified_user_ids: [user], user_ids: [user].map(({id}) => id)}, + from: this.userState.self().id, + timestamp: currentTimestamp, + }); this.eventRepository.injectEvent(event, EventRepository.SOURCE.BACKEND_RESPONSE); return event; diff --git a/src/script/conversation/ConversationVerificationStateHandler/Proteus/ProteusStateHandler.test.ts b/src/script/conversation/ConversationVerificationStateHandler/Proteus/ProteusStateHandler.test.ts index 206594929c3..f4352e6f1c3 100644 --- a/src/script/conversation/ConversationVerificationStateHandler/Proteus/ProteusStateHandler.test.ts +++ b/src/script/conversation/ConversationVerificationStateHandler/Proteus/ProteusStateHandler.test.ts @@ -135,7 +135,7 @@ describe('ProteusConversationVerificationStateHandler', () => { expect(conversationB.verification_state()).toBe(ConversationVerificationState.VERIFIED); expect(conversationAB.is_verified()).toBeDefined(); expect(conversationAB.is_verified()).toBeTruthy(); - expect(EventBuilder.buildAllVerified).not.toHaveBeenCalled(); + // expect(EventBuilder.buildAllVerified).not.toHaveBeenCalled(); expect(testFactory.event_repository.injectEvent).not.toHaveBeenCalled(); }); }); @@ -169,7 +169,7 @@ describe('ProteusConversationVerificationStateHandler', () => { expect(conversationAB.verification_state()).toBe(ConversationVerificationState.VERIFIED); expect(conversationB.verification_state()).toBe(ConversationVerificationState.VERIFIED); expect(conversationC.verification_state()).toBe(ConversationVerificationState.VERIFIED); - expect(EventBuilder.buildAllVerified).toHaveBeenCalledTimes(3); + // expect(EventBuilder.buildAllVerified).toHaveBeenCalledTimes(3); expect(testFactory.event_repository.injectEvent).toHaveBeenCalledWith(verifiedEvent); }); }); @@ -199,7 +199,7 @@ describe('ProteusConversationVerificationStateHandler', () => { expect(conversationAB.verification_state()).toBe(ConversationVerificationState.VERIFIED); expect(conversationB.verification_state()).toBe(ConversationVerificationState.VERIFIED); expect(conversationC.verification_state()).toBe(ConversationVerificationState.VERIFIED); - expect(EventBuilder.buildAllVerified).toHaveBeenCalledTimes(3); + // expect(EventBuilder.buildAllVerified).toHaveBeenCalledTimes(3); expect(testFactory.event_repository.injectEvent).toHaveBeenCalledWith(verifiedEvent); }); }); diff --git a/src/script/conversation/EventBuilder.test.ts b/src/script/conversation/EventBuilder.test.ts index 511e2cfa8ab..5df94284212 100644 --- a/src/script/conversation/EventBuilder.test.ts +++ b/src/script/conversation/EventBuilder.test.ts @@ -19,7 +19,7 @@ import type {QualifiedId} from '@wireapp/api-client/lib/user/'; -import {EventBuilder} from 'src/script/conversation/EventBuilder'; +import {AllVerifiedEvent, EventBuilder} from 'src/script/conversation/EventBuilder'; import {EventMapper} from 'src/script/conversation/EventMapper'; import {Conversation} from 'src/script/entity/Conversation'; import {User} from 'src/script/entity/User'; @@ -28,6 +28,8 @@ import {SuperType} from 'src/script/message/SuperType'; import {VerificationMessageType} from 'src/script/message/VerificationMessageType'; import {createUuid} from 'Util/uuid'; +import {createBaseEvent} from './EventNew'; + import {VerificationMessage} from '../entity/message/VerificationMessage'; describe('EventBuilder', () => { @@ -46,7 +48,13 @@ describe('EventBuilder', () => { }); it('buildAllVerified', () => { - const event = EventBuilder.buildAllVerified(conversation_et); + // const event = EventBuilder.buildAllVerified(conversation_et); + const event = createBaseEvent({ + conversation: conversation_et, + eventType: ClientEvent.CONVERSATION.VERIFICATION, + additionalData: {type: VerificationMessageType.VERIFIED}, + from: conversation_et.selfUser().id, + }); const messageEntity = event_mapper.mapJsonEvent(event as any, conversation_et) as VerificationMessage; expect(messageEntity).toBeDefined(); expect(messageEntity.super_type).toBe(SuperType.VERIFICATION); diff --git a/src/script/conversation/EventBuilder.ts b/src/script/conversation/EventBuilder.ts index 8c0b3ba465f..b0727328fd6 100644 --- a/src/script/conversation/EventBuilder.ts +++ b/src/script/conversation/EventBuilder.ts @@ -466,25 +466,6 @@ export const EventBuilder = { }; }, - buildIncomingMessageTooBig( - event: ConversationOtrMessageAddEvent, - messageError: Error, - errorCode: number, - ): ErrorEvent { - const {qualified_conversation: conversationId, conversation, data: eventData, from, time} = event; - - return { - ...buildQualifiedId(conversationId || conversation), - data: undefined, - error: `${messageError.message} (${eventData.sender})`, - error_code: errorCode, - from, - id: createUuid(), - time, - type: ClientEvent.CONVERSATION.INCOMING_MESSAGE_TOO_BIG, - }; - }, - buildLegalHoldMessage( conversationId: QualifiedId, userId: QualifiedId, diff --git a/src/script/conversation/EventNew.ts b/src/script/conversation/EventNew.ts new file mode 100644 index 00000000000..c446b9b6018 --- /dev/null +++ b/src/script/conversation/EventNew.ts @@ -0,0 +1,57 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import type {QualifiedId} from '@wireapp/api-client/lib/user'; + +import {createUuid} from 'Util/uuid'; + +import type {Conversation} from '../entity/Conversation'; + +function buildQualifiedId(conversation: QualifiedId | string) { + const qualifiedId = typeof conversation === 'string' ? {domain: '', id: conversation} : conversation; + return { + conversation: qualifiedId.id, + qualified_conversation: {domain: qualifiedId.domain, id: qualifiedId.id}, + }; +} + +type EventInput = { + conversation: Conversation; + eventType: string; + additionalData?: {}; + from?: string; + timestamp?: number; +}; + +export function createBaseEvent({ + conversation, + eventType, + additionalData = {}, + from, + timestamp, +}: EventInput): T { + return { + ...buildQualifiedId(conversation), + data: additionalData, + from: from ?? conversation.selfUser()?.id, + id: createUuid(), + time: new Date(timestamp || conversation.getNextTimestamp()).toISOString(), + type: eventType, + } as unknown as T; +} diff --git a/src/script/event/preprocessor/ServiceMiddleware.test.ts b/src/script/event/preprocessor/ServiceMiddleware.test.ts index 0c126c97f62..410110a8b31 100644 --- a/src/script/event/preprocessor/ServiceMiddleware.test.ts +++ b/src/script/event/preprocessor/ServiceMiddleware.test.ts @@ -18,7 +18,7 @@ */ import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; -import {EventBuilder} from 'src/script/conversation/EventBuilder'; +import {EventBuilder, OneToOneCreationEvent} from 'src/script/conversation/EventBuilder'; import {Conversation} from 'src/script/entity/Conversation'; import {User} from 'src/script/entity/User'; import {UserRepository} from 'src/script/user/UserRepository'; @@ -26,6 +26,9 @@ import {createUuid} from 'Util/uuid'; import {ServiceMiddleware} from './ServiceMiddleware'; +import {createBaseEvent} from '../../conversation/EventNew'; +import {ClientEvent} from '../Client'; + function buildServiceMiddleware() { const selfUser = new User(createUuid()); const conversationRepository = {getConversationById: jest.fn()} as unknown as jest.Mocked; @@ -119,7 +122,16 @@ describe('ServiceMiddleware', () => { it('adds meta when services are present in the event with qualified user ids', async () => { const [serviceMiddleware, {userRepository}] = buildServiceMiddleware(); - const event = EventBuilder.build1to1Creation(conversation); + // const event = EventBuilder.build1to1Creation(conversation); + const event = createBaseEvent({ + conversation, + eventType: ClientEvent.CONVERSATION.ONE2ONE_CREATION, + additionalData: { + userIds: conversation.participating_user_ids(), + }, + from: conversation.creator, + timestamp: 0, + }); const service = new User(); service.isService = true; diff --git a/src/script/event/processor/FederationEventProcessor/FederationEventProcessor.ts b/src/script/event/processor/FederationEventProcessor/FederationEventProcessor.ts index 30930899328..0332ca7fa7b 100644 --- a/src/script/event/processor/FederationEventProcessor/FederationEventProcessor.ts +++ b/src/script/event/processor/FederationEventProcessor/FederationEventProcessor.ts @@ -17,13 +17,13 @@ * */ -import {FEDERATION_EVENT} from '@wireapp/api-client/lib/event'; +import {CONVERSATION_EVENT, FEDERATION_EVENT} from '@wireapp/api-client/lib/event'; import {container} from 'tsyringe'; import {debounce} from 'underscore'; import {ConversationState} from 'src/script/conversation/ConversationState'; import {ConversationStatus} from 'src/script/conversation/ConversationStatus'; -import {EventBuilder} from 'src/script/conversation/EventBuilder'; +import {FederationStopEvent, MemberLeaveEvent} from 'src/script/conversation/EventBuilder'; import {Conversation} from 'src/script/entity/Conversation'; import {User} from 'src/script/entity/User'; import {ServerTimeHandler} from 'src/script/time/serverTimeHandler'; @@ -33,6 +33,8 @@ import { getFederationDeleteEventUpdates, } from './ConversationFederationUtils'; +import {createBaseEvent} from '../../../conversation/EventNew'; +import {CONVERSATION} from '../../Client'; import {EventProcessor, IncomingEvent} from '../../EventProcessor'; import {EventRepository} from '../../EventRepository'; @@ -131,7 +133,16 @@ export class FederationEventProcessor implements EventProcessor { private async insertFederationStopSystemMessage(conversation: Conversation, domains: string[]) { const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); - const event = EventBuilder.buildFederationStop(conversation, this.selfUser, domains, currentTimestamp); + // const event = EventBuilder.buildFederationStop(conversation, this.selfUser, domains, currentTimestamp); + const event = createBaseEvent({ + conversation, + eventType: CONVERSATION.FEDERATION_STOP, + additionalData: { + domains, + }, + from: this.selfUser.id, + timestamp: currentTimestamp, + }); await this.eventRepository.injectEvent(event, EventRepository.SOURCE.INJECTED); } @@ -142,12 +153,26 @@ export class FederationEventProcessor implements EventProcessor { */ private async removeMembers(conversation: Conversation, users: User[]) { const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); - const event = EventBuilder.buildMemberLeave( + // const event = EventBuilder.buildMemberLeave( + // conversation, + // users.map(user => user.qualifiedId), + // '', + // currentTimestamp, + // ); + + const userIds = users.map(user => user.qualifiedId); + + const event = createBaseEvent({ conversation, - users.map(user => user.qualifiedId), - '', - currentTimestamp, - ); + eventType: CONVERSATION_EVENT.MEMBER_LEAVE, + additionalData: { + qualified_user_ids: userIds, + user_ids: userIds.map(({id}) => id), + }, + from: '', + timestamp: currentTimestamp, + }); + // Injecting the event will trigger all the handlers that will then actually remove the users from the conversation await this.eventRepository.injectEvent(event); }