Skip to content

Commit

Permalink
feat: evaluate list of self supported protocols on team protocols upd…
Browse files Browse the repository at this point in the history
…ate (#15888)

* feat: evaluate list of self supported protocols on team protocols update

* refactor: use event emitter to inform self repo

* chore: update comment

* refactor: use pick instead of omit

* runfix: refresh self potocols only on team protocols update

* test: refresh supported protocols on team protocols update

* test: refresh self protocols after enabling mls feature

* test: fix type

* refactor: remove async keyword
  • Loading branch information
PatrykBuniX committed Sep 27, 2023
1 parent e3b130e commit e0ffc4e
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 30 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"@datadog/browser-rum": "^4.49.0",
"@emotion/react": "11.11.1",
"@wireapp/avs": "9.3.7",
"@wireapp/core": "42.6.0",
"@wireapp/core": "42.7.0",
"@wireapp/lru-cache": "3.8.1",
"@wireapp/react-ui-kit": "9.9.6",
"@wireapp/store-engine-dexie": "2.1.6",
Expand Down
120 changes: 120 additions & 0 deletions src/script/self/SelfRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

import {RegisteredClient} from '@wireapp/api-client/lib/client';
import {ConversationProtocol} from '@wireapp/api-client/lib/conversation';
import {TeamFeatureConfigurationUpdateEvent, TEAM_EVENT} from '@wireapp/api-client/lib/event';
import {FeatureList, FeatureStatus} from '@wireapp/api-client/lib/team';
import {FEATURE_KEY} from '@wireapp/api-client/lib/team/feature';
import {act} from 'react-dom/test-utils';
import {container} from 'tsyringe';

Expand Down Expand Up @@ -92,6 +94,16 @@ const generateMLSFeaturesConfig = (migrationStatus: MLSMigrationStatus, supporte
}
};

const generateMLSFeatureConfig = (supportedProtocols: ConversationProtocol[]) => {
return {
allowedCipherSuites: [1],
defaultCipherSuite: 1,
defaultProtocol: ConversationProtocol.PROTEUS,
protocolToggleUsers: [] as string[],
supportedProtocols,
};
};

const createMockClientResponse = (doesSupportMLS = false, wasActiveWithinLast4Weeks = false) => {
return {
mls_public_keys: doesSupportMLS ? {ed25519: 'key'} : {},
Expand Down Expand Up @@ -364,5 +376,113 @@ describe('SelfRepository', () => {
expect(selfUser.supportedProtocols()).toEqual(evaluatedProtocols);
expect(selfRepository['selfService'].putSupportedProtocols).not.toHaveBeenCalled();
});

it('refreshes self supported protocols on team supported protocols update', async () => {
const selfRepository = await testFactory.exposeSelfActors();

const currentSupportedProtocols = [ConversationProtocol.PROTEUS];
const newSupportedProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS];

const mockedFeatureList: FeatureList = {
[FEATURE_KEY.MLS]: {
config: generateMLSFeatureConfig(currentSupportedProtocols),
status: FeatureStatus.ENABLED,
},
};

const mockedMLSFeatureUpdateEvent: TeamFeatureConfigurationUpdateEvent = {
name: FEATURE_KEY.MLS,
team: '',
time: '',
data: {
status: FeatureStatus.ENABLED,
config: generateMLSFeatureConfig(newSupportedProtocols),
},

type: TEAM_EVENT.FEATURE_CONFIG_UPDATE,
};

jest.spyOn(selfRepository, 'refreshSelfSupportedProtocols').mockImplementationOnce(jest.fn());

selfRepository['teamRepository'].emit('featureUpdated', {
event: mockedMLSFeatureUpdateEvent,
prevFeatureList: mockedFeatureList,
});

expect(selfRepository.refreshSelfSupportedProtocols).toHaveBeenCalled();
});

it('refreshes self supported protocols after mls feature is enabled', async () => {
const selfRepository = await testFactory.exposeSelfActors();

const currentSupportedProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS];
const currentFeatureStatus = FeatureStatus.DISABLED;

const newSupportedProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS];
const newFeatureStatus = FeatureStatus.ENABLED;

const mockedFeatureList: FeatureList = {
[FEATURE_KEY.MLS]: {
config: generateMLSFeatureConfig(currentSupportedProtocols),
status: currentFeatureStatus,
},
};

const mockedMLSFeatureUpdateEvent: TeamFeatureConfigurationUpdateEvent = {
name: FEATURE_KEY.MLS,
team: '',
time: '',
data: {
status: newFeatureStatus,
config: generateMLSFeatureConfig(newSupportedProtocols),
},

type: TEAM_EVENT.FEATURE_CONFIG_UPDATE,
};

jest.spyOn(selfRepository, 'refreshSelfSupportedProtocols').mockImplementationOnce(jest.fn());

selfRepository['teamRepository'].emit('featureUpdated', {
event: mockedMLSFeatureUpdateEvent,
prevFeatureList: mockedFeatureList,
});

expect(selfRepository.refreshSelfSupportedProtocols).toHaveBeenCalled();
});

it('does not refresh self supported protocols if mls feature is updated without supported protocols change', async () => {
const selfRepository = await testFactory.exposeSelfActors();

const currentSupportedProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS];
const newSupportedProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS];

const mockedFeatureList: FeatureList = {
[FEATURE_KEY.MLS]: {
config: generateMLSFeatureConfig(currentSupportedProtocols),
status: FeatureStatus.ENABLED,
},
};

const mockedMLSFeatureUpdateEvent: TeamFeatureConfigurationUpdateEvent = {
name: FEATURE_KEY.MLS,
team: '',
time: '',
data: {
status: FeatureStatus.ENABLED,
config: generateMLSFeatureConfig(newSupportedProtocols),
},

type: TEAM_EVENT.FEATURE_CONFIG_UPDATE,
};

jest.spyOn(selfRepository, 'refreshSelfSupportedProtocols').mockImplementationOnce(jest.fn());

selfRepository['teamRepository'].emit('featureUpdated', {
event: mockedMLSFeatureUpdateEvent,
prevFeatureList: mockedFeatureList,
});

expect(selfRepository.refreshSelfSupportedProtocols).not.toHaveBeenCalled();
});
});
});
23 changes: 23 additions & 0 deletions src/script/self/SelfRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import {ConversationProtocol} from '@wireapp/api-client/lib/conversation';
import {FEATURE_KEY, FeatureMLS} from '@wireapp/api-client/lib/team/feature/';
import {registerRecurringTask} from '@wireapp/core/lib/util/RecurringTaskScheduler';
import {amplify} from 'amplify';
import {container} from 'tsyringe';
Expand Down Expand Up @@ -53,8 +54,30 @@ export class SelfRepository {
// Every time user's client is deleted, we need to re-evaluate self supported protocols.
// It's possible that they have removed proteus client, and now all their clients are mls-capable.
amplify.subscribe(WebAppEvents.CLIENT.REMOVE, this.refreshSelfSupportedProtocols);

teamRepository.on('featureUpdated', ({event, prevFeatureList}) => {
if (event.name === FEATURE_KEY.MLS) {
void this.handleMLSFeatureUpdate(event.data, prevFeatureList?.[FEATURE_KEY.MLS]);
}
});
}

private handleMLSFeatureUpdate = async (newMLSFeature: FeatureMLS, prevMLSFeature?: FeatureMLS) => {
const prevSupportedProtocols = prevMLSFeature?.config.supportedProtocols ?? [];
const newSupportedProtocols = newMLSFeature.config.supportedProtocols ?? [];

const hasFeatureStatusChanged = prevMLSFeature?.status !== newMLSFeature.status;

const hasTeamSupportedProtocolsChanged = !(
prevSupportedProtocols.length === newSupportedProtocols.length &&
[...prevSupportedProtocols].every(protocol => newSupportedProtocols.includes(protocol))
);

if (hasFeatureStatusChanged || hasTeamSupportedProtocolsChanged) {
await this.refreshSelfSupportedProtocols();
}
};

private get selfUser() {
const selfUser = this.userState.self();
if (!selfUser) {
Expand Down
33 changes: 25 additions & 8 deletions src/script/team/TeamRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,20 @@ import type {
TeamConversationDeleteEvent,
TeamDeleteEvent,
TeamEvent,
TeamFeatureConfigurationUpdateEvent,
TeamMemberJoinEvent,
TeamMemberLeaveEvent,
TeamMemberUpdateEvent,
TeamUpdateEvent,
} from '@wireapp/api-client/lib/event';
import {TEAM_EVENT} from '@wireapp/api-client/lib/event/TeamEvent';
import {FeatureStatus, FEATURE_KEY} from '@wireapp/api-client/lib/team/feature/';
import {FeatureStatus, FeatureList} from '@wireapp/api-client/lib/team/feature/';
import type {TeamData} from '@wireapp/api-client/lib/team/team/TeamData';
import {QualifiedId} from '@wireapp/api-client/lib/user';
import {amplify} from 'amplify';
import {container} from 'tsyringe';

import {Runtime} from '@wireapp/commons';
import {Runtime, TypedEventEmitter} from '@wireapp/commons';
import {Availability} from '@wireapp/protocol-messaging';
import {WebAppEvents} from '@wireapp/webapp-events';

Expand Down Expand Up @@ -72,7 +73,14 @@ export interface AccountInfo {
userID: string;
}

export class TeamRepository {
type Events = {
featureUpdated: {
prevFeatureList?: FeatureList;
event: TeamFeatureConfigurationUpdateEvent;
};
};

export class TeamRepository extends TypedEventEmitter<Events> {
private readonly logger: Logger;
private readonly teamMapper: TeamMapper;
private readonly userRepository: UserRepository;
Expand All @@ -85,6 +93,7 @@ export class TeamRepository {
private readonly userState = container.resolve(UserState),
private readonly teamState = container.resolve(TeamState),
) {
super();
this.logger = getLogger('TeamRepository');

this.teamMapper = new TeamMapper();
Expand Down Expand Up @@ -131,9 +140,15 @@ export class TeamRepository {
return {team, members};
};

private async updateFeatureConfig() {
const features = await this.teamService.getAllTeamFeatures();
this.teamState.teamFeatures(features);
private async updateFeatureConfig(): Promise<{newFeatureList: FeatureList; prevFeatureList?: FeatureList}> {
const prevFeatureList = this.teamState.teamFeatures();
const newFeatureList = await this.teamService.getAllTeamFeatures();
this.teamState.teamFeatures(newFeatureList);

return {
newFeatureList,
prevFeatureList,
};
}

private readonly scheduleTeamRefresh = (): void => {
Expand Down Expand Up @@ -399,15 +414,17 @@ export class TeamRepository {
};

private readonly onFeatureConfigUpdate = async (
_event: TeamEvent & {name: FEATURE_KEY},
event: TeamFeatureConfigurationUpdateEvent,
source: EventSource,
): Promise<void> => {
if (source !== EventSource.WEBSOCKET) {
// Ignore notification stream events
return;
}

// When we receive a `feature-config.update` event, we will refetch the entire feature config
await this.updateFeatureConfig();
const {prevFeatureList} = await this.updateFeatureConfig();
this.emit('featureUpdated', {event, prevFeatureList});
};

private onMemberLeave(eventJson: TeamMemberLeaveEvent): void {
Expand Down
42 changes: 21 additions & 21 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5373,11 +5373,11 @@ __metadata:
languageName: node
linkType: hard

"@wireapp/api-client@npm:^26.2.0":
version: 26.2.0
resolution: "@wireapp/api-client@npm:26.2.0"
"@wireapp/api-client@npm:^26.2.2":
version: 26.2.2
resolution: "@wireapp/api-client@npm:26.2.2"
dependencies:
"@wireapp/commons": ^5.1.3
"@wireapp/commons": ^5.2.0
"@wireapp/priority-queue": ^2.1.4
"@wireapp/protocol-messaging": 1.44.0
axios: 1.5.0
Expand All @@ -5389,7 +5389,7 @@ __metadata:
spark-md5: 3.0.2
tough-cookie: 4.1.3
ws: 8.14.2
checksum: c78a59f64164eb1f718e7a3390e6d25d8f097904b2db35958ec7cb3f1e2954a0f8670ea23c2d999c8f160b822a009e1bfc44fc1df1ddb19fcd1858d2fb9dfe34
checksum: bcf939e366a3cf2a4f8fd1184606d6d2c78d8304d0c1d40c2d2964dde1db5dcec80c91859f819da4e73b520dbfb5f0b5416a15c31ecadacdf428adb5a3411287
languageName: node
linkType: hard

Expand All @@ -5407,15 +5407,15 @@ __metadata:
languageName: node
linkType: hard

"@wireapp/commons@npm:^5.1.3":
version: 5.1.3
resolution: "@wireapp/commons@npm:5.1.3"
"@wireapp/commons@npm:^5.2.0":
version: 5.2.0
resolution: "@wireapp/commons@npm:5.2.0"
dependencies:
ansi-regex: 5.0.1
fs-extra: 11.1.0
logdown: 3.3.1
platform: 1.3.6
checksum: 3722abf157efbd01629e9f0ce4fd96ae53deffb08060530b948296176ba7f72201b0b2d227c87f6058bff9cc5a5f1a11c6b097f16a03da91a025de960b35ff98
checksum: 7537084a5c06dee8d8793e98841098ea5b2b507fd9efd1f0f6d4d7413b148f767690def018a15f4b77d34701415f20fce5755cd9755f6a96c71ff1197682b0b8
languageName: node
linkType: hard

Expand All @@ -5442,15 +5442,15 @@ __metadata:
languageName: node
linkType: hard

"@wireapp/core@npm:42.6.0":
version: 42.6.0
resolution: "@wireapp/core@npm:42.6.0"
"@wireapp/core@npm:42.7.0":
version: 42.7.0
resolution: "@wireapp/core@npm:42.7.0"
dependencies:
"@wireapp/api-client": ^26.2.0
"@wireapp/commons": ^5.1.3
"@wireapp/api-client": ^26.2.2
"@wireapp/commons": ^5.2.0
"@wireapp/core-crypto": 1.0.0-rc.12
"@wireapp/cryptobox": 12.8.0
"@wireapp/promise-queue": ^2.2.4
"@wireapp/promise-queue": ^2.2.5
"@wireapp/protocol-messaging": 1.44.0
"@wireapp/store-engine": 5.1.4
"@wireapp/store-engine-dexie": ^2.1.6
Expand All @@ -5463,7 +5463,7 @@ __metadata:
logdown: 3.3.1
long: ^5.2.0
uuidjs: 4.2.13
checksum: 66c57a07d0e03d45896730107a8a33ccdfc38c246325efc83684734bb54989b830117b897f703282074264ceb979b415b4073c3c2d941aa0e7fa8cbe55a71d7e
checksum: 15f2dbe460decdf9f9387271f17b2898c93d18b896c149ad11556c0be43fd3bdf999ff9bc33a11ecc8844160f31b794889be8025e61b6a219c4e3a6faecd8c6b
languageName: node
linkType: hard

Expand Down Expand Up @@ -5546,10 +5546,10 @@ __metadata:
languageName: node
linkType: hard

"@wireapp/promise-queue@npm:^2.2.4":
version: 2.2.4
resolution: "@wireapp/promise-queue@npm:2.2.4"
checksum: 4983c4f046a5533afd66a02e695d01acee2ffceef0277044933a74730906376eb66f4fc15db1b9b15c5df47e970fbe929523e3c178d1600d4b70e965267f6a5b
"@wireapp/promise-queue@npm:^2.2.5":
version: 2.2.5
resolution: "@wireapp/promise-queue@npm:2.2.5"
checksum: 0cb1423f8b5963ae133c4bd31c56b1bd43dd8fac3fbd20e7045d95025fc2409db748f1245d29ffd97eb801d9c01b1b7da1b2e8da646d58710878b12c00d96403
languageName: node
linkType: hard

Expand Down Expand Up @@ -18599,7 +18599,7 @@ __metadata:
"@types/webpack-env": 1.18.1
"@wireapp/avs": 9.3.7
"@wireapp/copy-config": 2.1.8
"@wireapp/core": 42.6.0
"@wireapp/core": 42.7.0
"@wireapp/eslint-config": 3.0.4
"@wireapp/lru-cache": 3.8.1
"@wireapp/prettier-config": 0.6.3
Expand Down

0 comments on commit e0ffc4e

Please sign in to comment.