From b3d52c87bb66407f02acc1ed0e254a0b10116cb3 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Fri, 11 Aug 2023 15:41:48 +0300 Subject: [PATCH 01/24] feat: Add stopping to federate (WPB-203) --- package.json | 3 +- .../Message/FederationStopMessage.tsx | 32 ++++++++++ .../MessagesList/Message/MessageWrapper.tsx | 4 ++ .../conversation/ConversationRepository.ts | 60 ++++++++++++++++++- src/script/conversation/EventBuilder.ts | 22 +++++++ src/script/conversation/EventMapper.ts | 11 ++++ .../entity/message/FederationStopMessage.ts | 34 +++++++++++ src/script/entity/message/Message.ts | 13 ++-- src/script/event/Client.ts | 1 + src/script/event/EventRepository.ts | 3 + src/script/event/EventType.ts | 1 + src/script/event/EventTypeHandling.ts | 1 + src/script/message/SuperType.ts | 1 + 13 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 src/script/components/MessagesList/Message/FederationStopMessage.tsx create mode 100644 src/script/entity/message/FederationStopMessage.ts diff --git a/package.json b/package.json index 67c24a53902..ff0ff261112 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,14 @@ "@datadog/browser-rum": "^4.45.0", "@emotion/react": "11.11.1", "@types/eslint": "8.37.0", + "@wireapp/api-client": "file:.yalc/@wireapp/api-client", "@wireapp/avs": "9.3.7", "@wireapp/core": "40.7.1", "@wireapp/lru-cache": "3.8.1", "@wireapp/react-ui-kit": "9.7.6", "@wireapp/store-engine-dexie": "2.1.1", "@wireapp/store-engine-sqleet": "1.8.9", - "@wireapp/webapp-events": "0.17.0", + "@wireapp/webapp-events": "file:.yalc/@wireapp/webapp-events", "amplify": "https://github.com/wireapp/amplify#head=master", "beautiful-react-hooks": "^4.3.0", "classnames": "2.3.2", diff --git a/src/script/components/MessagesList/Message/FederationStopMessage.tsx b/src/script/components/MessagesList/Message/FederationStopMessage.tsx new file mode 100644 index 00000000000..456227d1136 --- /dev/null +++ b/src/script/components/MessagesList/Message/FederationStopMessage.tsx @@ -0,0 +1,32 @@ +/* + * Wire + * Copyright (C) 2023 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 React from 'react'; + +import {FederationStopMessage as FederationStopMessageEntity} from '../../../entity/message/FederationStopMessage'; + +export interface FederationStopMessageProps { + message: FederationStopMessageEntity; +} + +const FederationStopMessage: React.FC = ({message: {from}}) => { + return
federation stop message!!!
; +}; + +export {FederationStopMessage}; diff --git a/src/script/components/MessagesList/Message/MessageWrapper.tsx b/src/script/components/MessagesList/Message/MessageWrapper.tsx index e589941d60c..165234644c6 100644 --- a/src/script/components/MessagesList/Message/MessageWrapper.tsx +++ b/src/script/components/MessagesList/Message/MessageWrapper.tsx @@ -38,6 +38,7 @@ import {ContentMessageComponent} from './ContentMessage'; import {DecryptErrorMessage} from './DecryptErrorMessage'; import {DeleteMessage} from './DeleteMessage'; import {FailedToAddUsersMessage} from './FailedToAddUsersMessage'; +import {FederationStopMessage} from './FederationStopMessage'; import {FileTypeRestrictedMessage} from './FileTypeRestrictedMessage'; import {LegalHoldMessage} from './LegalHoldMessage'; import {MemberMessage} from './MemberMessage'; @@ -225,6 +226,9 @@ export const MessageWrapper: React.FC; } + if (message.isFederationStop()) { + return ; + } if (message.isVerification()) { return ; } diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 7d269369440..75fbd539aec 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -39,6 +39,9 @@ import { ConversationRenameEvent, ConversationTypingEvent, CONVERSATION_EVENT, + FederationEvent, + FEDERATION_EVENT, + FederationDeleteEvent, } from '@wireapp/api-client/lib/event'; import {BackendErrorLabel} from '@wireapp/api-client/lib/http/'; import type {BackendError} from '@wireapp/api-client/lib/http/'; @@ -143,7 +146,7 @@ import {UserState} from '../user/UserState'; type ConversationDBChange = {obj: EventRecord; oldObj: EventRecord}; type FetchPromise = {rejectFn: (error: ConversationError) => void; resolveFn: (conversation: Conversation) => void}; type EntityObject = {conversationEntity: Conversation; messageEntity: Message}; -type IncomingEvent = ConversationEvent | ClientConversationEvent; +type IncomingEvent = ConversationEvent | ClientConversationEvent | FederationEvent; export class ConversationRepository { private isBlockingNotificationHandling: boolean; @@ -309,13 +312,67 @@ export class ConversationRepository { amplify.subscribe(WebAppEvents.TEAM.MEMBER_LEAVE, this.teamMemberLeave); amplify.subscribe(WebAppEvents.USER.UNBLOCKED, this.onUnblockUser); amplify.subscribe(WebAppEvents.CONVERSATION.INJECT_LEGAL_HOLD_MESSAGE, this.injectLegalHoldMessage); + amplify.subscribe(WebAppEvents.FEDERATION.EVENT_FROM_BACKEND, this.onFederationEvent); this.eventService.addEventUpdatedListener(this.updateLocalMessageEntity); this.eventService.addEventDeletedListener(this.deleteLocalMessageEntity); window.addEventListener(WebAppEvents.CONVERSATION.JOIN, this.onConversationJoin); + + setTimeout(() => { + this.onFederationEvent({ + type: FEDERATION_EVENT.FEDERATION_DELETE, + data: {domain: 'bella.wire.link'} as FederationDeleteEvent, + }); + }, 10000); } + private readonly onFederationEvent = async (event: FederationEvent) => { + const {type, data} = event; + + if (type === FEDERATION_EVENT.FEDERATION_DELETE) { + const {domain} = data; + const conversationsWithUsersToDelete = this.findConversationsWithUsersByDomain(domain); + for (const {conversation, users} of conversationsWithUsersToDelete) { + if (conversation.is1to1()) { + // todo: remove connection at this point. + return; + } + + for (const user of users) { + await this.removeMember(conversation, user.qualifiedId); + } + + const selfUser = conversation.selfUser(); + + if (!selfUser) { + return; + } + + const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); + const event = EventBuilder.buildFederationStop(conversation, selfUser, users, currentTimestamp); + + await this.eventRepository.injectEvent(event, EventRepository.SOURCE.INJECTED); + } + } + + if (type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED) { + // TODO: Implement connection removed event. + } + }; + + private readonly findConversationsWithUsersByDomain = ( + domain: string, + ): {conversation: Conversation; users: User[]}[] => { + return this.conversationState.conversations().flatMap(conversation => { + const matchingUsers = conversation.participating_user_ets().filter(user => user.domain === domain); + if (matchingUsers.length > 0) { + return [{conversation, users: matchingUsers}]; + } + return []; + }); + }; + private readonly updateLocalMessageEntity = async ({ obj: updatedEvent, oldObj: oldEvent, @@ -2331,6 +2388,7 @@ export class ConversationRepository { case ClientEvent.CONVERSATION.KNOCK: case ClientEvent.CONVERSATION.CALL_TIME_OUT: case ClientEvent.CONVERSATION.FAILED_TO_ADD_USERS: + case ClientEvent.CONVERSATION.FEDERATION_STOP: case ClientEvent.CONVERSATION.LEGAL_HOLD_UPDATE: case ClientEvent.CONVERSATION.LOCATION: case ClientEvent.CONVERSATION.MISSED_MESSAGES: diff --git a/src/script/conversation/EventBuilder.ts b/src/script/conversation/EventBuilder.ts index 5adbdd414cd..209cd50ff45 100644 --- a/src/script/conversation/EventBuilder.ts +++ b/src/script/conversation/EventBuilder.ts @@ -97,6 +97,9 @@ export type DegradedMessageEvent = ConversationEvent & export type DeleteEvent = ConversationEvent<{deleted_time: number; message_id: string; time: string}> & { type: CONVERSATION.MESSAGE_DELETE; }; +export type FederationStopEvent = ConversationEvent<{deletedUsers: User[]}> & { + type: CONVERSATION.FEDERATION_STOP; +}; export type GroupCreationEventData = { allTeamMembers: boolean; name: string; @@ -206,6 +209,7 @@ export type ClientConversationEvent = | ErrorEvent | CompositeMessageAddEvent | ConfirmationEvent + | FederationStopEvent | DeleteEvent | DeleteEverywhereEvent | DegradedMessageEvent @@ -479,6 +483,24 @@ export const EventBuilder = { }; }, + buildFederationStop( + conversationEntity: Conversation, + selfUser: User, + deletedUsers: User[], + currentTimestamp: number, + ): FederationStopEvent { + return { + ...buildQualifiedId(conversationEntity), + data: { + deletedUsers, + }, + id: createUuid(), + from: selfUser.id, + time: conversationEntity.getNextIsoDate(currentTimestamp), + type: CONVERSATION.FEDERATION_STOP, + }; + }, + buildMessageAdd(conversationEntity: Conversation, currentTimestamp: number, senderId: string): MessageAddEvent { return { ...buildQualifiedId(conversationEntity), diff --git a/src/script/conversation/EventMapper.ts b/src/script/conversation/EventMapper.ts index 75699741cd4..1bf42cd2f7a 100644 --- a/src/script/conversation/EventMapper.ts +++ b/src/script/conversation/EventMapper.ts @@ -32,6 +32,7 @@ import { TeamMemberLeaveEvent, ErrorEvent, ClientConversationEvent, + FederationStopEvent, } from './EventBuilder'; import {AssetRemoteData} from '../assets/AssetRemoteData'; @@ -48,6 +49,7 @@ import {ContentMessage} from '../entity/message/ContentMessage'; import {DecryptErrorMessage} from '../entity/message/DecryptErrorMessage'; import {DeleteMessage} from '../entity/message/DeleteMessage'; import {FailedToAddUsersMessage} from '../entity/message/FailedToAddUsersMessage'; +import {FederationStopMessage} from '../entity/message/FederationStopMessage'; import {FileAsset} from '../entity/message/FileAsset'; import {FileTypeRestrictedMessage} from '../entity/message/FileTypeRestrictedMessage'; import {LegalHoldMessage} from '../entity/message/LegalHoldMessage'; @@ -295,6 +297,11 @@ export class EventMapper { break; } + case ClientEvent.CONVERSATION.FEDERATION_STOP: { + messageEntity = this._mapEventFederationStop(event); + break; + } + case ClientEvent.CONVERSATION.LEGAL_HOLD_UPDATE: { messageEntity = this._mapEventLegalHoldUpdate(event); break; @@ -487,6 +494,10 @@ export class EventMapper { return new FailedToAddUsersMessage(data.qualifiedIds, parseInt(time, 10)); } + _mapEventFederationStop({data, time}: FederationStopEvent) { + return new FederationStopMessage(data.deletedUsers, parseInt(time, 10)); + } + _mapEventLegalHoldUpdate({data, timestamp}: LegacyEventRecord) { return new LegalHoldMessage(data.legal_hold_status, timestamp); } diff --git a/src/script/entity/message/FederationStopMessage.ts b/src/script/entity/message/FederationStopMessage.ts new file mode 100644 index 00000000000..8be3165a607 --- /dev/null +++ b/src/script/entity/message/FederationStopMessage.ts @@ -0,0 +1,34 @@ +/* + * Wire + * Copyright (C) 2023 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 {Message} from './Message'; + +import {SuperType} from '../../message/SuperType'; +import {User} from '../User'; + +/** + * Federation stop system message + */ +export class FederationStopMessage extends Message { + constructor(public readonly deletedUsers: User[], timestamp: number) { + super(); + this.super_type = SuperType.FEDERATION_STOP; + this.timestamp(timestamp); + } +} diff --git a/src/script/entity/message/Message.ts b/src/script/entity/message/Message.ts index 77aba96d19d..26307ae4abb 100644 --- a/src/script/entity/message/Message.ts +++ b/src/script/entity/message/Message.ts @@ -31,14 +31,15 @@ import type {CallMessage} from './CallMessage'; import type {CompositeMessage} from './CompositeMessage'; import type {ContentMessage} from './ContentMessage'; import type {DecryptErrorMessage} from './DecryptErrorMessage'; -import {DeleteMessage} from './DeleteMessage'; -import {FailedToAddUsersMessage} from './FailedToAddUsersMessage'; +import type {DeleteMessage} from './DeleteMessage'; +import type {FailedToAddUsersMessage} from './FailedToAddUsersMessage'; +import type {FederationStopMessage} from './FederationStopMessage'; import type {FileAsset} from './FileAsset'; -import {FileTypeRestrictedMessage} from './FileTypeRestrictedMessage'; +import type {FileTypeRestrictedMessage} from './FileTypeRestrictedMessage'; import type {LegalHoldMessage} from './LegalHoldMessage'; import type {LinkPreview} from './LinkPreview'; import type {MemberMessage} from './MemberMessage'; -import {MissedMessage} from './MissedMessage'; +import type {MissedMessage} from './MissedMessage'; import type {PingMessage} from './PingMessage'; import type {SystemMessage} from './SystemMessage'; import type {VerificationMessage} from './VerificationMessage'; @@ -340,6 +341,10 @@ export class Message { return this.super_type === SuperType.VERIFICATION; } + isFederationStop(): this is FederationStopMessage { + return this.super_type === SuperType.FEDERATION_STOP; + } + isLegalHold(): this is LegalHoldMessage { return this.super_type === SuperType.LEGALHOLD; } diff --git a/src/script/event/Client.ts b/src/script/event/Client.ts index 53fa1c66e9e..6885fc7a889 100644 --- a/src/script/event/Client.ts +++ b/src/script/event/Client.ts @@ -44,6 +44,7 @@ export enum CONVERSATION { TEAM_MEMBER_LEAVE = 'conversation.team-member-leave', UNABLE_TO_DECRYPT = 'conversation.unable-to-decrypt', VERIFICATION = 'conversation.verification', + FEDERATION_STOP = 'conversation.federation-stop', VOICE_CHANNEL_ACTIVATE = 'conversation.voice-channel-activate', VOICE_CHANNEL_DEACTIVATE = 'conversation.voice-channel-deactivate', } diff --git a/src/script/event/EventRepository.ts b/src/script/event/EventRepository.ts index 5554f064961..4edb6e3fa39 100644 --- a/src/script/event/EventRepository.ts +++ b/src/script/event/EventRepository.ts @@ -324,6 +324,9 @@ export class EventRepository { case EVENT_TYPE.CALL: amplify.publish(WebAppEvents.CALL.EVENT_FROM_BACKEND, event, source); break; + case EVENT_TYPE.FEDERATION: + amplify.publish(WebAppEvents.FEDERATION.EVENT_FROM_BACKEND, event, source); + break; case EVENT_TYPE.CONVERSATION: amplify.publish(WebAppEvents.CONVERSATION.EVENT_FROM_BACKEND, event, source); break; diff --git a/src/script/event/EventType.ts b/src/script/event/EventType.ts index e185c6c8092..b5c2c1008cd 100644 --- a/src/script/event/EventType.ts +++ b/src/script/event/EventType.ts @@ -21,6 +21,7 @@ export enum EVENT_TYPE { CALL = 'call', CONVERSATION = 'conversation', FEATURE_CONFIG = 'feature-config', + FEDERATION = 'federation', TEAM = 'team', USER = 'user', } diff --git a/src/script/event/EventTypeHandling.ts b/src/script/event/EventTypeHandling.ts index 64d2c525ab3..2e296e682c0 100644 --- a/src/script/event/EventTypeHandling.ts +++ b/src/script/event/EventTypeHandling.ts @@ -32,6 +32,7 @@ export const EventTypeHandling = { STORE: [ CONVERSATION_EVENT.MEMBER_JOIN, CONVERSATION_EVENT.MEMBER_LEAVE, + CONVERSATION_EVENT.FEDERATION_STOP, CONVERSATION_EVENT.MESSAGE_TIMER_UPDATE, CONVERSATION_EVENT.RECEIPT_MODE_UPDATE, CONVERSATION_EVENT.RENAME, diff --git a/src/script/message/SuperType.ts b/src/script/message/SuperType.ts index fe1f11584b0..0a7f6504372 100644 --- a/src/script/message/SuperType.ts +++ b/src/script/message/SuperType.ts @@ -25,6 +25,7 @@ export enum SuperType { CONTENT = 'normal', DELETE = 'delete', DEVICE = 'device', + FEDERATION_STOP = 'federation-stop', FILE_TYPE_RESTRICTED = 'file-type-restricted', LEGALHOLD = 'legal-hold', LOCATION = 'location', From b215a8a85ac47bd0c0c82323e06cb911fba6e0f6 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 14:38:13 +0300 Subject: [PATCH 02/24] disable 1:1 conversations --- .../Message/FederationStopMessage.tsx | 6 ++- .../conversation/ConversationRepository.ts | 53 +++++++++++-------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/script/components/MessagesList/Message/FederationStopMessage.tsx b/src/script/components/MessagesList/Message/FederationStopMessage.tsx index 456227d1136..857fd376d00 100644 --- a/src/script/components/MessagesList/Message/FederationStopMessage.tsx +++ b/src/script/components/MessagesList/Message/FederationStopMessage.tsx @@ -26,7 +26,11 @@ export interface FederationStopMessageProps { } const FederationStopMessage: React.FC = ({message: {from}}) => { - return
federation stop message!!!
; + return ( +
+ Your backend stopped federating with backend URL. Learn more +
+ ); }; export {FederationStopMessage}; diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 75fbd539aec..a8fa95b4303 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -331,41 +331,50 @@ export class ConversationRepository { const {type, data} = event; if (type === FEDERATION_EVENT.FEDERATION_DELETE) { - const {domain} = data; - const conversationsWithUsersToDelete = this.findConversationsWithUsersByDomain(domain); - for (const {conversation, users} of conversationsWithUsersToDelete) { - if (conversation.is1to1()) { - // todo: remove connection at this point. - return; - } + const {domain: deletedDomain} = data; - for (const user of users) { - await this.removeMember(conversation, user.qualifiedId); - } + await this.onFederationDelete(deletedDomain); + } - const selfUser = conversation.selfUser(); + if (type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED) { + // TODO: Implement connection removed event. + } + }; - if (!selfUser) { - return; - } + private onFederationDelete = async (deletedDomain: string) => { + const selfUser = this.userState.self(); - const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); - const event = EventBuilder.buildFederationStop(conversation, selfUser, users, currentTimestamp); + const conversationsWithUsersToDelete = this.findConversationsWithUsersByDomain(deletedDomain); - await this.eventRepository.injectEvent(event, EventRepository.SOURCE.INJECTED); + for (const {conversation, users} of conversationsWithUsersToDelete) { + if (conversation.is1to1()) { + conversation.status(ConversationStatus.PAST_MEMBER); } - } - if (type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED) { - // TODO: Implement connection removed event. + try { + if (conversation.domain === selfUser.qualifiedId.domain) { + for (const user of users) { + await this.removeMember(conversation, user.qualifiedId); + } + } else { + await this.leaveConversation(conversation, false); + } + } catch (error) { + console.warn('failed to remove/leave conversation', error); + } + + const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); + const event = EventBuilder.buildFederationStop(conversation, selfUser, users, currentTimestamp); + + await this.eventRepository.injectEvent(event, EventRepository.SOURCE.INJECTED); } }; private readonly findConversationsWithUsersByDomain = ( - domain: string, + usersDomain: string, ): {conversation: Conversation; users: User[]}[] => { return this.conversationState.conversations().flatMap(conversation => { - const matchingUsers = conversation.participating_user_ets().filter(user => user.domain === domain); + const matchingUsers = conversation.participating_user_ets().filter(user => user.domain === usersDomain); if (matchingUsers.length > 0) { return [{conversation, users: matchingUsers}]; } From 0d371019b2c7af3edd8c8cf1f0b86c6b36bb15bc Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 15:51:48 +0300 Subject: [PATCH 03/24] display domains --- src/i18n/en-US.json | 2 ++ .../Message/FederationStopMessage.tsx | 35 +++++++++++++++++-- .../conversation/ConversationRepository.ts | 2 +- src/script/conversation/EventBuilder.ts | 6 ++-- src/script/conversation/EventMapper.ts | 2 +- .../entity/message/FederationStopMessage.ts | 3 +- 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index 7d0332e9380..fd6e444cb8c 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -548,6 +548,8 @@ "featureConfigChangeModalSelfDeletingMessagesDescriptionItemEnabled": "Self-deleting messages are enabled. You can set a timer before writing a message.", "featureConfigChangeModalSelfDeletingMessagesDescriptionItemEnforced": "Self-deleting messages are now mandatory. New messages will self-delete after {{timeout}}.", "featureConfigChangeModalSelfDeletingMessagesHeadline": "There has been a change in {{brandName}}", + "federationDelete": "Your backend stopped federating with {{backendUrl}}. [bold][link]Learn more[/link][/bold]", + "federationConnectionRemove": "The backends {{backendUrlOne}} and {{backendUrlTwo}} stopped federating. [bold][link]Learn more[/link][/bold]", "fileTypeRestrictedIncoming": "File from [bold]{{name}}[/bold] can’t be opened", "fileTypeRestrictedOutgoing": "Sharing files with the {{fileExt}} extension is not permitted by your organization", "folderViewTooltip": "Folders", diff --git a/src/script/components/MessagesList/Message/FederationStopMessage.tsx b/src/script/components/MessagesList/Message/FederationStopMessage.tsx index 857fd376d00..ddafd86e576 100644 --- a/src/script/components/MessagesList/Message/FederationStopMessage.tsx +++ b/src/script/components/MessagesList/Message/FederationStopMessage.tsx @@ -19,16 +19,47 @@ import React from 'react'; +import {Icon} from 'Components/Icon'; +import {Config} from 'src/script/Config'; +import {useKoSubscribableChildren} from 'Util/ComponentUtil'; +import {replaceLink, t} from 'Util/LocalizerUtil'; + +import {MessageTime} from './MessageTime'; + import {FederationStopMessage as FederationStopMessageEntity} from '../../../entity/message/FederationStopMessage'; export interface FederationStopMessageProps { message: FederationStopMessageEntity; } -const FederationStopMessage: React.FC = ({message: {from}}) => { +const FederationStopMessage: React.FC = ({message}) => { + const {timestamp} = useKoSubscribableChildren(message, ['timestamp']); + const {id, domains} = message; + const link = replaceLink(Config.getConfig().URL.SUPPORT.BUG_REPORT); + return (
- Your backend stopped federating with backend URL. Learn more +
+
+ +
+
+
+ {domains.length === 1 + ? t('federationDelete', {backendUrl: domains[0]}, link) + : t('federationDelete', {backendUrlOne: domains[0], backendUrlTwo: domains[1]}, link)} +
+

+ +

); }; diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index a8fa95b4303..8d824b3a331 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -364,7 +364,7 @@ export class ConversationRepository { } const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); - const event = EventBuilder.buildFederationStop(conversation, selfUser, users, currentTimestamp); + const event = EventBuilder.buildFederationStop(conversation, selfUser, [deletedDomain], currentTimestamp); await this.eventRepository.injectEvent(event, EventRepository.SOURCE.INJECTED); } diff --git a/src/script/conversation/EventBuilder.ts b/src/script/conversation/EventBuilder.ts index 209cd50ff45..e58ba8c98d1 100644 --- a/src/script/conversation/EventBuilder.ts +++ b/src/script/conversation/EventBuilder.ts @@ -97,7 +97,7 @@ export type DegradedMessageEvent = ConversationEvent & export type DeleteEvent = ConversationEvent<{deleted_time: number; message_id: string; time: string}> & { type: CONVERSATION.MESSAGE_DELETE; }; -export type FederationStopEvent = ConversationEvent<{deletedUsers: User[]}> & { +export type FederationStopEvent = ConversationEvent<{domains: string[]}> & { type: CONVERSATION.FEDERATION_STOP; }; export type GroupCreationEventData = { @@ -486,13 +486,13 @@ export const EventBuilder = { buildFederationStop( conversationEntity: Conversation, selfUser: User, - deletedUsers: User[], + domains: string[], currentTimestamp: number, ): FederationStopEvent { return { ...buildQualifiedId(conversationEntity), data: { - deletedUsers, + domains, }, id: createUuid(), from: selfUser.id, diff --git a/src/script/conversation/EventMapper.ts b/src/script/conversation/EventMapper.ts index 1bf42cd2f7a..edcfd133edd 100644 --- a/src/script/conversation/EventMapper.ts +++ b/src/script/conversation/EventMapper.ts @@ -495,7 +495,7 @@ export class EventMapper { } _mapEventFederationStop({data, time}: FederationStopEvent) { - return new FederationStopMessage(data.deletedUsers, parseInt(time, 10)); + return new FederationStopMessage(data.domains, parseInt(time, 10)); } _mapEventLegalHoldUpdate({data, timestamp}: LegacyEventRecord) { diff --git a/src/script/entity/message/FederationStopMessage.ts b/src/script/entity/message/FederationStopMessage.ts index 8be3165a607..87a72dce0c2 100644 --- a/src/script/entity/message/FederationStopMessage.ts +++ b/src/script/entity/message/FederationStopMessage.ts @@ -20,13 +20,12 @@ import {Message} from './Message'; import {SuperType} from '../../message/SuperType'; -import {User} from '../User'; /** * Federation stop system message */ export class FederationStopMessage extends Message { - constructor(public readonly deletedUsers: User[], timestamp: number) { + constructor(public readonly domains: string[], timestamp: number) { super(); this.super_type = SuperType.FEDERATION_STOP; this.timestamp(timestamp); From 54a36f041a8d2f36dcf1a4056c08b318fa38f5cb Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 15:56:33 +0300 Subject: [PATCH 04/24] remove yalc --- package.json | 4 ++-- yarn.lock | 55 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index ff0ff261112..b6e8b8e5c75 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,14 @@ "@datadog/browser-rum": "^4.45.0", "@emotion/react": "11.11.1", "@types/eslint": "8.37.0", - "@wireapp/api-client": "file:.yalc/@wireapp/api-client", + "@wireapp/api-client": "24.21.0", "@wireapp/avs": "9.3.7", "@wireapp/core": "40.7.1", "@wireapp/lru-cache": "3.8.1", "@wireapp/react-ui-kit": "9.7.6", "@wireapp/store-engine-dexie": "2.1.1", "@wireapp/store-engine-sqleet": "1.8.9", - "@wireapp/webapp-events": "file:.yalc/@wireapp/webapp-events", + "@wireapp/webapp-events": "0.18.0", "amplify": "https://github.com/wireapp/amplify#head=master", "beautiful-react-hooks": "^4.3.0", "classnames": "2.3.2", diff --git a/yarn.lock b/yarn.lock index cbe4a5dd4ba..5ef6e43163d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6327,6 +6327,25 @@ __metadata: languageName: node linkType: hard +"@wireapp/api-client@npm:24.21.0": + version: 24.21.0 + resolution: "@wireapp/api-client@npm:24.21.0" + dependencies: + "@wireapp/commons": ^5.1.0 + "@wireapp/priority-queue": ^2.1.1 + "@wireapp/protocol-messaging": 1.44.0 + axios: 1.4.0 + axios-retry: 3.6.0 + http-status-codes: 2.2.0 + logdown: 3.3.1 + reconnecting-websocket: 4.4.0 + spark-md5: 3.0.2 + tough-cookie: 4.1.3 + ws: 8.13.0 + checksum: 261f65bed73815b30e1fdea74caccce34b05ef76539aedb4680ff10d2205e95f208a4c42b0c4e0eba2c44f72f47c3b8b8373720d395d05cdfbae3f482fa8ffdb + languageName: node + linkType: hard + "@wireapp/api-client@npm:^24.20.0": version: 24.20.0 resolution: "@wireapp/api-client@npm:24.20.0" @@ -6599,10 +6618,10 @@ __metadata: languageName: node linkType: hard -"@wireapp/webapp-events@npm:0.17.0": - version: 0.17.0 - resolution: "@wireapp/webapp-events@npm:0.17.0" - checksum: 9257321dc120d7ac33e11c2ae66cc5f04de64fed029c6d50644d86bf283044b3c74e18d487721793171333261b22de2d032266554b95d36f99da623d11d3cc10 +"@wireapp/webapp-events@npm:0.18.0": + version: 0.18.0 + resolution: "@wireapp/webapp-events@npm:0.18.0" + checksum: 8415b690f2faf0da686caa0d909d831ba04e4bd545e612528e5d11c8d2885c7f2e64699ca3396a9b6529e2b1b0e142b61e64e167273361bd7e2c1f665370d411 languageName: node linkType: hard @@ -7249,6 +7268,16 @@ __metadata: languageName: node linkType: hard +"axios-retry@npm:3.6.0": + version: 3.6.0 + resolution: "axios-retry@npm:3.6.0" + dependencies: + "@babel/runtime": ^7.15.4 + is-retry-allowed: ^2.2.0 + checksum: 9ed0879453170a55960dea21116e7f732f1e78acb72eb49d80b1620583814d94bda78200d0767bae82d37cc2ab80752f0aec49717ce4b141858059c0c6b9921c + languageName: node + linkType: hard + "axios@npm:1.4.0": version: 1.4.0 resolution: "axios@npm:1.4.0" @@ -19286,6 +19315,7 @@ dexie@latest: "@types/webpack-env": 1.18.1 "@typescript-eslint/eslint-plugin": ^5.61.0 "@typescript-eslint/parser": ^5.61.0 + "@wireapp/api-client": 24.21.0 "@wireapp/avs": 9.3.7 "@wireapp/copy-config": 2.1.1 "@wireapp/core": 40.7.1 @@ -19296,7 +19326,7 @@ dexie@latest: "@wireapp/store-engine": ^5.1.1 "@wireapp/store-engine-dexie": 2.1.1 "@wireapp/store-engine-sqleet": 1.8.9 - "@wireapp/webapp-events": 0.17.0 + "@wireapp/webapp-events": 0.18.0 adm-zip: 0.5.10 amplify: "https://github.com/wireapp/amplify#head=master" archiver: ^5.3.1 @@ -19709,6 +19739,21 @@ dexie@latest: languageName: node linkType: hard +"ws@npm:8.13.0": + version: 8.13.0 + resolution: "ws@npm:8.13.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 53e991bbf928faf5dc6efac9b8eb9ab6497c69feeb94f963d648b7a3530a720b19ec2e0ec037344257e05a4f35bd9ad04d9de6f289615ffb133282031b18c61c + languageName: node + linkType: hard + "xdg-basedir@npm:^4.0.0": version: 4.0.0 resolution: "xdg-basedir@npm:4.0.0" From 82467e16f2b671f8e0322187d41728ff0cc30581 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 16:48:57 +0300 Subject: [PATCH 05/24] handle federation.connectionRemoved --- .../conversation/ConversationRepository.ts | 91 ++++++++++++++----- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 8d824b3a331..01d3f782875 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -41,7 +41,7 @@ import { CONVERSATION_EVENT, FederationEvent, FEDERATION_EVENT, - FederationDeleteEvent, + FederationConnectionRemovedEvent, } from '@wireapp/api-client/lib/event'; import {BackendErrorLabel} from '@wireapp/api-client/lib/http/'; import type {BackendError} from '@wireapp/api-client/lib/http/'; @@ -321,8 +321,8 @@ export class ConversationRepository { setTimeout(() => { this.onFederationEvent({ - type: FEDERATION_EVENT.FEDERATION_DELETE, - data: {domain: 'bella.wire.link'} as FederationDeleteEvent, + type: FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED, + data: {domains: ['bella.wire.link', 'anta.wire.link']} as FederationConnectionRemovedEvent, }); }, 10000); } @@ -337,25 +337,24 @@ export class ConversationRepository { } if (type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED) { - // TODO: Implement connection removed event. + const {domains: deletedDomains} = data; + this.onFederationConnectionRemove(deletedDomains); } }; private onFederationDelete = async (deletedDomain: string) => { const selfUser = this.userState.self(); - - const conversationsWithUsersToDelete = this.findConversationsWithUsersByDomain(deletedDomain); - - for (const {conversation, users} of conversationsWithUsersToDelete) { + const allConversations = this.conversationState.conversations(); + allConversations.forEach(async conversation => { if (conversation.is1to1()) { conversation.status(ConversationStatus.PAST_MEMBER); } - try { if (conversation.domain === selfUser.qualifiedId.domain) { - for (const user of users) { - await this.removeMember(conversation, user.qualifiedId); - } + await this.removeDeletedFederationUsers( + conversation, + conversation.allUserEntities().filter(user => user.domain === deletedDomain), + ); } else { await this.leaveConversation(conversation, false); } @@ -363,23 +362,65 @@ export class ConversationRepository { console.warn('failed to remove/leave conversation', error); } - const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); - const event = EventBuilder.buildFederationStop(conversation, selfUser, [deletedDomain], currentTimestamp); + await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); + }); + }; - await this.eventRepository.injectEvent(event, EventRepository.SOURCE.INJECTED); - } + private readonly onFederationConnectionRemove = async (domains: string[]) => { + const selfUser = this.userState.self(); + const [domainOne, domainTwo] = domains; + const allConversations = this.conversationState.conversations(); + + allConversations + .filter(conversation => conversation.domain === domainOne) + .forEach(async conversation => { + await this.removeDeletedFederationUsers( + conversation, + conversation.allUserEntities().filter(user => user.domain === domainTwo), + ); + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + }); + + allConversations + .filter(conversation => conversation.domain === domainTwo) + .forEach(async conversation => { + await this.removeDeletedFederationUsers( + conversation, + conversation.allUserEntities().filter(user => user.domain === domainOne), + ); + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + }); + + allConversations + .filter(conversation => conversation.domain === selfUser.qualifiedId.domain) + .forEach(async conversation => { + await this.removeDeletedFederationUsers( + conversation, + conversation.allUserEntities().filter(user => user.domain === domainOne || user.domain === domainTwo), + ); + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + }); }; - private readonly findConversationsWithUsersByDomain = ( - usersDomain: string, - ): {conversation: Conversation; users: User[]}[] => { - return this.conversationState.conversations().flatMap(conversation => { - const matchingUsers = conversation.participating_user_ets().filter(user => user.domain === usersDomain); - if (matchingUsers.length > 0) { - return [{conversation, users: matchingUsers}]; + private readonly removeDeletedFederationUsers = async (conversation: Conversation, usersToRemove: User[]) => { + if (usersToRemove.length === 0) { + return; + } + + try { + for (const user of usersToRemove) { + await this.removeMember(conversation, user.qualifiedId); } - return []; - }); + } catch (error) { + console.warn('failed to remove users', error); + } + }; + + private readonly insertFederationStopSystemMessage = async (conversation: Conversation, domains: string[]) => { + const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); + const selfUser = this.userState.self(); + const event = EventBuilder.buildFederationStop(conversation, selfUser, domains, currentTimestamp); + await this.eventRepository.injectEvent(event, EventRepository.SOURCE.INJECTED); }; private readonly updateLocalMessageEntity = async ({ From cb87abd62198f1ff65fdfe299961664a1dd67605 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 17:06:39 +0300 Subject: [PATCH 06/24] avoid too many system messages --- src/i18n/en-US.json | 4 +- .../Message/FederationStopMessage.tsx | 8 ++-- .../conversation/ConversationRepository.ts | 43 ++++++++++--------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index fd6e444cb8c..5ed46415da0 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -548,8 +548,8 @@ "featureConfigChangeModalSelfDeletingMessagesDescriptionItemEnabled": "Self-deleting messages are enabled. You can set a timer before writing a message.", "featureConfigChangeModalSelfDeletingMessagesDescriptionItemEnforced": "Self-deleting messages are now mandatory. New messages will self-delete after {{timeout}}.", "featureConfigChangeModalSelfDeletingMessagesHeadline": "There has been a change in {{brandName}}", - "federationDelete": "Your backend stopped federating with {{backendUrl}}. [bold][link]Learn more[/link][/bold]", - "federationConnectionRemove": "The backends {{backendUrlOne}} and {{backendUrlTwo}} stopped federating. [bold][link]Learn more[/link][/bold]", + "federationDelete": "Your backend stopped federating with {{backendUrl}}.", + "federationConnectionRemove": "The backends {{backendUrlOne}} and {{backendUrlTwo}} stopped federating.", "fileTypeRestrictedIncoming": "File from [bold]{{name}}[/bold] can’t be opened", "fileTypeRestrictedOutgoing": "Sharing files with the {{fileExt}} extension is not permitted by your organization", "folderViewTooltip": "Folders", diff --git a/src/script/components/MessagesList/Message/FederationStopMessage.tsx b/src/script/components/MessagesList/Message/FederationStopMessage.tsx index ddafd86e576..a4835e7763a 100644 --- a/src/script/components/MessagesList/Message/FederationStopMessage.tsx +++ b/src/script/components/MessagesList/Message/FederationStopMessage.tsx @@ -20,9 +20,8 @@ import React from 'react'; import {Icon} from 'Components/Icon'; -import {Config} from 'src/script/Config'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; -import {replaceLink, t} from 'Util/LocalizerUtil'; +import {t} from 'Util/LocalizerUtil'; import {MessageTime} from './MessageTime'; @@ -35,7 +34,6 @@ export interface FederationStopMessageProps { const FederationStopMessage: React.FC = ({message}) => { const {timestamp} = useKoSubscribableChildren(message, ['timestamp']); const {id, domains} = message; - const link = replaceLink(Config.getConfig().URL.SUPPORT.BUG_REPORT); return (
@@ -50,8 +48,8 @@ const FederationStopMessage: React.FC = ({message}) data-uie-value={`domains-${domains.join('_')}`} > {domains.length === 1 - ? t('federationDelete', {backendUrl: domains[0]}, link) - : t('federationDelete', {backendUrlOne: domains[0], backendUrlTwo: domains[1]}, link)} + ? t('federationDelete', {backendUrl: domains[0]}) + : t('federationConnectionRemove', {backendUrlOne: domains[0], backendUrlTwo: domains[1]})}

user.domain === deletedDomain); + try { if (conversation.domain === selfUser.qualifiedId.domain) { - await this.removeDeletedFederationUsers( - conversation, - conversation.allUserEntities().filter(user => user.domain === deletedDomain), - ); + await this.removeDeletedFederationUsers(conversation, usersToRemove); } else { await this.leaveConversation(conversation, false); } @@ -362,7 +361,9 @@ export class ConversationRepository { console.warn('failed to remove/leave conversation', error); } - await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); + if (usersToRemove.length > 0) { + await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); + } }); }; @@ -374,31 +375,33 @@ export class ConversationRepository { allConversations .filter(conversation => conversation.domain === domainOne) .forEach(async conversation => { - await this.removeDeletedFederationUsers( - conversation, - conversation.allUserEntities().filter(user => user.domain === domainTwo), - ); - await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + const usersToDelete = conversation.allUserEntities().filter(user => user.domain === domainTwo); + await this.removeDeletedFederationUsers(conversation, usersToDelete); + if (usersToDelete.length > 0) { + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + } }); allConversations .filter(conversation => conversation.domain === domainTwo) .forEach(async conversation => { - await this.removeDeletedFederationUsers( - conversation, - conversation.allUserEntities().filter(user => user.domain === domainOne), - ); - await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + const usersToDelete = conversation.allUserEntities().filter(user => user.domain === domainOne); + await this.removeDeletedFederationUsers(conversation, usersToDelete); + if (usersToDelete.length > 0) { + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + } }); allConversations .filter(conversation => conversation.domain === selfUser.qualifiedId.domain) .forEach(async conversation => { - await this.removeDeletedFederationUsers( - conversation, - conversation.allUserEntities().filter(user => user.domain === domainOne || user.domain === domainTwo), - ); - await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + const usersToDelete = conversation + .allUserEntities() + .filter(user => user.domain === domainOne || user.domain === domainTwo); + await this.removeDeletedFederationUsers(conversation, usersToDelete); + if (usersToDelete.length > 0) { + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + } }); }; From 0425f359af709c0053a3efd8ad215a34cf6b4eba Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 17:12:17 +0300 Subject: [PATCH 07/24] check if conversation has members from both backends --- src/script/conversation/ConversationRepository.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index c5dc0855391..7e5fc840a55 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -393,7 +393,20 @@ export class ConversationRepository { }); allConversations - .filter(conversation => conversation.domain === selfUser.qualifiedId.domain) + .filter(conversation => { + if (conversation.domain !== selfUser.qualifiedId.domain) { + return false; + } + + const hasAnyUserFromDomainOne = conversation + .allUserEntities() + .find(user => user.qualifiedId.domain === domainOne); + const hasAnyUserFromDomainTwo = conversation + .allUserEntities() + .find(user => user.qualifiedId.domain === domainTwo); + + return hasAnyUserFromDomainOne && hasAnyUserFromDomainTwo; + }) .forEach(async conversation => { const usersToDelete = conversation .allUserEntities() From 19e80c283a3adaf0bb7fb4c866689951f330488f Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 17:16:28 +0300 Subject: [PATCH 08/24] remove fake event --- src/script/conversation/ConversationRepository.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 7e5fc840a55..cb0a6fdc9e9 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -41,7 +41,6 @@ import { CONVERSATION_EVENT, FederationEvent, FEDERATION_EVENT, - FederationConnectionRemovedEvent, } from '@wireapp/api-client/lib/event'; import {BackendErrorLabel} from '@wireapp/api-client/lib/http/'; import type {BackendError} from '@wireapp/api-client/lib/http/'; @@ -318,13 +317,6 @@ export class ConversationRepository { this.eventService.addEventDeletedListener(this.deleteLocalMessageEntity); window.addEventListener(WebAppEvents.CONVERSATION.JOIN, this.onConversationJoin); - - setTimeout(() => { - this.onFederationEvent({ - type: FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED, - data: {domains: ['bella.wire.link', 'anta.wire.link']} as FederationConnectionRemovedEvent, - }); - }, 10000); } private readonly onFederationEvent = async (event: FederationEvent) => { From 14e5ee9331b4936a008b9d71642eb0803672a4e9 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 17:20:08 +0300 Subject: [PATCH 09/24] bump core --- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 01e726b4715..be994ae415a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "@datadog/browser-rum": "^4.46.0", "@emotion/react": "11.11.1", "@wireapp/avs": "9.3.7", - "@wireapp/core": "40.9.0", + "@wireapp/core": "40.9.1", "@wireapp/lru-cache": "3.8.1", "@wireapp/react-ui-kit": "9.8.0", "@wireapp/store-engine-dexie": "2.1.3", diff --git a/yarn.lock b/yarn.lock index 86f3ca3e273..2e89761cf1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5154,9 +5154,9 @@ __metadata: languageName: node linkType: hard -"@wireapp/api-client@npm:^24.20.3": - version: 24.20.3 - resolution: "@wireapp/api-client@npm:24.20.3" +"@wireapp/api-client@npm:^24.21.0": + version: 24.21.0 + resolution: "@wireapp/api-client@npm:24.21.0" dependencies: "@wireapp/commons": ^5.1.0 "@wireapp/priority-queue": ^2.1.1 @@ -5169,7 +5169,7 @@ __metadata: spark-md5: 3.0.2 tough-cookie: 4.1.3 ws: 8.13.0 - checksum: 29b60507b5f93aae07e89f5c7dc0158b18edb4a9b6699aba99046d68d869659201fd20e72f480823d8f98b554b41eac1ad5feaa5fe2c738a01f123f45173d3cb + checksum: 261f65bed73815b30e1fdea74caccce34b05ef76539aedb4680ff10d2205e95f208a4c42b0c4e0eba2c44f72f47c3b8b8373720d395d05cdfbae3f482fa8ffdb languageName: node linkType: hard @@ -5222,11 +5222,11 @@ __metadata: languageName: node linkType: hard -"@wireapp/core@npm:40.9.0": - version: 40.9.0 - resolution: "@wireapp/core@npm:40.9.0" +"@wireapp/core@npm:40.9.1": + version: 40.9.1 + resolution: "@wireapp/core@npm:40.9.1" dependencies: - "@wireapp/api-client": ^24.20.3 + "@wireapp/api-client": ^24.21.0 "@wireapp/commons": ^5.1.0 "@wireapp/core-crypto": 1.0.0-rc.6 "@wireapp/cryptobox": 12.8.0 @@ -5243,7 +5243,7 @@ __metadata: logdown: 3.3.1 long: ^5.2.0 uuidjs: 4.2.13 - checksum: 0a6de1eb4dcfb5b0079a24ca606bc95c0b289bb5c5d413e0189a144160b9f461c670b971ffd0158647043626779286406f134362bd944551ec40c842373b9178 + checksum: 35a2e882e7eb96c636cf9c8698808ffd05032e0522ab285328d8db81e4ff4ff9c6b01492d1f5b0f239118e97e6ff6fe43cbba6e14dc174e32c26a6ccdd3c7587 languageName: node linkType: hard @@ -18106,7 +18106,7 @@ __metadata: "@types/webpack-env": 1.18.1 "@wireapp/avs": 9.3.7 "@wireapp/copy-config": 2.1.1 - "@wireapp/core": 40.9.0 + "@wireapp/core": 40.9.1 "@wireapp/eslint-config": 3.0.0 "@wireapp/lru-cache": 3.8.1 "@wireapp/prettier-config": 0.6.0 From 2814782bf63ba81a7bdfffb134556125512cb2b2 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 17:27:16 +0300 Subject: [PATCH 10/24] fix type error --- src/script/conversation/AbstractConversationEventHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script/conversation/AbstractConversationEventHandler.ts b/src/script/conversation/AbstractConversationEventHandler.ts index 5e7a5a99598..06bfa81c67a 100644 --- a/src/script/conversation/AbstractConversationEventHandler.ts +++ b/src/script/conversation/AbstractConversationEventHandler.ts @@ -17,7 +17,7 @@ * */ -import {ConversationEvent} from '@wireapp/api-client/lib/event'; +import {ConversationEvent, FederationEvent} from '@wireapp/api-client/lib/event'; import {ClientConversationEvent} from './EventBuilder'; @@ -52,7 +52,7 @@ export class AbstractConversationEventHandler { */ handleConversationEvent( conversationEntity: Conversation, - eventJson: ConversationEvent | ClientConversationEvent, + eventJson: ConversationEvent | ClientConversationEvent | FederationEvent, ): Promise { const handler = this.eventHandlingConfig[eventJson.type] || (() => Promise.resolve()); return handler.bind(this)(conversationEntity, eventJson); From c0754bc7bb78fa07969b618934c117af9d530f97 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 17:36:25 +0300 Subject: [PATCH 11/24] fix more type errors --- src/script/conversation/ConversationRepository.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index f9d1ebbcc94..c5c8a27c585 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -2133,6 +2133,15 @@ export class ConversationRepository { // Prevent logging typing events return; } + // event.type + if ( + event.type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED || + event.type === FEDERATION_EVENT.FEDERATION_DELETE + ) { + this.logger.info(`Federation Event of type ${event.type} received.`); + return; + } + const {time, from, qualified_conversation, type} = event; const extra: Record = {}; extra.messageId = 'id' in event && event.id; From 64271936a2d432652892d23594b3d70a3034898d Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 17:36:47 +0300 Subject: [PATCH 12/24] remove extra comment --- src/script/conversation/ConversationRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index c5c8a27c585..b77172a073e 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -2133,7 +2133,7 @@ export class ConversationRepository { // Prevent logging typing events return; } - // event.type + if ( event.type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED || event.type === FEDERATION_EVENT.FEDERATION_DELETE From 1d1458ab37f753f3f044907a9616d892ed4249b6 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 17:47:35 +0300 Subject: [PATCH 13/24] remove event save type error --- src/script/conversation/ConversationRepository.ts | 10 +--------- src/script/event/EventTypeHandling.ts | 1 - 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index b77172a073e..15d54735fb1 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -146,7 +146,7 @@ import {UserState} from '../user/UserState'; type ConversationDBChange = {obj: EventRecord; oldObj: EventRecord}; type FetchPromise = {rejectFn: (error: ConversationError) => void; resolveFn: (conversation: Conversation) => void}; type EntityObject = {conversationEntity: Conversation; messageEntity: Message}; -type IncomingEvent = ConversationEvent | ClientConversationEvent | FederationEvent; +type IncomingEvent = ConversationEvent | ClientConversationEvent; export class ConversationRepository { private isBlockingNotificationHandling: boolean; @@ -2134,14 +2134,6 @@ export class ConversationRepository { return; } - if ( - event.type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED || - event.type === FEDERATION_EVENT.FEDERATION_DELETE - ) { - this.logger.info(`Federation Event of type ${event.type} received.`); - return; - } - const {time, from, qualified_conversation, type} = event; const extra: Record = {}; extra.messageId = 'id' in event && event.id; diff --git a/src/script/event/EventTypeHandling.ts b/src/script/event/EventTypeHandling.ts index 2e296e682c0..64d2c525ab3 100644 --- a/src/script/event/EventTypeHandling.ts +++ b/src/script/event/EventTypeHandling.ts @@ -32,7 +32,6 @@ export const EventTypeHandling = { STORE: [ CONVERSATION_EVENT.MEMBER_JOIN, CONVERSATION_EVENT.MEMBER_LEAVE, - CONVERSATION_EVENT.FEDERATION_STOP, CONVERSATION_EVENT.MESSAGE_TIMER_UPDATE, CONVERSATION_EVENT.RECEIPT_MODE_UPDATE, CONVERSATION_EVENT.RENAME, From 1e1df3e79f7c5e2b090e7b5f06fd6c89c0364e61 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 18:13:36 +0300 Subject: [PATCH 14/24] persist system message --- src/script/event/EventTypeHandling.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/event/EventTypeHandling.ts b/src/script/event/EventTypeHandling.ts index 64d2c525ab3..d6d164e7400 100644 --- a/src/script/event/EventTypeHandling.ts +++ b/src/script/event/EventTypeHandling.ts @@ -35,6 +35,7 @@ export const EventTypeHandling = { CONVERSATION_EVENT.MESSAGE_TIMER_UPDATE, CONVERSATION_EVENT.RECEIPT_MODE_UPDATE, CONVERSATION_EVENT.RENAME, + ClientEvent.CONVERSATION.FEDERATION_STOP, ClientEvent.CONVERSATION.ASSET_ADD, ClientEvent.CONVERSATION.COMPOSITE_MESSAGE_ADD, ClientEvent.CONVERSATION.DELETE_EVERYWHERE, From 51e7e523a7182e23621ee3b00847ca189418b134 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 19:04:20 +0300 Subject: [PATCH 15/24] offline delete --- .../conversation/ConversationRepository.ts | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 15d54735fb1..17179139af6 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -331,32 +331,36 @@ export class ConversationRepository { if (type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED) { const {domains: deletedDomains} = data; - this.onFederationConnectionRemove(deletedDomains); + await this.onFederationConnectionRemove(deletedDomains); } }; private onFederationDelete = async (deletedDomain: string) => { - const selfUser = this.userState.self(); - const allConversations = this.conversationState.conversations(); - allConversations.forEach(async conversation => { + const conversationsToLeave = this.conversationState + .conversations() + .filter(conversation => conversation.domain === deletedDomain); + + conversationsToLeave.forEach(async conversation => { + await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); if (conversation.is1to1()) { conversation.status(ConversationStatus.PAST_MEMBER); + return; } - const usersToRemove = conversation.allUserEntities().filter(user => user.domain === deletedDomain); - try { - if (conversation.domain === selfUser.qualifiedId.domain) { - await this.removeDeletedFederationUsers(conversation, usersToRemove); - } else { - await this.leaveConversation(conversation, false); - } - } catch (error) { - console.warn('failed to remove/leave conversation', error); - } + await this.leaveConversation(conversation, false); + }); + + const conversationsToRemoveTheirDeletedDomainUsers = this.conversationState + .conversations() + .filter(conversation => conversation.domain !== deletedDomain); - if (usersToRemove.length > 0) { - await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); + conversationsToRemoveTheirDeletedDomainUsers.forEach(async conversation => { + const usersToRemove = conversation.allUserEntities().filter(user => user.domain === deletedDomain); + if (usersToRemove.length === 0) { + return; } + await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); + await this.removeDeletedFederationUsers(conversation, usersToRemove); }); }; @@ -369,8 +373,8 @@ export class ConversationRepository { .filter(conversation => conversation.domain === domainOne) .forEach(async conversation => { const usersToDelete = conversation.allUserEntities().filter(user => user.domain === domainTwo); - await this.removeDeletedFederationUsers(conversation, usersToDelete); if (usersToDelete.length > 0) { + await this.removeDeletedFederationUsers(conversation, usersToDelete); await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); } }); @@ -379,8 +383,8 @@ export class ConversationRepository { .filter(conversation => conversation.domain === domainTwo) .forEach(async conversation => { const usersToDelete = conversation.allUserEntities().filter(user => user.domain === domainOne); - await this.removeDeletedFederationUsers(conversation, usersToDelete); if (usersToDelete.length > 0) { + await this.removeDeletedFederationUsers(conversation, usersToDelete); await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); } }); @@ -404,8 +408,8 @@ export class ConversationRepository { const usersToDelete = conversation .allUserEntities() .filter(user => user.domain === domainOne || user.domain === domainTwo); - await this.removeDeletedFederationUsers(conversation, usersToDelete); if (usersToDelete.length > 0) { + await this.removeDeletedFederationUsers(conversation, usersToDelete); await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); } }); @@ -418,7 +422,7 @@ export class ConversationRepository { try { for (const user of usersToRemove) { - await this.removeMember(conversation, user.qualifiedId); + await this.removeMember(conversation, user.qualifiedId, {localOnly: true}); } } catch (error) { console.warn('failed to remove users', error); @@ -1730,18 +1734,18 @@ export class ConversationRepository { * @param userId ID of member to be removed from the conversation * @returns Resolves when member was removed from the conversation */ - private async removeMemberFromConversation(conversationEntity: Conversation, userId: QualifiedId) { - const response = await this.core.service!.conversation.removeUserFromConversation( - conversationEntity.qualifiedId, - userId, - ); + private async removeMemberFromConversation(conversationEntity: Conversation, userId: QualifiedId, localOnly = false) { + const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); + const response = localOnly + ? EventBuilder.buildMemberLeave(conversationEntity, userId, true, currentTimestamp) + : await this.core.service!.conversation.removeUserFromConversation(conversationEntity.qualifiedId, userId); + const roles = conversationEntity.roles(); delete roles[userId.id]; conversationEntity.roles(roles); - const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); - const event = response || EventBuilder.buildMemberLeave(conversationEntity, userId, true, currentTimestamp); - await this.eventRepository.injectEvent(event, EventRepository.SOURCE.BACKEND_RESPONSE); - return event; + await this.eventRepository.injectEvent(response, EventRepository.SOURCE.BACKEND_RESPONSE); + + return response; } /** @@ -1767,7 +1771,11 @@ export class ConversationRepository { * @param clearContent Should we clear the conversation content from the database? * @returns Resolves when member was removed from the conversation */ - public async removeMember(conversationEntity: Conversation, userId: QualifiedId, clearContent: boolean = false) { + public async removeMember( + conversationEntity: Conversation, + userId: QualifiedId, + {clearContent = false, localOnly = false} = {}, + ) { const isUserLeaving = this.userState.self().qualifiedId.id === userId.id; const isMLSConversation = conversationEntity.isUsingMLSProtocol; @@ -1777,7 +1785,7 @@ export class ConversationRepository { return isMLSConversation ? this.removeMemberFromMLSConversation(conversationEntity, userId) - : this.removeMemberFromConversation(conversationEntity, userId); + : this.removeMemberFromConversation(conversationEntity, userId, localOnly); } /** From 53d9ff3aa651f6af0b0585b3938fcc50d367d6e4 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 19:22:24 +0300 Subject: [PATCH 16/24] fix type error --- src/script/view_model/ActionsViewModel.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/script/view_model/ActionsViewModel.ts b/src/script/view_model/ActionsViewModel.ts index f552629d274..a36cdf56aa5 100644 --- a/src/script/view_model/ActionsViewModel.ts +++ b/src/script/view_model/ActionsViewModel.ts @@ -267,11 +267,9 @@ export class ActionsViewModel { PrimaryModal.show(PrimaryModal.type.OPTION, { primaryAction: { action: async (clearContent = false) => { - await this.conversationRepository.removeMember( - conversationEntity, - this.userState.self().qualifiedId, + await this.conversationRepository.removeMember(conversationEntity, this.userState.self().qualifiedId, { clearContent, - ); + }); resolve(); }, From 371458ebca14d841fa6402133612eb5c45b3bab5 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Mon, 14 Aug 2023 19:30:19 +0300 Subject: [PATCH 17/24] fix lint error --- src/script/entity/message/FederationStopMessage.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/script/entity/message/FederationStopMessage.ts b/src/script/entity/message/FederationStopMessage.ts index 87a72dce0c2..c9a5bbfca22 100644 --- a/src/script/entity/message/FederationStopMessage.ts +++ b/src/script/entity/message/FederationStopMessage.ts @@ -25,7 +25,10 @@ import {SuperType} from '../../message/SuperType'; * Federation stop system message */ export class FederationStopMessage extends Message { - constructor(public readonly domains: string[], timestamp: number) { + constructor( + public readonly domains: string[], + timestamp: number, + ) { super(); this.super_type = SuperType.FEDERATION_STOP; this.timestamp(timestamp); From 9038815f4d58ecb2ac0781d0380794e49ac2f4cd Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Tue, 15 Aug 2023 12:14:14 +0300 Subject: [PATCH 18/24] add jsdoc --- .../conversation/ConversationRepository.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 17179139af6..b4e13293b6a 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -335,6 +335,14 @@ export class ConversationRepository { } }; + /** + * For the `federation.delete` event: (Backend A has stopped federating with us) + - receive the event from backend + - leave the conversations locally that are owned by the backend A which was deleted. + - remove the deleted backend A users locally from our own conversations. + - insert system message to the affected conversations about federation termination. + * @param deletedDomain the domain that stopped federating + */ private onFederationDelete = async (deletedDomain: string) => { const conversationsToLeave = this.conversationState .conversations() @@ -364,6 +372,20 @@ export class ConversationRepository { }); }; + /** + * For the `federation.connectionRemoved` event: (Backend A & B stopped federating, user is on C) + - receive the event from backend + - Identify all conversations that are not owned from A or B domain and that contain users from A and B + - remove users from A and B from those conversations + - insert system message in those conversations about backend A and B stopping to federate + - identify all conversations owned by domain A that contains users from B + - remove users from B from those conversations + - insert system message in those conversations about backend A and B stopping to federate + - Identify all conversations owned by domain B that contains users from A + - remove users from A from those conversations + - insert system message in those conversations about backend A and B stopping to federate + * @param domains The domains that stopped federating with each other + */ private readonly onFederationConnectionRemove = async (domains: string[]) => { const selfUser = this.userState.self(); const [domainOne, domainTwo] = domains; From 76d1df00d310492f36e57e1f9338a2982bc423b7 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Tue, 15 Aug 2023 12:17:28 +0300 Subject: [PATCH 19/24] refactor: use switch statement --- .../conversation/ConversationRepository.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index b4e13293b6a..5403071ed51 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -323,15 +323,17 @@ export class ConversationRepository { private readonly onFederationEvent = async (event: FederationEvent) => { const {type, data} = event; - if (type === FEDERATION_EVENT.FEDERATION_DELETE) { - const {domain: deletedDomain} = data; + switch (type) { + case FEDERATION_EVENT.FEDERATION_DELETE: + const {domain: deletedDomain} = data; + await this.onFederationDelete(deletedDomain); - await this.onFederationDelete(deletedDomain); - } + break; + case FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED: + const {domains: deletedDomains} = data; + await this.onFederationConnectionRemove(deletedDomains); - if (type === FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED) { - const {domains: deletedDomains} = data; - await this.onFederationConnectionRemove(deletedDomains); + break; } }; From a692a88f9509292dee314977cfe476d1884c1eff Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Tue, 15 Aug 2023 15:44:19 +0300 Subject: [PATCH 20/24] fix bugs --- package.json | 2 +- .../conversation/ConversationRepository.ts | 52 +++++++++++++------ yarn.lock | 20 +++---- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index be994ae415a..5e78919f1b6 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "@datadog/browser-rum": "^4.46.0", "@emotion/react": "11.11.1", "@wireapp/avs": "9.3.7", - "@wireapp/core": "40.9.1", + "@wireapp/core": "40.9.2", "@wireapp/lru-cache": "3.8.1", "@wireapp/react-ui-kit": "9.8.0", "@wireapp/store-engine-dexie": "2.1.3", diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 5403071ed51..3925f2183a9 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -318,19 +318,23 @@ export class ConversationRepository { this.eventService.addEventDeletedListener(this.deleteLocalMessageEntity); window.addEventListener(WebAppEvents.CONVERSATION.JOIN, this.onConversationJoin); + + window.fireFederationDelete = () => { + this.onFederationEvent({type: FEDERATION_EVENT.FEDERATION_DELETE, domain: 'bella.wire.link'}); + }; } private readonly onFederationEvent = async (event: FederationEvent) => { - const {type, data} = event; + const {type} = event; switch (type) { case FEDERATION_EVENT.FEDERATION_DELETE: - const {domain: deletedDomain} = data; + const {domain: deletedDomain} = event; await this.onFederationDelete(deletedDomain); break; case FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED: - const {domains: deletedDomains} = data; + const {domains: deletedDomains} = event; await this.onFederationConnectionRemove(deletedDomains); break; @@ -357,7 +361,7 @@ export class ConversationRepository { return; } - await this.leaveConversation(conversation, false); + await this.leaveConversation(conversation, {clearContent: false, localOnly: true}); }); const conversationsToRemoveTheirDeletedDomainUsers = this.conversationState @@ -365,6 +369,11 @@ export class ConversationRepository { .filter(conversation => conversation.domain !== deletedDomain); conversationsToRemoveTheirDeletedDomainUsers.forEach(async conversation => { + if (conversation.is1to1() && conversation.firstUserEntity().qualifiedId.domain === deletedDomain) { + conversation.status(ConversationStatus.PAST_MEMBER); + await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); + return; + } const usersToRemove = conversation.allUserEntities().filter(user => user.domain === deletedDomain); if (usersToRemove.length === 0) { return; @@ -1738,13 +1747,20 @@ export class ConversationRepository { * @param userId ID of member to be removed from the conversation * @returns Resolves when member was removed from the conversation */ - private async removeMemberFromMLSConversation(conversationEntity: Conversation, userId: QualifiedId) { + private async removeMemberFromMLSConversation( + conversationEntity: Conversation, + userId: QualifiedId, + {localOnly = false} = {}, + ) { + const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); const {groupId, qualifiedId} = conversationEntity; - const {events} = await this.core.service!.conversation.removeUsersFromMLSConversation({ - conversationId: qualifiedId, - groupId, - qualifiedUserIds: [userId], - }); + const {events} = localOnly + ? {events: [EventBuilder.buildMemberLeave(conversationEntity, userId, true, currentTimestamp)]} + : await this.core.service!.conversation.removeUsersFromMLSConversation({ + conversationId: qualifiedId, + groupId, + qualifiedUserIds: [userId], + }); if (!!events.length) { events.forEach(event => this.eventRepository.injectEvent(event)); @@ -1758,7 +1774,11 @@ export class ConversationRepository { * @param userId ID of member to be removed from the conversation * @returns Resolves when member was removed from the conversation */ - private async removeMemberFromConversation(conversationEntity: Conversation, userId: QualifiedId, localOnly = false) { + private async removeMemberFromConversation( + conversationEntity: Conversation, + userId: QualifiedId, + {localOnly = false}, + ) { const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); const response = localOnly ? EventBuilder.buildMemberLeave(conversationEntity, userId, true, currentTimestamp) @@ -1779,12 +1799,12 @@ export class ConversationRepository { * @param clearContent Should we clear the conversation content from the database? * @returns Resolves when user was removed from the conversation */ - private async leaveConversation(conversationEntity: Conversation, clearContent: boolean) { + private async leaveConversation(conversationEntity: Conversation, {clearContent = false, localOnly = false} = {}) { const userQualifiedId = this.userState.self().qualifiedId; return clearContent ? this.clearConversation(conversationEntity, true) - : this.removeMemberFromConversation(conversationEntity, userQualifiedId); + : this.removeMemberFromConversation(conversationEntity, userQualifiedId, {localOnly}); } /** @@ -1804,12 +1824,12 @@ export class ConversationRepository { const isMLSConversation = conversationEntity.isUsingMLSProtocol; if (isUserLeaving) { - return this.leaveConversation(conversationEntity, clearContent); + return this.leaveConversation(conversationEntity, {clearContent}); } return isMLSConversation - ? this.removeMemberFromMLSConversation(conversationEntity, userId) - : this.removeMemberFromConversation(conversationEntity, userId, localOnly); + ? this.removeMemberFromMLSConversation(conversationEntity, userId, {localOnly}) + : this.removeMemberFromConversation(conversationEntity, userId, {localOnly}); } /** diff --git a/yarn.lock b/yarn.lock index 2e89761cf1d..7fa328b3cc1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5154,9 +5154,9 @@ __metadata: languageName: node linkType: hard -"@wireapp/api-client@npm:^24.21.0": - version: 24.21.0 - resolution: "@wireapp/api-client@npm:24.21.0" +"@wireapp/api-client@npm:^24.21.1": + version: 24.21.1 + resolution: "@wireapp/api-client@npm:24.21.1" dependencies: "@wireapp/commons": ^5.1.0 "@wireapp/priority-queue": ^2.1.1 @@ -5169,7 +5169,7 @@ __metadata: spark-md5: 3.0.2 tough-cookie: 4.1.3 ws: 8.13.0 - checksum: 261f65bed73815b30e1fdea74caccce34b05ef76539aedb4680ff10d2205e95f208a4c42b0c4e0eba2c44f72f47c3b8b8373720d395d05cdfbae3f482fa8ffdb + checksum: b92f08d0b96b43f2c6d34c79d93fa547fbd0ae1a1962eba06cec037d2af74e92037352322cdc1ead13c94b5548f0960dad402f4431a4876a556429cc0fbc1982 languageName: node linkType: hard @@ -5222,11 +5222,11 @@ __metadata: languageName: node linkType: hard -"@wireapp/core@npm:40.9.1": - version: 40.9.1 - resolution: "@wireapp/core@npm:40.9.1" +"@wireapp/core@npm:40.9.2": + version: 40.9.2 + resolution: "@wireapp/core@npm:40.9.2" dependencies: - "@wireapp/api-client": ^24.21.0 + "@wireapp/api-client": ^24.21.1 "@wireapp/commons": ^5.1.0 "@wireapp/core-crypto": 1.0.0-rc.6 "@wireapp/cryptobox": 12.8.0 @@ -5243,7 +5243,7 @@ __metadata: logdown: 3.3.1 long: ^5.2.0 uuidjs: 4.2.13 - checksum: 35a2e882e7eb96c636cf9c8698808ffd05032e0522ab285328d8db81e4ff4ff9c6b01492d1f5b0f239118e97e6ff6fe43cbba6e14dc174e32c26a6ccdd3c7587 + checksum: 9d5f07e66345e74307424799b5eb60466bc81244f47b74d8e5fa5f3e850ebc5fa95ab963448658d7a04a71b31c284a4d9061e4d0dce111b4258dacb87cbbccf1 languageName: node linkType: hard @@ -18106,7 +18106,7 @@ __metadata: "@types/webpack-env": 1.18.1 "@wireapp/avs": 9.3.7 "@wireapp/copy-config": 2.1.1 - "@wireapp/core": 40.9.1 + "@wireapp/core": 40.9.2 "@wireapp/eslint-config": 3.0.0 "@wireapp/lru-cache": 3.8.1 "@wireapp/prettier-config": 0.6.0 From d5a3f6e2a1229e4a18af4d6f775e87b44a1286dd Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Tue, 15 Aug 2023 16:27:16 +0300 Subject: [PATCH 21/24] add learn more link and bold --- server/config/client.config.ts | 1 + server/config/env.ts | 2 ++ src/i18n/en-US.json | 4 +-- .../Message/FederationStopMessage.tsx | 31 ++++++++++++++++--- .../MessagesList/Message/MessageWrapper.tsx | 4 +-- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/server/config/client.config.ts b/server/config/client.config.ts index 1a0f3f47961..c284aaf69c7 100644 --- a/server/config/client.config.ts +++ b/server/config/client.config.ts @@ -93,6 +93,7 @@ export function generateConfig(params: ConfigGeneratorParams, env: Env) { PRIVACY_VERIFY_FINGERPRINT: env.URL_SUPPORT_PRIVACY_VERIFY_FINGERPRINT, SCREEN_ACCESS_DENIED: env.URL_SUPPORT_SCREEN_ACCESS_DENIED, OFFLINE_BACKEND: env.URL_SUPPORT_OFFLINE_BACKEND, + FEDERATION_STOP: env.URL_SUPPORT_FEDERATION_STOP, }, TEAMS_BASE: env.URL_TEAMS_BASE, TEAMS_CREATE: env.URL_TEAMS_CREATE, diff --git a/server/config/env.ts b/server/config/env.ts index c318a37bd97..2bea6353f0a 100644 --- a/server/config/env.ts +++ b/server/config/env.ts @@ -243,6 +243,8 @@ export type Env = { URL_SUPPORT_OFFLINE_BACKEND: string; + URL_SUPPORT_FEDERATION_STOP: string; + URL_WHATS_NEW: string; /** Content Security Policy diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index 5d1beef288d..bc6b1c081e0 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -548,8 +548,8 @@ "featureConfigChangeModalSelfDeletingMessagesDescriptionItemEnabled": "Self-deleting messages are enabled. You can set a timer before writing a message.", "featureConfigChangeModalSelfDeletingMessagesDescriptionItemEnforced": "Self-deleting messages are now mandatory. New messages will self-delete after {{timeout}}.", "featureConfigChangeModalSelfDeletingMessagesHeadline": "There has been a change in {{brandName}}", - "federationDelete": "Your backend stopped federating with {{backendUrl}}.", - "federationConnectionRemove": "The backends {{backendUrlOne}} and {{backendUrlTwo}} stopped federating.", + "federationDelete": "[bold]Your backend[/bold] stopped federating with [bold]{{backendUrl}}.[/bold]", + "federationConnectionRemove": "The backends [bold]{{backendUrlOne}}[/bold] and [bold]{{backendUrlTwo}}[/bold] stopped federating.", "fileTypeRestrictedIncoming": "File from [bold]{{name}}[/bold] can’t be opened", "fileTypeRestrictedOutgoing": "Sharing files with the {{fileExt}} extension is not permitted by your organization", "folderViewTooltip": "Folders", diff --git a/src/script/components/MessagesList/Message/FederationStopMessage.tsx b/src/script/components/MessagesList/Message/FederationStopMessage.tsx index a4835e7763a..312951c9bc5 100644 --- a/src/script/components/MessagesList/Message/FederationStopMessage.tsx +++ b/src/script/components/MessagesList/Message/FederationStopMessage.tsx @@ -19,21 +19,29 @@ import React from 'react'; +import {Link, LinkVariant} from '@wireapp/react-ui-kit'; + import {Icon} from 'Components/Icon'; +import {Config} from 'src/script/Config'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {t} from 'Util/LocalizerUtil'; import {MessageTime} from './MessageTime'; +import {useMessageFocusedTabIndex} from './util'; import {FederationStopMessage as FederationStopMessageEntity} from '../../../entity/message/FederationStopMessage'; export interface FederationStopMessageProps { message: FederationStopMessageEntity; + isMessageFocused: boolean; } -const FederationStopMessage: React.FC = ({message}) => { +const config = Config.getConfig(); + +const FederationStopMessage: React.FC = ({message, isMessageFocused}) => { const {timestamp} = useKoSubscribableChildren(message, ['timestamp']); const {id, domains} = message; + const messageFocusedTabIndex = useMessageFocusedTabIndex(isMessageFocused); return (

@@ -47,9 +55,24 @@ const FederationStopMessage: React.FC = ({message}) data-uie-name="element-message-failed-to-add-users" data-uie-value={`domains-${domains.join('_')}`} > - {domains.length === 1 - ? t('federationDelete', {backendUrl: domains[0]}) - : t('federationConnectionRemove', {backendUrlOne: domains[0], backendUrlTwo: domains[1]})} + + + {t('offlineBackendLearnMore')} +

{ const findMessage = async (conversation: Conversation, messageId: string) => { @@ -227,7 +225,7 @@ export const MessageWrapper: React.FC; } if (message.isFederationStop()) { - return ; + return ; } if (message.isVerification()) { return ; From e1400262b3408e067590548761df25bbad3550bc Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Tue, 15 Aug 2023 16:33:35 +0300 Subject: [PATCH 22/24] remove debug util --- src/script/conversation/ConversationRepository.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 3925f2183a9..59ffbb2da2e 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -318,10 +318,6 @@ export class ConversationRepository { this.eventService.addEventDeletedListener(this.deleteLocalMessageEntity); window.addEventListener(WebAppEvents.CONVERSATION.JOIN, this.onConversationJoin); - - window.fireFederationDelete = () => { - this.onFederationEvent({type: FEDERATION_EVENT.FEDERATION_DELETE, domain: 'bella.wire.link'}); - }; } private readonly onFederationEvent = async (event: FederationEvent) => { From fc35485500af1f1fc508cf1a7a29b10e46460b8a Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Tue, 15 Aug 2023 16:35:45 +0300 Subject: [PATCH 23/24] refactor --- src/script/conversation/ConversationRepository.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 59ffbb2da2e..740ec4fb38b 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -424,19 +424,14 @@ export class ConversationRepository { return false; } - const hasAnyUserFromDomainOne = conversation - .allUserEntities() - .find(user => user.qualifiedId.domain === domainOne); - const hasAnyUserFromDomainTwo = conversation - .allUserEntities() - .find(user => user.qualifiedId.domain === domainTwo); + const userDomains = new Set(conversation.allUserEntities().map(user => user.qualifiedId.domain)); - return hasAnyUserFromDomainOne && hasAnyUserFromDomainTwo; + return userDomains.has(domainOne) && userDomains.has(domainTwo); }) .forEach(async conversation => { const usersToDelete = conversation .allUserEntities() - .filter(user => user.domain === domainOne || user.domain === domainTwo); + .filter(user => [domainOne, domainTwo].includes(user.domain)); if (usersToDelete.length > 0) { await this.removeDeletedFederationUsers(conversation, usersToDelete); await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); From 1be862675c8402e6bbdd77e248e8ad409ddb637b Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Wed, 16 Aug 2023 13:02:04 +0300 Subject: [PATCH 24/24] feat: debounce function --- src/script/conversation/ConversationRepository.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 64a01a92fa2..66ff4660fa8 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -49,7 +49,7 @@ import {MLSReturnType} from '@wireapp/core/lib/conversation'; import {amplify} from 'amplify'; import {StatusCodes as HTTP_STATUS} from 'http-status-codes'; import {container} from 'tsyringe'; -import {flatten} from 'underscore'; +import {debounce, flatten} from 'underscore'; import {Asset as ProtobufAsset, Confirmation, LegalHoldStatus} from '@wireapp/protocol-messaging'; import {WebAppEvents} from '@wireapp/webapp-events'; @@ -311,7 +311,7 @@ export class ConversationRepository { amplify.subscribe(WebAppEvents.TEAM.MEMBER_LEAVE, this.teamMemberLeave); amplify.subscribe(WebAppEvents.USER.UNBLOCKED, this.onUnblockUser); amplify.subscribe(WebAppEvents.CONVERSATION.INJECT_LEGAL_HOLD_MESSAGE, this.injectLegalHoldMessage); - amplify.subscribe(WebAppEvents.FEDERATION.EVENT_FROM_BACKEND, this.onFederationEvent); + amplify.subscribe(WebAppEvents.FEDERATION.EVENT_FROM_BACKEND, debounce(this.onFederationEvent, 1000)); this.eventService.addEventUpdatedListener(this.updateLocalMessageEntity); this.eventService.addEventDeletedListener(this.deleteLocalMessageEntity);