diff --git a/package.json b/package.json index b0a50b87794..308b591d343 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,12 @@ "@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.2", "@wireapp/lru-cache": "3.8.1", "@wireapp/react-ui-kit": "9.8.0", "@wireapp/store-engine-dexie": "2.1.3", "@wireapp/store-engine-sqleet": "1.8.9", - "@wireapp/webapp-events": "0.17.0", + "@wireapp/webapp-events": "0.18.0", "amplify": "https://github.com/wireapp/amplify#head=master", "beautiful-react-hooks": "^5.0.0", "classnames": "2.3.2", 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 57135e5f866..b4da1442a51 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": "[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 new file mode 100644 index 00000000000..312951c9bc5 --- /dev/null +++ b/src/script/components/MessagesList/Message/FederationStopMessage.tsx @@ -0,0 +1,88 @@ +/* + * 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 {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 config = Config.getConfig(); + +const FederationStopMessage: React.FC = ({message, isMessageFocused}) => { + const {timestamp} = useKoSubscribableChildren(message, ['timestamp']); + const {id, domains} = message; + const messageFocusedTabIndex = useMessageFocusedTabIndex(isMessageFocused); + + return ( +
+
+
+ +
+
+
+ + + {t('offlineBackendLearnMore')} + +
+

+ +

+
+ ); +}; + +export {FederationStopMessage}; diff --git a/src/script/components/MessagesList/Message/MessageWrapper.tsx b/src/script/components/MessagesList/Message/MessageWrapper.tsx index e589941d60c..5a9fa835242 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'; @@ -83,8 +84,6 @@ export const MessageWrapper: React.FC { const findMessage = async (conversation: Conversation, messageId: string) => { @@ -225,6 +224,9 @@ export const MessageWrapper: React.FC; } + if (message.isFederationStop()) { + return ; + } if (message.isVerification()) { return ; } 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); diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index b85a401f576..66ff4660fa8 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -39,6 +39,8 @@ import { ConversationRenameEvent, ConversationTypingEvent, CONVERSATION_EVENT, + FederationEvent, + FEDERATION_EVENT, } from '@wireapp/api-client/lib/event'; import {BackendErrorLabel} from '@wireapp/api-client/lib/http/'; import type {BackendError} from '@wireapp/api-client/lib/http/'; @@ -47,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'; @@ -309,6 +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, debounce(this.onFederationEvent, 1000)); this.eventService.addEventUpdatedListener(this.updateLocalMessageEntity); this.eventService.addEventDeletedListener(this.deleteLocalMessageEntity); @@ -316,6 +319,146 @@ export class ConversationRepository { window.addEventListener(WebAppEvents.CONVERSATION.JOIN, this.onConversationJoin); } + private readonly onFederationEvent = async (event: FederationEvent) => { + const {type} = event; + + switch (type) { + case FEDERATION_EVENT.FEDERATION_DELETE: + const {domain: deletedDomain} = event; + await this.onFederationDelete(deletedDomain); + + break; + case FEDERATION_EVENT.FEDERATION_CONNECTION_REMOVED: + const {domains: deletedDomains} = event; + await this.onFederationConnectionRemove(deletedDomains); + + break; + } + }; + + /** + * 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() + .filter(conversation => conversation.domain === deletedDomain); + + conversationsToLeave.forEach(async conversation => { + await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); + if (conversation.is1to1()) { + conversation.status(ConversationStatus.PAST_MEMBER); + return; + } + + await this.leaveConversation(conversation, {clearContent: false, localOnly: true}); + }); + + const conversationsToRemoveTheirDeletedDomainUsers = this.conversationState + .conversations() + .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; + } + await this.insertFederationStopSystemMessage(conversation, [deletedDomain]); + await this.removeDeletedFederationUsers(conversation, usersToRemove); + }); + }; + + /** + * 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; + const allConversations = this.conversationState.conversations(); + + allConversations + .filter(conversation => conversation.domain === domainOne) + .forEach(async conversation => { + const usersToDelete = conversation.allUserEntities().filter(user => user.domain === domainTwo); + if (usersToDelete.length > 0) { + await this.removeDeletedFederationUsers(conversation, usersToDelete); + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + } + }); + + allConversations + .filter(conversation => conversation.domain === domainTwo) + .forEach(async conversation => { + const usersToDelete = conversation.allUserEntities().filter(user => user.domain === domainOne); + if (usersToDelete.length > 0) { + await this.removeDeletedFederationUsers(conversation, usersToDelete); + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + } + }); + + allConversations + .filter(conversation => { + if (conversation.domain !== selfUser.qualifiedId.domain) { + return false; + } + + const userDomains = new Set(conversation.allUserEntities().map(user => user.qualifiedId.domain)); + + return userDomains.has(domainOne) && userDomains.has(domainTwo); + }) + .forEach(async conversation => { + const usersToDelete = conversation + .allUserEntities() + .filter(user => [domainOne, domainTwo].includes(user.domain)); + if (usersToDelete.length > 0) { + await this.removeDeletedFederationUsers(conversation, usersToDelete); + await this.insertFederationStopSystemMessage(conversation, [domainOne, domainTwo]); + } + }); + }; + + 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, {localOnly: true}); + } + } 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 ({ obj: updatedEvent, oldObj: oldEvent, @@ -1590,13 +1733,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)); @@ -1610,18 +1760,22 @@ 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; } /** @@ -1631,12 +1785,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}); } /** @@ -1647,17 +1801,21 @@ 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; if (isUserLeaving) { - return this.leaveConversation(conversationEntity, clearContent); + return this.leaveConversation(conversationEntity, {clearContent}); } return isMLSConversation - ? this.removeMemberFromMLSConversation(conversationEntity, userId) - : this.removeMemberFromConversation(conversationEntity, userId); + ? this.removeMemberFromMLSConversation(conversationEntity, userId, {localOnly}) + : this.removeMemberFromConversation(conversationEntity, userId, {localOnly}); } /** @@ -2013,6 +2171,7 @@ export class ConversationRepository { // Prevent logging typing events return; } + const {time, from, qualified_conversation, type} = event; const extra: Record = {}; extra.messageId = 'id' in event && event.id; @@ -2315,6 +2474,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..e58ba8c98d1 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<{domains: string[]}> & { + 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, + domains: string[], + currentTimestamp: number, + ): FederationStopEvent { + return { + ...buildQualifiedId(conversationEntity), + data: { + domains, + }, + 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..edcfd133edd 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.domains, 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..c9a5bbfca22 --- /dev/null +++ b/src/script/entity/message/FederationStopMessage.ts @@ -0,0 +1,36 @@ +/* + * 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'; + +/** + * Federation stop system message + */ +export class FederationStopMessage extends Message { + constructor( + public readonly domains: string[], + 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..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, 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', 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(); }, diff --git a/yarn.lock b/yarn.lock index b16686b55e1..24234894777 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5188,9 +5188,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.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 @@ -5203,7 +5203,7 @@ __metadata: spark-md5: 3.0.2 tough-cookie: 4.1.3 ws: 8.13.0 - checksum: 29b60507b5f93aae07e89f5c7dc0158b18edb4a9b6699aba99046d68d869659201fd20e72f480823d8f98b554b41eac1ad5feaa5fe2c738a01f123f45173d3cb + checksum: b92f08d0b96b43f2c6d34c79d93fa547fbd0ae1a1962eba06cec037d2af74e92037352322cdc1ead13c94b5548f0960dad402f4431a4876a556429cc0fbc1982 languageName: node linkType: hard @@ -5256,11 +5256,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.2": + version: 40.9.2 + resolution: "@wireapp/core@npm:40.9.2" dependencies: - "@wireapp/api-client": ^24.20.3 + "@wireapp/api-client": ^24.21.1 "@wireapp/commons": ^5.1.0 "@wireapp/core-crypto": 1.0.0-rc.6 "@wireapp/cryptobox": 12.8.0 @@ -5277,7 +5277,7 @@ __metadata: logdown: 3.3.1 long: ^5.2.0 uuidjs: 4.2.13 - checksum: 0a6de1eb4dcfb5b0079a24ca606bc95c0b289bb5c5d413e0189a144160b9f461c670b971ffd0158647043626779286406f134362bd944551ec40c842373b9178 + checksum: 9d5f07e66345e74307424799b5eb60466bc81244f47b74d8e5fa5f3e850ebc5fa95ab963448658d7a04a71b31c284a4d9061e4d0dce111b4258dacb87cbbccf1 languageName: node linkType: hard @@ -5452,10 +5452,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 @@ -18181,7 +18181,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.2 "@wireapp/eslint-config": 3.0.0 "@wireapp/lru-cache": 3.8.1 "@wireapp/prettier-config": 0.6.0 @@ -18189,7 +18189,7 @@ __metadata: "@wireapp/store-engine": ^5.1.1 "@wireapp/store-engine-dexie": 2.1.3 "@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