From 110bdd74787a975d5e436d8f3237caf5f1060b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20G=C3=B3rka?= Date: Wed, 28 Jun 2023 08:50:44 +0200 Subject: [PATCH] feat: update self supported protocols [WPB-2174] (#15399) * feat: periodically check migration config [FS-1893] (#15117) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: start migration of proteus conversations [FS-1888] (#15198) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: initialise migration of proteus conversations * feat: create mls group after switching to mixed and add other clients * runfix: send messages with mls if conversation is actually mls (not if group id exists) * chore: bump core * refactor: don't replace conversation's reference * feat: insert system message after conversation protocol update * refactor: improve reaction to protocol update event * refactor: move protocol update logic to conversation repository * feat: save conversation state to db after updating protocol * feat: update conversation protocol-related fields after protocol was updated * refactor: move adding users of conversation to separate module * refactor: move establishing group for mixed conversation to separate module * chore: update comments * runfix: don't try to to add users to mls group if mixed conv is empty * test: adding all conversation members to mls group * test: try establishing mls group for mixed conversation * chore: remove comment * test: conversation repo updateConversationProtocol * test: initialise migration of proteus conversations * refactor: CR suggestions * feat: automatically join mixed conversations [FS-1897] (#15248) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: initialise migration of proteus conversations * feat: create mls group after switching to mixed and add other clients * feat: periodically check migration config [FS-1893] (#15117) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * runfix: send messages with mls if conversation is actually mls (not if group id exists) * chore: bump core * refactor: don't replace conversation's reference * feat: insert system message after conversation protocol update * refactor: improve reaction to protocol update event * refactor: move protocol update logic to conversation repository * feat: save conversation state to db after updating protocol * feat: update conversation protocol-related fields after protocol was updated * refactor: move adding users of conversation to separate module * refactor: move establishing group for mixed conversation to separate module * chore: update comments * runfix: don't try to to add users to mls group if mixed conv is empty * test: adding all conversation members to mls group * test: try establishing mls group for mixed conversation * chore: remove comment * test: conversation repo updateConversationProtocol * test: initialise migration of proteus conversations * refactor: CR suggestions * feat: join unestablished mixed conversations * test: join unestablished mixed conversations * feat: periodically check migration config [FS-1893] (#15117) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: start migration of proteus conversations [FS-1888] (#15198) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: initialise migration of proteus conversations * feat: create mls group after switching to mixed and add other clients * runfix: send messages with mls if conversation is actually mls (not if group id exists) * chore: bump core * refactor: don't replace conversation's reference * feat: insert system message after conversation protocol update * refactor: improve reaction to protocol update event * refactor: move protocol update logic to conversation repository * feat: save conversation state to db after updating protocol * feat: update conversation protocol-related fields after protocol was updated * refactor: move adding users of conversation to separate module * refactor: move establishing group for mixed conversation to separate module * chore: update comments * runfix: don't try to to add users to mls group if mixed conv is empty * test: adding all conversation members to mls group * test: try establishing mls group for mixed conversation * chore: remove comment * test: conversation repo updateConversationProtocol * test: initialise migration of proteus conversations * refactor: CR suggestions * chore: improve logs in add users method * chore: bump core * runfix: filter duplicated system messages (#15264) * feat: maintain mls group list during migration [WPB-1115] (#15318) * feat: wipe mls group if user is removed / leave mls-capable conversation * feat: add users to mls group when conversation is mixed * feat: restart periodic key material timers on app reload * test: adding users to mls/mixed/proteus group * test: add users to mls group * runfix: joining mls capable conversations * test: remove / leave conversation * runfix: add users to mixed conversation * runfix: show unestablished mixed conversations * refactor: test * refactor: apply cr suggestion * refactor: add MLSCapableConversation type * test: fix test * chore: bump core with draft-20 corecrypto * chore: bump core * feat: debug util to update migration feature team settings config * feat: add qa debug util for displaying epoch info * refactor: reuse existing functionality of joining mls conv with ext commit * runfix: welcome message not being sent in self conversation anymore * feat: parse supported protocols (#15379) * feat: periodically check migration config [FS-1893] (#15117) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: start migration of proteus conversations [FS-1888] (#15198) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: initialise migration of proteus conversations * feat: create mls group after switching to mixed and add other clients * runfix: send messages with mls if conversation is actually mls (not if group id exists) * chore: bump core * refactor: don't replace conversation's reference * feat: insert system message after conversation protocol update * refactor: improve reaction to protocol update event * refactor: move protocol update logic to conversation repository * feat: save conversation state to db after updating protocol * feat: update conversation protocol-related fields after protocol was updated * refactor: move adding users of conversation to separate module * refactor: move establishing group for mixed conversation to separate module * chore: update comments * runfix: don't try to to add users to mls group if mixed conv is empty * test: adding all conversation members to mls group * test: try establishing mls group for mixed conversation * chore: remove comment * test: conversation repo updateConversationProtocol * test: initialise migration of proteus conversations * refactor: CR suggestions * feat: automatically join mixed conversations [FS-1897] (#15248) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: initialise migration of proteus conversations * feat: create mls group after switching to mixed and add other clients * feat: periodically check migration config [FS-1893] (#15117) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * runfix: send messages with mls if conversation is actually mls (not if group id exists) * chore: bump core * refactor: don't replace conversation's reference * feat: insert system message after conversation protocol update * refactor: improve reaction to protocol update event * refactor: move protocol update logic to conversation repository * feat: save conversation state to db after updating protocol * feat: update conversation protocol-related fields after protocol was updated * refactor: move adding users of conversation to separate module * refactor: move establishing group for mixed conversation to separate module * chore: update comments * runfix: don't try to to add users to mls group if mixed conv is empty * test: adding all conversation members to mls group * test: try establishing mls group for mixed conversation * chore: remove comment * test: conversation repo updateConversationProtocol * test: initialise migration of proteus conversations * refactor: CR suggestions * feat: join unestablished mixed conversations * test: join unestablished mixed conversations * feat: periodically check migration config [FS-1893] (#15117) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: start migration of proteus conversations [FS-1888] (#15198) * feat: base for initialisation and finalisation methods * feat: check if migration time has arrived after mls is supported * refactor: improve init migration api * refactor: rename func * refactor: move initialisation and finalisation to separate modules * runfix: start migration flow in background after app was initialised * feat: filter unestablished mls conversations out * feat: filter out only mls conversations that are unestablished * refactor: move mls migration logger to separate file * feat: update conversation locally * chore: remove migration init code (follow-up pr) * refactor: resolve core and api client in module * feat: read feature config from teamstate * chore: update core * runfix: check fresh migration config when timer elapses * chore: update comment * refactor: use reduce for grouping conversations by protocol * refactor: simplify removal key check * feat: update core with new migration config types * refactor: improve types * feat: initialise migration of proteus conversations * feat: create mls group after switching to mixed and add other clients * runfix: send messages with mls if conversation is actually mls (not if group id exists) * chore: bump core * refactor: don't replace conversation's reference * feat: insert system message after conversation protocol update * refactor: improve reaction to protocol update event * refactor: move protocol update logic to conversation repository * feat: save conversation state to db after updating protocol * feat: update conversation protocol-related fields after protocol was updated * refactor: move adding users of conversation to separate module * refactor: move establishing group for mixed conversation to separate module * chore: update comments * runfix: don't try to to add users to mls group if mixed conv is empty * test: adding all conversation members to mls group * test: try establishing mls group for mixed conversation * chore: remove comment * test: conversation repo updateConversationProtocol * test: initialise migration of proteus conversations * refactor: CR suggestions * chore: improve logs in add users method * chore: bump core * runfix: filter duplicated system messages (#15264) * feat: maintain mls group list during migration [WPB-1115] (#15318) * feat: wipe mls group if user is removed / leave mls-capable conversation * feat: add users to mls group when conversation is mixed * feat: restart periodic key material timers on app reload * test: adding users to mls/mixed/proteus group * test: add users to mls group * runfix: joining mls capable conversations * test: remove / leave conversation * runfix: add users to mixed conversation * runfix: show unestablished mixed conversations * refactor: test * refactor: apply cr suggestion * refactor: add MLSCapableConversation type * test: fix test * chore: bump core with draft-20 corecrypto * chore: bump core * feat: debug util to update migration feature team settings config * feat: add qa debug util for displaying epoch info * refactor: reuse existing functionality of joining mls conv with ext commit * runfix: welcome message not being sent in self conversation anymore * feat: add supportedProtocols field on user entity * chore: bump core to beta * feat: evaluate self supported protocols * test: evaluate supported protocols scenarios * feat: wrap updating supported protocols with try catch and add logs * test: supported protocols * chore: bump core * refactor: simplify mls migration feature status check * runfix: return both protocols by default * refactor: address pr comments --- .../conversation/ConversationRepository.ts | 1 - src/script/main/app.ts | 7 + src/script/mls/MLSMigration/MLSMigration.ts | 16 +- .../mls/MLSMigration/migrationStatus.ts | 68 ++++++ .../evaluateSelfSupportedProtocols.test.ts | 212 ++++++++++++++++++ .../evaluateSelfSupportedProtocols.ts | 126 +++++++++++ .../evaluateSelfSupportedProtocols/index.ts | 20 ++ src/script/mls/supportedProtocols/index.ts | 20 ++ .../supportedProtocols.test.ts | 112 +++++++++ .../supportedProtocols/supportedProtocols.ts | 102 +++++++++ src/script/self/SelfService.ts | 5 + src/script/user/UserRepository.ts | 12 + 12 files changed, 691 insertions(+), 10 deletions(-) create mode 100644 src/script/mls/MLSMigration/migrationStatus.ts create mode 100644 src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols.test.ts create mode 100644 src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols.ts create mode 100644 src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/index.ts create mode 100644 src/script/mls/supportedProtocols/index.ts create mode 100644 src/script/mls/supportedProtocols/supportedProtocols.test.ts create mode 100644 src/script/mls/supportedProtocols/supportedProtocols.ts diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 167246ad8c1..496ebcbcf98 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -2981,7 +2981,6 @@ export class ConversationRepository { if (changes) { this.eventService.updateEventSequentially(messageEntity.primary_key, changes); } - return; } catch (error) { const isNotFound = error.type === ConversationError.TYPE.MESSAGE_NOT_FOUND; if (!isNotFound) { diff --git a/src/script/main/app.ts b/src/script/main/app.ts index a7786c2fb43..99b51eac44b 100644 --- a/src/script/main/app.ts +++ b/src/script/main/app.ts @@ -81,6 +81,7 @@ import {IntegrationService} from '../integration/IntegrationService'; import {startNewVersionPolling} from '../lifecycle/newVersionHandler'; import {MediaRepository} from '../media/MediaRepository'; import {initMLSCallbacks, initMLSConversations, registerUninitializedSelfAndTeamConversations} from '../mls'; +import {initialisePeriodicSelfSupportedProtocolsCheck} from '../mls/supportedProtocols'; import {NotificationRepository} from '../notification/NotificationRepository'; import {PreferenceNotificationRepository} from '../notification/PreferenceNotificationRepository'; import {PermissionRepository} from '../permission/PermissionRepository'; @@ -94,6 +95,7 @@ import {Core} from '../service/CoreSingleton'; import {StorageKey, StorageRepository, StorageService} from '../storage'; import {TeamRepository} from '../team/TeamRepository'; import {TeamService} from '../team/TeamService'; +import {TeamState} from '../team/TeamState'; import {AppInitStatisticsValue} from '../telemetry/app_init/AppInitStatisticsValue'; import {AppInitTelemetry} from '../telemetry/app_init/AppInitTelemetry'; import {AppInitTimingsStep} from '../telemetry/app_init/AppInitTimingsStep'; @@ -474,6 +476,11 @@ export class App { callingRepository.setReady(); telemetry.timeStep(AppInitTimingsStep.APP_LOADED); + const teamState = container.resolve(TeamState); + await initialisePeriodicSelfSupportedProtocolsCheck(selfUser, teamState.teamFeatures(), { + userRepository: this.repository.user, + }); + this.logger.info(`App loaded in ${Date.now() - startTime}ms`); return selfUser; diff --git a/src/script/mls/MLSMigration/MLSMigration.ts b/src/script/mls/MLSMigration/MLSMigration.ts index 1f98e5d3440..66704f3d310 100644 --- a/src/script/mls/MLSMigration/MLSMigration.ts +++ b/src/script/mls/MLSMigration/MLSMigration.ts @@ -18,7 +18,6 @@ */ import {CONVERSATION_TYPE} from '@wireapp/api-client/lib/conversation'; -import {FeatureStatus} from '@wireapp/api-client/lib/team'; import {QualifiedId} from '@wireapp/api-client/lib/user'; import {registerRecurringTask} from '@wireapp/core/lib/util/RecurringTaskScheduler'; import {container} from 'tsyringe'; @@ -36,6 +35,7 @@ import {TIME_IN_MILLIS} from 'Util/TimeUtil'; import {initialiseMigrationOfProteusConversations} from './initialiseMigration'; import {joinUnestablishedMixedConversations} from './initialiseMigration/joinUnestablishedMixedConversations'; +import {getMLSMigrationStatus, MLSMigrationStatus} from './migrationStatus'; import {mlsMigrationLogger} from './MLSMigrationLogger'; import {isMLSSupportedByEnvironment} from '../isMLSSupportedByEnvironment'; @@ -110,26 +110,24 @@ const checkMigrationConfig = async ( if (!isMLSSupportedByEnv) { return; } - //at this point we know that MLS is supported by environment, we can check MLS migration config + //at this point we know that MLS is supported by environment, we can check MLS migration status + //fetch current mls migration feature config from memory const mlsMigrationFeature = teamState.teamFeatures().mlsMigration; + const migrationStatus = getMLSMigrationStatus(mlsMigrationFeature); - if (!mlsMigrationFeature || mlsMigrationFeature.status === FeatureStatus.DISABLED) { + if (migrationStatus === MLSMigrationStatus.DISABLED) { mlsMigrationLogger.info('MLS migration feature is disabled, will retry in 24 hours or on next app reload.'); return; } mlsMigrationLogger.info('MLS migration feature enabled, checking the configuration...'); - //if startTime is not defined, we never start the migration, will retry in 24 hours or on next app reload - const startDateISO = mlsMigrationFeature.config.startTime; - const startTime = (startDateISO && Date.parse(startDateISO)) || Infinity; - const hasStartTimeArrived = Date.now() >= startTime; - - if (!hasStartTimeArrived) { + if (migrationStatus === MLSMigrationStatus.NOT_STARTED) { mlsMigrationLogger.info( 'MLS migration start time has not arrived yet, will retry in 24 hours or on next app reload.', ); + return; } mlsMigrationLogger.info( diff --git a/src/script/mls/MLSMigration/migrationStatus.ts b/src/script/mls/MLSMigration/migrationStatus.ts new file mode 100644 index 00000000000..fbd2546bf3a --- /dev/null +++ b/src/script/mls/MLSMigration/migrationStatus.ts @@ -0,0 +1,68 @@ +/* + * 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 {FeatureMLSMigration, FeatureStatus} from '@wireapp/api-client/lib/team'; + +const hasMigrationStartTimeArrived = (mlsMigrationFeature: FeatureMLSMigration): boolean => { + if (mlsMigrationFeature.status === FeatureStatus.DISABLED) { + return false; + } + + const startDateISO = mlsMigrationFeature.config.startTime; + const startTime = startDateISO ? Date.parse(startDateISO) : Infinity; + + return Date.now() >= startTime; +}; + +const hasMigrationFinaliseRegardlessAfterDateArrived = (mlsMigrationFeature: FeatureMLSMigration): boolean => { + if (mlsMigrationFeature.status === FeatureStatus.DISABLED) { + return false; + } + + const finaliseDateISO = mlsMigrationFeature.config.finaliseRegardlessAfter; + const finaliseTime = finaliseDateISO ? Date.parse(finaliseDateISO) : Infinity; + + return Date.now() >= finaliseTime; +}; + +export enum MLSMigrationStatus { + DISABLED = 'DISABLED', //migration feature is disabled + NOT_STARTED = 'NOT_STARTED', //migration feature is enabled but startTime has not arrived + ONGOING = 'ONGOING', //migration feature is enabled and startTime has arrived, but finaliseRegardlessAfter has not arrived + FINALISED = 'FINALISED', //migration feature is enabled and finaliseRegardlessAfter has arrived +} + +export const getMLSMigrationStatus = (mlsMigrationFeature?: FeatureMLSMigration): MLSMigrationStatus => { + if (!mlsMigrationFeature || mlsMigrationFeature.status === FeatureStatus.DISABLED) { + return MLSMigrationStatus.DISABLED; + } + + const hasMigrationStarted = hasMigrationStartTimeArrived(mlsMigrationFeature); + const hasMigrationEnded = hasMigrationFinaliseRegardlessAfterDateArrived(mlsMigrationFeature); + + if (hasMigrationStarted && !hasMigrationEnded) { + return MLSMigrationStatus.ONGOING; + } + + if (hasMigrationEnded) { + return MLSMigrationStatus.FINALISED; + } + + return MLSMigrationStatus.NOT_STARTED; +}; diff --git a/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols.test.ts b/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols.test.ts new file mode 100644 index 00000000000..ff82f600720 --- /dev/null +++ b/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols.test.ts @@ -0,0 +1,212 @@ +/* + * 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 {RegisteredClient} from '@wireapp/api-client/lib/client'; +import {ConversationProtocol} from '@wireapp/api-client/lib/conversation'; +import {FeatureList, FeatureStatus} from '@wireapp/api-client/lib/team'; +import {container} from 'tsyringe'; + +import {APIClient} from '@wireapp/api-client'; + +import {Core} from 'src/script/service/CoreSingleton'; +import {TIME_IN_MILLIS} from 'Util/TimeUtil'; + +import {evaluateSelfSupportedProtocols} from './evaluateSelfSupportedProtocols'; + +import * as mlsSupport from '../../isMLSSupportedByEnvironment'; +import {MLSMigrationStatus} from '../../MLSMigration/migrationStatus'; + +jest.spyOn(mlsSupport, 'isMLSSupportedByEnvironment').mockResolvedValue(true); + +const generateMLSFeaturesConfig = (migrationStatus: MLSMigrationStatus, supportedProtocols: ConversationProtocol[]) => { + const now = Date.now(); + + switch (migrationStatus) { + case MLSMigrationStatus.DISABLED: + return { + mls: { + status: FeatureStatus.ENABLED, + config: {supportedProtocols}, + }, + mlsMigration: {status: FeatureStatus.DISABLED, config: {}}, + }; + case MLSMigrationStatus.NOT_STARTED: + return { + mls: { + status: FeatureStatus.ENABLED, + config: {supportedProtocols}, + }, + mlsMigration: { + status: FeatureStatus.ENABLED, + config: { + startTime: new Date(now + 1 * TIME_IN_MILLIS.DAY).toISOString(), + finaliseRegardlessAfter: new Date(now + 2 * TIME_IN_MILLIS.DAY).toISOString(), + }, + }, + }; + case MLSMigrationStatus.ONGOING: + return { + mls: { + status: FeatureStatus.ENABLED, + config: {supportedProtocols}, + }, + mlsMigration: { + status: FeatureStatus.ENABLED, + config: { + startTime: new Date(now - 1 * TIME_IN_MILLIS.DAY).toISOString(), + finaliseRegardlessAfter: new Date(now + 1 * TIME_IN_MILLIS.DAY).toISOString(), + }, + }, + }; + case MLSMigrationStatus.FINALISED: + return { + mls: { + status: FeatureStatus.ENABLED, + config: {supportedProtocols}, + }, + mlsMigration: { + status: FeatureStatus.ENABLED, + config: { + startTime: new Date(now - 2 * TIME_IN_MILLIS.DAY).toISOString(), + finaliseRegardlessAfter: new Date(now - 1 * TIME_IN_MILLIS.DAY).toISOString(), + }, + }, + }; + } +}; + +const createMockClientResponse = (doesSupportMLS = false, wasActiveWithinLast4Weeks = false) => { + return { + mls_public_keys: doesSupportMLS ? {ed25519: 'key'} : undefined, + last_active: wasActiveWithinLast4Weeks + ? new Date().toISOString() + : new Date(Date.now() - 5 * 7 * 24 * 60 * 60 * 1000).toISOString(), + } as unknown as RegisteredClient; +}; + +const generateListOfSelfClients = ({allActiveClientsMLSCapable}: {allActiveClientsMLSCapable: boolean}) => { + const clients: RegisteredClient[] = []; + + new Array(4).fill(0).forEach(() => clients.push(createMockClientResponse(true, true))); + if (!allActiveClientsMLSCapable) { + new Array(2).fill(0).forEach(() => clients.push(createMockClientResponse(false, true))); + } + + return clients; +}; + +const testScenarios = [ + [ + //with given config + generateMLSFeaturesConfig(MLSMigrationStatus.DISABLED, [ConversationProtocol.PROTEUS]), + + //we expect the following result based on whether all active clients are MLS capable or not + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.PROTEUS]), + someActiveClientsNotMLSCapable: new Set([ConversationProtocol.PROTEUS]), + }, + ], + [ + generateMLSFeaturesConfig(MLSMigrationStatus.DISABLED, [ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + someActiveClientsNotMLSCapable: new Set([ConversationProtocol.PROTEUS]), + }, + ], + [ + generateMLSFeaturesConfig(MLSMigrationStatus.DISABLED, [ConversationProtocol.MLS]), + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.MLS]), + someActiveClientsNotMLSCapable: new Set([]), //FIXME: This may be [ConversationProtocol.PROTEUS] + }, + ], + [ + generateMLSFeaturesConfig(MLSMigrationStatus.NOT_STARTED, [ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + someActiveClientsNotMLSCapable: new Set([ConversationProtocol.PROTEUS]), + }, + ], + [ + generateMLSFeaturesConfig(MLSMigrationStatus.NOT_STARTED, [ConversationProtocol.MLS]), + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + someActiveClientsNotMLSCapable: new Set([ConversationProtocol.PROTEUS]), + }, + ], + [ + generateMLSFeaturesConfig(MLSMigrationStatus.ONGOING, [ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + someActiveClientsNotMLSCapable: new Set([ConversationProtocol.PROTEUS]), + }, + ], + [ + generateMLSFeaturesConfig(MLSMigrationStatus.ONGOING, [ConversationProtocol.MLS]), + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + someActiveClientsNotMLSCapable: new Set([ConversationProtocol.PROTEUS]), + }, + ], + [ + generateMLSFeaturesConfig(MLSMigrationStatus.FINALISED, [ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + someActiveClientsNotMLSCapable: new Set([ConversationProtocol.PROTEUS, ConversationProtocol.MLS]), + }, + ], + [ + generateMLSFeaturesConfig(MLSMigrationStatus.FINALISED, [ConversationProtocol.MLS]), + { + allActiveClientsMLSCapable: new Set([ConversationProtocol.MLS]), + someActiveClientsNotMLSCapable: new Set([ConversationProtocol.MLS]), + }, + ], +] as const; + +describe('evaluateSelfSupportedProtocols', () => { + describe.each([{allActiveClientsMLSCapable: true}, {allActiveClientsMLSCapable: false}])( + '%o', + ({allActiveClientsMLSCapable}) => { + const selfClients = generateListOfSelfClients({allActiveClientsMLSCapable}); + + it.each(testScenarios)('evaluates self supported protocols', async ({mls, mlsMigration}, expected) => { + const mockedApiClient = {api: {client: {getClients: jest.fn()}}} as unknown as APIClient; + const mockCore = container.resolve(Core); + + jest.spyOn(mockedApiClient.api.client, 'getClients').mockResolvedValueOnce(selfClients); + + const teamFeatureList = { + mlsMigration, + mls, + } as unknown as FeatureList; + + const supportedProtocols = await evaluateSelfSupportedProtocols({ + apiClient: mockedApiClient, + core: mockCore, + teamFeatureList, + }); + + expect(supportedProtocols).toEqual( + allActiveClientsMLSCapable ? expected.allActiveClientsMLSCapable : expected.someActiveClientsNotMLSCapable, + ); + }); + }, + ); +}); diff --git a/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols.ts b/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols.ts new file mode 100644 index 00000000000..73893416085 --- /dev/null +++ b/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols.ts @@ -0,0 +1,126 @@ +/* + * 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 {RegisteredClient} from '@wireapp/api-client/lib/client'; +import {ConversationProtocol} from '@wireapp/api-client/lib/conversation'; +import {FeatureList, FeatureMLS, FeatureStatus} from '@wireapp/api-client/lib/team'; + +import {APIClient} from '@wireapp/api-client'; +import {Account} from '@wireapp/core'; + +import {TIME_IN_MILLIS} from 'Util/TimeUtil'; + +import {isMLSSupportedByEnvironment} from '../../isMLSSupportedByEnvironment'; +import {getMLSMigrationStatus, MLSMigrationStatus} from '../../MLSMigration/migrationStatus'; + +export const evaluateSelfSupportedProtocols = async ({ + core, + apiClient, + teamFeatureList, +}: { + core: Account; + apiClient: APIClient; + teamFeatureList: FeatureList; +}): Promise> => { + const supportedProtocols = new Set(); + + const {mlsMigration: mlsMigrationFeature, mls: mlsFeature} = teamFeatureList; + + const teamSupportedProtocols = getSelfTeamSupportedProtocols(mlsFeature); + + const selfClients = await apiClient.api.client.getClients(); + + const mlsMigrationStatus = getMLSMigrationStatus(mlsMigrationFeature); + + if (await isProteusSupported({teamSupportedProtocols, mlsMigrationStatus})) { + supportedProtocols.add(ConversationProtocol.PROTEUS); + } + + if (await isMLSSupported({teamSupportedProtocols, selfClients, mlsMigrationStatus, core, apiClient})) { + supportedProtocols.add(ConversationProtocol.MLS); + } + + return supportedProtocols; +}; + +const isMLSSupported = async ({ + teamSupportedProtocols, + selfClients, + mlsMigrationStatus, + core, + apiClient, +}: { + teamSupportedProtocols: Set; + selfClients: RegisteredClient[]; + mlsMigrationStatus: MLSMigrationStatus; + core: Account; + apiClient: APIClient; +}): Promise => { + const isMLSSupportedByEnv = await isMLSSupportedByEnvironment({core, apiClient}); + + if (!isMLSSupportedByEnv) { + return false; + } + + const isMLSSupportedByTeam = teamSupportedProtocols.has(ConversationProtocol.MLS); + const doActiveClientsSupportMLS = await haveAllActiveClientsRegisteredMLSDevice(selfClients); + return isMLSSupportedByTeam && (doActiveClientsSupportMLS || mlsMigrationStatus === MLSMigrationStatus.FINALISED); +}; + +const isProteusSupported = async ({ + teamSupportedProtocols, + mlsMigrationStatus, +}: { + teamSupportedProtocols: Set; + mlsMigrationStatus: MLSMigrationStatus; +}): Promise => { + const isProteusSupportedByTeam = teamSupportedProtocols.has(ConversationProtocol.PROTEUS); + return ( + isProteusSupportedByTeam || + [MLSMigrationStatus.NOT_STARTED, MLSMigrationStatus.ONGOING].includes(mlsMigrationStatus) + ); +}; + +const wasClientActiveWithinLast4Weeks = (client: RegisteredClient): boolean => { + //FIXME: once last_active field is added to the client entity + const lastActiveISODate = (client as any).last_active as string; + const lastActiveDate = new Date(lastActiveISODate).getTime(); + const fourWeeks = TIME_IN_MILLIS.WEEK * 4; + return Date.now() - lastActiveDate < fourWeeks; +}; + +const haveAllActiveClientsRegisteredMLSDevice = async (selfClients: RegisteredClient[]): Promise => { + //TODO: filter only active clients once last_active field is added to the client entity + const activeClients = selfClients.filter(wasClientActiveWithinLast4Weeks); + return activeClients.every(client => !!client.mls_public_keys); +}; + +const getSelfTeamSupportedProtocols = (mlsFeature?: FeatureMLS): Set => { + if (!mlsFeature || mlsFeature.status === FeatureStatus.DISABLED) { + return new Set([ConversationProtocol.PROTEUS]); + } + + //FIXME: fix type after supportedProtocols is implemented on backend + const teamSupportedProtocols = (mlsFeature.config as any).supportedProtocols || [ + ConversationProtocol.PROTEUS, + ConversationProtocol.MLS, + ]; + + return new Set(teamSupportedProtocols); +}; diff --git a/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/index.ts b/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/index.ts new file mode 100644 index 00000000000..d106ff936e5 --- /dev/null +++ b/src/script/mls/supportedProtocols/evaluateSelfSupportedProtocols/index.ts @@ -0,0 +1,20 @@ +/* + * 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/. + * + */ + +export * from './evaluateSelfSupportedProtocols'; diff --git a/src/script/mls/supportedProtocols/index.ts b/src/script/mls/supportedProtocols/index.ts new file mode 100644 index 00000000000..f6b2b21e58c --- /dev/null +++ b/src/script/mls/supportedProtocols/index.ts @@ -0,0 +1,20 @@ +/* + * 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/. + * + */ + +export * from './supportedProtocols'; diff --git a/src/script/mls/supportedProtocols/supportedProtocols.test.ts b/src/script/mls/supportedProtocols/supportedProtocols.test.ts new file mode 100644 index 00000000000..e7f5fd9bfca --- /dev/null +++ b/src/script/mls/supportedProtocols/supportedProtocols.test.ts @@ -0,0 +1,112 @@ +/* + * 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 {ConversationProtocol} from '@wireapp/api-client/lib/conversation'; +import {FeatureList} from '@wireapp/api-client/lib/team'; +import {act} from 'react-dom/test-utils'; + +import {TestFactory} from 'test/helper/TestFactory'; + +import * as supportedProtocols from './evaluateSelfSupportedProtocols/evaluateSelfSupportedProtocols'; +import {initialisePeriodicSelfSupportedProtocolsCheck} from './supportedProtocols'; + +const testFactory = new TestFactory(); + +describe('supportedProtocols', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('Updates the list of supported protocols', async () => { + const userRepository = await testFactory.exposeUserActors(); + const selfUser = userRepository['userState'].self(); + + const initialProtocols = [ConversationProtocol.PROTEUS]; + selfUser.supportedProtocols(initialProtocols); + + const evaluatedProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS]; + + const mockFeatureList = {} as FeatureList; + + //this funciton is tested standalone in evaluateSelfSupportedProtocols.test.ts + jest.spyOn(supportedProtocols, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(new Set(evaluatedProtocols)); + jest.spyOn(userRepository, 'changeSupportedProtocols'); + + await initialisePeriodicSelfSupportedProtocolsCheck(selfUser, mockFeatureList, {userRepository}); + + expect(userRepository.changeSupportedProtocols).toHaveBeenCalledWith(evaluatedProtocols); + expect(selfUser.supportedProtocols()).toEqual(evaluatedProtocols); + }); + + it("Does not update supported protocols if they didn't change", async () => { + const userRepository = await testFactory.exposeUserActors(); + const selfUser = userRepository['userState'].self(); + + const initialProtocols = [ConversationProtocol.PROTEUS]; + selfUser.supportedProtocols(initialProtocols); + + const evaluatedProtocols = [ConversationProtocol.PROTEUS]; + + const mockFeatureList = {} as FeatureList; + + //this funciton is tested standalone in evaluateSelfSupportedProtocols.test.ts + jest.spyOn(supportedProtocols, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(new Set(evaluatedProtocols)); + jest.spyOn(userRepository, 'changeSupportedProtocols'); + + await initialisePeriodicSelfSupportedProtocolsCheck(selfUser, mockFeatureList, {userRepository}); + expect(selfUser.supportedProtocols()).toEqual(evaluatedProtocols); + expect(userRepository.changeSupportedProtocols).not.toHaveBeenCalled(); + }); + + it('Re-evaluates supported protocols every 24h', async () => { + const userRepository = await testFactory.exposeUserActors(); + const selfUser = userRepository['userState'].self(); + + const initialProtocols = [ConversationProtocol.PROTEUS]; + selfUser.supportedProtocols(initialProtocols); + + const evaluatedProtocols = [ConversationProtocol.PROTEUS]; + + const mockFeatureList = {} as FeatureList; + + //this funciton is tested standalone in evaluateSelfSupportedProtocols.test.ts + jest.spyOn(supportedProtocols, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(new Set(evaluatedProtocols)); + jest.spyOn(userRepository, 'changeSupportedProtocols'); + + await initialisePeriodicSelfSupportedProtocolsCheck(selfUser, mockFeatureList, {userRepository}); + expect(selfUser.supportedProtocols()).toEqual(evaluatedProtocols); + expect(userRepository.changeSupportedProtocols).not.toHaveBeenCalled(); + + const evaluatedProtocols2 = [ConversationProtocol.MLS]; + jest + .spyOn(supportedProtocols, 'evaluateSelfSupportedProtocols') + .mockResolvedValueOnce(new Set(evaluatedProtocols2)); + + await act(async () => { + jest.advanceTimersByTime(24 * 60 * 60 * 1000); + }); + + expect(selfUser.supportedProtocols()).toEqual(evaluatedProtocols2); + expect(userRepository.changeSupportedProtocols).toHaveBeenCalledWith(evaluatedProtocols2); + }); +}); diff --git a/src/script/mls/supportedProtocols/supportedProtocols.ts b/src/script/mls/supportedProtocols/supportedProtocols.ts new file mode 100644 index 00000000000..3e13b92aa87 --- /dev/null +++ b/src/script/mls/supportedProtocols/supportedProtocols.ts @@ -0,0 +1,102 @@ +/* + * 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 {FeatureList} from '@wireapp/api-client/lib/team'; +import {registerRecurringTask} from '@wireapp/core/lib/util/RecurringTaskScheduler'; +import {container} from 'tsyringe'; + +import {APIClient} from '@wireapp/api-client'; +import {Account} from '@wireapp/core'; + +import {User} from 'src/script/entity/User'; +import {APIClient as APIClientSingleton} from 'src/script/service/APIClientSingleton'; +import {Core as CoreSingleton} from 'src/script/service/CoreSingleton'; +import {UserRepository} from 'src/script/user/UserRepository'; +import {getLogger} from 'Util/Logger'; +import {TIME_IN_MILLIS} from 'Util/TimeUtil'; + +import {evaluateSelfSupportedProtocols} from './evaluateSelfSupportedProtocols'; + +const SELF_SUPPORTED_PROTOCOLS_CHECK_KEY = 'self-supported-protocols-check'; + +const logger = getLogger('SupportedProtocols'); + +/** + * Will initialise the intervals for checking (and updating if necessary) self supported protocols. + * Should be called only once on app load. + * + * @param selfUser - self user + * @param teamState - team state + * @param userRepository - user repository + */ +export const initialisePeriodicSelfSupportedProtocolsCheck = async ( + selfUser: User, + teamFeatureList: FeatureList, + {userRepository}: {userRepository: UserRepository}, +) => { + const apiClient = container.resolve(APIClientSingleton); + const core = container.resolve(CoreSingleton); + + const checkSupportedProtocolsTask = () => + updateSelfSupportedProtocols(selfUser, teamFeatureList, {apiClient, core, userRepository}); + + // We update supported protocols of self user on initial app load and then in 24 hours intervals + await checkSupportedProtocolsTask(); + + return registerRecurringTask({ + every: TIME_IN_MILLIS.DAY, + task: checkSupportedProtocolsTask, + key: SELF_SUPPORTED_PROTOCOLS_CHECK_KEY, + }); +}; + +const updateSelfSupportedProtocols = async ( + selfUser: User, + teamFeatureList: FeatureList, + { + core, + apiClient, + userRepository, + }: { + core: Account; + apiClient: APIClient; + userRepository: UserRepository; + }, +) => { + const localSupportedProtocols = new Set(selfUser.supportedProtocols()); + logger.info('Evaluating self supported protocols, currently supported protocols:', localSupportedProtocols); + + try { + const refreshedSupportedProtocols = await evaluateSelfSupportedProtocols({apiClient, core, teamFeatureList}); + + const hasSupportedProtocolsChanged = !( + localSupportedProtocols.size === refreshedSupportedProtocols.size && + [...localSupportedProtocols].every(protocol => refreshedSupportedProtocols.has(protocol)) + ); + + if (!hasSupportedProtocolsChanged) { + return; + } + + logger.info('Supported protocols will get updated to:', refreshedSupportedProtocols); + await userRepository.changeSupportedProtocols(Array.from(refreshedSupportedProtocols)); + } catch (error) { + logger.error('Failed to update self supported protocols, will retry after 24h. Error: ', error); + } +}; diff --git a/src/script/self/SelfService.ts b/src/script/self/SelfService.ts index 4df92940cea..b0a4b4cc107 100644 --- a/src/script/self/SelfService.ts +++ b/src/script/self/SelfService.ts @@ -17,6 +17,7 @@ * */ +import {ConversationProtocol} from '@wireapp/api-client/lib/conversation'; import type {TraceState} from '@wireapp/api-client/lib/http/'; import type {Consent, Self} from '@wireapp/api-client/lib/self/'; import type {UserUpdate} from '@wireapp/api-client/lib/user/'; @@ -67,4 +68,8 @@ export class SelfService { putSelfPhone(phone: string): Promise { return this.apiClient.api.self.putPhone({phone}); } + + putSupportedProtocols(supportedProtocols: ConversationProtocol[]): Promise { + return this.apiClient.api.self.putSupportedProtocols(supportedProtocols); + } } diff --git a/src/script/user/UserRepository.ts b/src/script/user/UserRepository.ts index 20be0fc4ca4..dd09598958b 100644 --- a/src/script/user/UserRepository.ts +++ b/src/script/user/UserRepository.ts @@ -18,6 +18,7 @@ */ import type {AddedClient, PublicClient} from '@wireapp/api-client/lib/client'; +import {ConversationProtocol} from '@wireapp/api-client/lib/conversation'; import { UserEvent, UserLegalHoldDisableEvent, @@ -306,6 +307,7 @@ export class UserRepository { private async updateUser(userId: QualifiedId, user: Partial, isWebSocket = false): Promise { const selfUser = this.userState.self(); const isSelfUser = matchQualifiedIds(userId, selfUser.qualifiedId); + const userEntity = isSelfUser ? selfUser : await this.getUserById(userId); if (isWebSocket && user.name) { @@ -807,6 +809,16 @@ export class UserRepository { return this.updateUser(this.userState.self().qualifiedId, {name}); } + /** + * Change supported protocols. + * It will send a request to the backend to change the supported protocols and then update the user in the local state. + * @param supportedProtocols - an array of new supported protocols + */ + async changeSupportedProtocols(supportedProtocols: ConversationProtocol[]): Promise { + await this.selfService.putSupportedProtocols(supportedProtocols); + return await this.updateUser(this.userState.self().qualifiedId, {supported_protocols: supportedProtocols}); + } + async changeEmail(email: string): Promise { return this.selfService.putSelfEmail(email); }