From 9b2d334a5a9010596468201dd06fa771805e20a7 Mon Sep 17 00:00:00 2001 From: Przemyslaw Jozwik Date: Tue, 25 Feb 2025 15:08:26 +0100 Subject: [PATCH 1/5] feat(TeamRepository): Prevent removal of MLS from supported protocols if it was previously supported --- src/script/team/TeamRepository.ts | 47 +++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/script/team/TeamRepository.ts b/src/script/team/TeamRepository.ts index 63c8f549c48..07d9142be3d 100644 --- a/src/script/team/TeamRepository.ts +++ b/src/script/team/TeamRepository.ts @@ -17,7 +17,7 @@ * */ -import {ConversationRolesList, ConversationProtocol} from '@wireapp/api-client/lib/conversation'; +import {ConversationProtocol, ConversationRolesList} from '@wireapp/api-client/lib/conversation'; import type { TeamConversationDeleteEvent, TeamDeleteEvent, @@ -26,7 +26,7 @@ import type { TeamMemberLeaveEvent, } from '@wireapp/api-client/lib/event'; import {TEAM_EVENT} from '@wireapp/api-client/lib/event/TeamEvent'; -import {FeatureStatus, FeatureList, FEATURE_KEY} from '@wireapp/api-client/lib/team/feature/'; +import {FEATURE_KEY, FeatureList, FeatureStatus} from '@wireapp/api-client/lib/team/feature/'; import type {PermissionsData} from '@wireapp/api-client/lib/team/member/PermissionsData'; import type {TeamData} from '@wireapp/api-client/lib/team/team/TeamData'; import {QualifiedId} from '@wireapp/api-client/lib/user'; @@ -57,12 +57,14 @@ import {NOTIFICATION_HANDLING_STATE} from '../event/NotificationHandlingState'; import {IntegrationMapper} from '../integration/IntegrationMapper'; import {ServiceEntity} from '../integration/ServiceEntity'; import {scheduleRecurringTask, updateRemoteConfigLogger} from '../lifecycle/updateRemoteConfigs'; -import {MLSMigrationStatus, getMLSMigrationStatus} from '../mls/MLSMigration/migrationStatus'; +import {getMLSMigrationStatus, MLSMigrationStatus} from '../mls/MLSMigration/migrationStatus'; import {APIClient} from '../service/APIClientSingleton'; import {ROLE, ROLE as TEAM_ROLE, roleFromTeamPermissions} from '../user/UserPermission'; import {UserRepository} from '../user/UserRepository'; import {UserState} from '../user/UserState'; +export const HAS_PERSISTED_SUPPORTED_PROTOCOLS = 'HAS_PERSISTED_SUPPORTED_PROTOCOLS'; + export interface AccountInfo { accentID: number; availability?: Availability.Type; @@ -87,6 +89,8 @@ export class TeamRepository extends TypedEventEmitter { private readonly assetRepository: AssetRepository; private backendSupportsMLS: boolean | null = null; + private hasPersistedSupportedProtocols: boolean = localStorage.getItem(HAS_PERSISTED_SUPPORTED_PROTOCOLS) === 'true'; + constructor( userRepository: UserRepository, assetRepository: AssetRepository, @@ -120,6 +124,10 @@ export class TeamRepository extends TypedEventEmitter { ); } + private updatePersistedSupportedProtocols() { + localStorage.setItem(HAS_PERSISTED_SUPPORTED_PROTOCOLS, 'true'); + } + /** * Will init the team configuration and all the team members from the contact list. * @param teamId the Id of the team to init @@ -134,6 +142,17 @@ export class TeamRepository extends TypedEventEmitter { this.teamState.teamFeatures(newFeatureList); + if (newFeatureList[FEATURE_KEY.MLS]?.config?.supportedProtocols?.includes(ConversationProtocol.MLS)) { + this.updatePersistedSupportedProtocols(); + } + + if (this.hasPersistedSupportedProtocols && newFeatureList?.[FEATURE_KEY.MLS]?.config.supportedProtocols) { + newFeatureList[FEATURE_KEY.MLS].config.supportedProtocols = [ + ConversationProtocol.MLS, + ConversationProtocol.PROTEUS, + ]; + } + if (!teamId) { return {team: undefined, features: {}, members: []}; } @@ -157,6 +176,22 @@ export class TeamRepository extends TypedEventEmitter { const prevFeatureList = this.teamState.teamFeatures(); const newFeatureList = await this.teamService.getAllTeamFeatures(); + if ( + this.hasPersistedSupportedProtocols && + prevFeatureList?.[FEATURE_KEY.MLS]?.config.supportedProtocols && + newFeatureList?.[FEATURE_KEY.MLS]?.config.supportedProtocols + ) { + prevFeatureList[FEATURE_KEY.MLS].config.supportedProtocols = [ + ConversationProtocol.MLS, + ConversationProtocol.PROTEUS, + ]; + + newFeatureList[FEATURE_KEY.MLS].config.supportedProtocols = [ + ConversationProtocol.MLS, + ConversationProtocol.PROTEUS, + ]; + } + this.teamState.teamFeatures(newFeatureList); this.emit('featureConfigUpdated', {prevFeatureList, newFeatureList}); @@ -165,6 +200,8 @@ export class TeamRepository extends TypedEventEmitter { !prevFeatureList?.[FEATURE_KEY.MLS]?.config.supportedProtocols.includes(ConversationProtocol.MLS) && newFeatureList?.[FEATURE_KEY.MLS]?.config.supportedProtocols.includes(ConversationProtocol.MLS) ) { + this.updatePersistedSupportedProtocols(); + PrimaryModal.show(PrimaryModal.type.CONFIRM, { primaryAction: { action: () => window.location.reload(), @@ -502,6 +539,10 @@ export class TeamRepository extends TypedEventEmitter { const teamSupportedProtocols = mlsFeature.config.supportedProtocols; + if (this.hasPersistedSupportedProtocols) { + return [...teamSupportedProtocols, ConversationProtocol.MLS]; + } + // For old teams (created on some older backend versions) supportedProtocols field might not exist or be empty, // we fallback to proteus in this case. return teamSupportedProtocols && teamSupportedProtocols.length > 0 From d529993a322d18562c984e931c82f4f18877326f Mon Sep 17 00:00:00 2001 From: Przemyslaw Jozwik Date: Tue, 25 Feb 2025 15:19:31 +0100 Subject: [PATCH 2/5] fix tests --- src/script/team/TeamRepository.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script/team/TeamRepository.ts b/src/script/team/TeamRepository.ts index 07d9142be3d..d9136a32380 100644 --- a/src/script/team/TeamRepository.ts +++ b/src/script/team/TeamRepository.ts @@ -539,8 +539,8 @@ export class TeamRepository extends TypedEventEmitter { const teamSupportedProtocols = mlsFeature.config.supportedProtocols; - if (this.hasPersistedSupportedProtocols) { - return [...teamSupportedProtocols, ConversationProtocol.MLS]; + if (this.hasPersistedSupportedProtocols && teamSupportedProtocols?.length > 0) { + return [...new Set([...teamSupportedProtocols, ConversationProtocol.MLS])]; } // For old teams (created on some older backend versions) supportedProtocols field might not exist or be empty, From c1c6030ba5cff2749011a4e71407c1516d0380b6 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Fri, 28 Feb 2025 13:21:31 +0300 Subject: [PATCH 3/5] always include mls if it is already in supported list --- src/script/self/SelfRepository.ts | 6 ++++++ .../self/SelfSupportedProtocols/SelfSupportedProtocols.ts | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/script/self/SelfRepository.ts b/src/script/self/SelfRepository.ts index 0dedeef9b44..bba5cac02be 100644 --- a/src/script/self/SelfRepository.ts +++ b/src/script/self/SelfRepository.ts @@ -185,9 +185,15 @@ export class SelfRepository extends TypedEventEmitter { const localSupportedProtocols = this.selfUser.supportedProtocols(); this.logger.info('Evaluating self supported protocols, currently supported protocols:', localSupportedProtocols); + + const currentSelf = await this.selfService.getSelf([]); + + const previousSupportedProtocols = currentSelf.supported_protocols; + const refreshedSupportedProtocols = await evaluateSelfSupportedProtocols( this.teamRepository, this.clientRepository, + previousSupportedProtocols, ); if (!localSupportedProtocols) { diff --git a/src/script/self/SelfSupportedProtocols/SelfSupportedProtocols.ts b/src/script/self/SelfSupportedProtocols/SelfSupportedProtocols.ts index 40763b3b62c..cc6daeff976 100644 --- a/src/script/self/SelfSupportedProtocols/SelfSupportedProtocols.ts +++ b/src/script/self/SelfSupportedProtocols/SelfSupportedProtocols.ts @@ -104,6 +104,7 @@ const isMLSSupported = async ( export const evaluateSelfSupportedProtocols = async ( teamHandler: SelfSupportedProtocolsTeamHandler, selfClientsHandler: SelfSupportedProtocolsSelfClientsHandler, + previousSupportedProtocols: ConversationProtocol[] = [], ): Promise => { const supportedProtocols: ConversationProtocol[] = []; @@ -116,7 +117,7 @@ export const evaluateSelfSupportedProtocols = async ( const isMLSForced = await isMLSForcedWithoutMigration(teamHandler, selfClientsHandler); - if (isMLSProtocolSupported || isMLSForced) { + if (isMLSProtocolSupported || isMLSForced || previousSupportedProtocols.includes(ConversationProtocol.MLS)) { supportedProtocols.push(ConversationProtocol.MLS); } From 90520be4f2e4612e9a059b69da0f5afd44352198 Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Fri, 28 Feb 2025 13:58:33 +0300 Subject: [PATCH 4/5] fix tests --- src/script/self/SelfRepository.test.ts | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/script/self/SelfRepository.test.ts b/src/script/self/SelfRepository.test.ts index 88d74b899f3..72c675901e4 100644 --- a/src/script/self/SelfRepository.test.ts +++ b/src/script/self/SelfRepository.test.ts @@ -67,6 +67,12 @@ describe('SelfRepository', () => { jest.spyOn(SelfSupportedProtocols, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(evaluatedProtocols); jest.spyOn(selfRepository['selfService'], 'putSupportedProtocols'); + jest.spyOn(selfRepository['selfService'], 'getSelf').mockResolvedValueOnce({ + locale: 'test', + qualified_id: {domain: 'test-domain', id: 'test-id'}, + id: 'test-id', + name: 'test-name', + }); void selfRepository.initialisePeriodicSelfSupportedProtocolsCheck(); @@ -88,6 +94,12 @@ describe('SelfRepository', () => { jest.spyOn(SelfSupportedProtocols, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(evaluatedProtocols); jest.spyOn(selfRepository['selfService'], 'putSupportedProtocols'); + jest.spyOn(selfRepository['selfService'], 'getSelf').mockResolvedValueOnce({ + locale: 'test', + qualified_id: {domain: 'test-domain', id: 'test-id'}, + id: 'test-id', + name: 'test-name', + }); await selfRepository.initialisePeriodicSelfSupportedProtocolsCheck(); @@ -108,6 +120,12 @@ describe('SelfRepository', () => { jest.spyOn(SelfSupportedProtocols, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(evaluatedProtocols); jest.spyOn(core.recurringTaskScheduler, 'registerTask'); + jest.spyOn(selfRepository['selfService'], 'getSelf').mockResolvedValueOnce({ + locale: 'test', + qualified_id: {domain: 'test-domain', id: 'test-id'}, + id: 'test-id', + name: 'test-name', + }); await selfRepository.initialisePeriodicSelfSupportedProtocolsCheck(); @@ -162,6 +180,12 @@ describe('SelfRepository', () => { jest.spyOn(SelfSupportedProtocols, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(evaluatedProtocols); jest.spyOn(selfRepository['selfService'], 'putSupportedProtocols'); + jest.spyOn(selfRepository['selfService'], 'getSelf').mockResolvedValueOnce({ + locale: 'test', + qualified_id: {domain: 'test-domain', id: 'test-id'}, + id: 'test-id', + name: 'test-name', + }); await selfRepository.refreshSelfSupportedProtocols(); @@ -181,6 +205,12 @@ describe('SelfRepository', () => { jest.spyOn(SelfSupportedProtocols, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(evaluatedProtocols); jest.spyOn(selfRepository['selfService'], 'putSupportedProtocols'); + jest.spyOn(selfRepository['selfService'], 'getSelf').mockResolvedValueOnce({ + locale: 'test', + qualified_id: {domain: 'test-domain', id: 'test-id'}, + id: 'test-id', + name: 'test-name', + }); await selfRepository.refreshSelfSupportedProtocols(); From 6919ea5f0ef8e306bde7893dd22e59245b5638ac Mon Sep 17 00:00:00 2001 From: Amir Ghezelbash Date: Fri, 28 Feb 2025 14:33:42 +0300 Subject: [PATCH 5/5] fix localstorage --- .../ConversationDetails/ConversationDetails.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/page/RightSidebar/ConversationDetails/ConversationDetails.test.tsx b/src/script/page/RightSidebar/ConversationDetails/ConversationDetails.test.tsx index d5c079de537..3163aa930c6 100644 --- a/src/script/page/RightSidebar/ConversationDetails/ConversationDetails.test.tsx +++ b/src/script/page/RightSidebar/ConversationDetails/ConversationDetails.test.tsx @@ -21,6 +21,7 @@ import {act, render} from '@testing-library/react'; import {CONVERSATION_TYPE} from '@wireapp/api-client/lib/conversation'; import {Conversation} from 'src/script/entity/Conversation'; +import 'src/script/util/test/mock/LocalStorageMock'; import {createUuid} from 'Util/uuid'; import {ConversationDetails} from './ConversationDetails';