Skip to content

Commit

Permalink
feat: reevaluate supported protocols on client delete (#15840)
Browse files Browse the repository at this point in the history
* feat: reevaluate self supported protocols on user client delete

* test: deleteSelfUserClient

* test: refreshSelfSupportedProtocols

* refactor: self user getter in self repo
  • Loading branch information
PatrykBuniX authored Sep 18, 2023
1 parent 2979a63 commit 95c49f0
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/__mocks__/@wireapp/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,9 @@ export class Account extends EventEmitter {
setConversationLevelTimer: jest.fn(),
},
},

client: {
deleteClient: jest.fn(),
},
};
}
79 changes: 76 additions & 3 deletions src/script/self/SelfRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ 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 {act} from 'react-dom/test-utils';
import {container} from 'tsyringe';

import {TestFactory} from 'test/helper/TestFactory';
import {TIME_IN_MILLIS} from 'Util/TimeUtil';

import {ClientEntity} from '../client';
import * as mlsSupport from '../mls/isMLSSupportedByEnvironment';
import {MLSMigrationStatus} from '../mls/MLSMigration/migrationStatus';
import {Core} from '../service/CoreSingleton';

const testFactory = new TestFactory();

Expand Down Expand Up @@ -228,7 +231,7 @@ describe('SelfRepository', () => {
])('Updates the list of supported protocols', async (initialProtocols, evaluatedProtocols) => {
const selfRepository = await testFactory.exposeSelfActors();

const selfUser = selfRepository['userState'].self();
const selfUser = selfRepository['userState'].self()!;

selfUser.supportedProtocols(initialProtocols);

Expand All @@ -246,7 +249,7 @@ describe('SelfRepository', () => {
it("Does not update supported protocols if they didn't change", async () => {
const selfRepository = await testFactory.exposeSelfActors();

const selfUser = selfRepository['userState'].self();
const selfUser = selfRepository['userState'].self()!;

const initialProtocols = [ConversationProtocol.PROTEUS];
selfUser.supportedProtocols(initialProtocols);
Expand All @@ -265,7 +268,7 @@ describe('SelfRepository', () => {
it('Re-evaluates supported protocols every 24h', async () => {
const selfRepository = await testFactory.exposeSelfActors();

const selfUser = selfRepository['userState'].self();
const selfUser = selfRepository['userState'].self()!;

const initialProtocols = [ConversationProtocol.PROTEUS];
selfUser.supportedProtocols(initialProtocols);
Expand All @@ -292,4 +295,74 @@ describe('SelfRepository', () => {
expect(selfRepository['selfService'].putSupportedProtocols).toHaveBeenCalledWith(evaluatedProtocols2);
});
});

describe('deleteSelfUserClient', () => {
it('deletes the self user client and refreshes self supported protocols', async () => {
const selfRepository = await testFactory.exposeSelfActors();

const selfUser = selfRepository['userState'].self()!;

selfRepository['clientRepository'].init(selfUser);

const client1 = new ClientEntity(true, null, 'client1');
const client2 = new ClientEntity(true, null, 'client2');

const initialClients = [client1, client2];
selfUser.devices(initialClients);

const clientToDelete = initialClients[0];

jest.spyOn(container.resolve(Core).service?.client!, 'deleteClient');
jest.spyOn(selfRepository, 'refreshSelfSupportedProtocols').mockImplementationOnce(jest.fn());

const expectedClients = [...initialClients].filter(client => client.id !== clientToDelete.id);

await act(async () => {
await selfRepository.deleteSelfUserClient(clientToDelete.id);
});

expect(selfUser.devices()).toEqual(expectedClients);
expect(selfRepository.refreshSelfSupportedProtocols).toHaveBeenCalled();
});
});

describe('refreshSelfSupportedProtocols', () => {
it('refreshes self supported protocols and updates backend with the new list', async () => {
const selfRepository = await testFactory.exposeSelfActors();

const selfUser = selfRepository['userState'].self()!;

const initialProtocols = [ConversationProtocol.PROTEUS];
selfUser.supportedProtocols(initialProtocols);

const evaluatedProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS];

jest.spyOn(selfRepository, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(evaluatedProtocols);
jest.spyOn(selfRepository['selfService'], 'putSupportedProtocols');

await selfRepository.refreshSelfSupportedProtocols();

expect(selfUser.supportedProtocols()).toEqual(evaluatedProtocols);
expect(selfRepository['selfService'].putSupportedProtocols).toHaveBeenCalledWith(evaluatedProtocols);
});

it('does not update backend with supported protocols when not changed', async () => {
const selfRepository = await testFactory.exposeSelfActors();

const selfUser = selfRepository['userState'].self()!;

const initialProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS];
selfUser.supportedProtocols(initialProtocols);

const evaluatedProtocols = [ConversationProtocol.PROTEUS, ConversationProtocol.MLS];

jest.spyOn(selfRepository, 'evaluateSelfSupportedProtocols').mockResolvedValueOnce(evaluatedProtocols);
jest.spyOn(selfRepository['selfService'], 'putSupportedProtocols');

await selfRepository.refreshSelfSupportedProtocols();

expect(selfUser.supportedProtocols()).toEqual(evaluatedProtocols);
expect(selfRepository['selfService'].putSupportedProtocols).not.toHaveBeenCalled();
});
});
});
33 changes: 27 additions & 6 deletions src/script/self/SelfRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@

import {ConversationProtocol} from '@wireapp/api-client/lib/conversation';
import {registerRecurringTask} from '@wireapp/core/lib/util/RecurringTaskScheduler';
import {amplify} from 'amplify';
import {container} from 'tsyringe';

import {WebAppEvents} from '@wireapp/webapp-events';

import {Logger, getLogger} from 'Util/Logger';
import {TIME_IN_MILLIS} from 'Util/TimeUtil';

import {SelfService} from './SelfService';

import {ClientRepository} from '../client';
import {ClientEntity, ClientRepository} from '../client';
import {isMLSSupportedByEnvironment} from '../mls/isMLSSupportedByEnvironment';
import {MLSMigrationStatus} from '../mls/MLSMigration/migrationStatus';
import {TeamRepository} from '../team/TeamRepository';
Expand All @@ -46,6 +49,18 @@ export class SelfRepository {
private readonly userState = container.resolve(UserState),
) {
this.logger = getLogger('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);
}

private get selfUser() {
const selfUser = this.userState.self();
if (!selfUser) {
throw new Error('Self user is not available');
}
return selfUser;
}

/**
Expand Down Expand Up @@ -141,7 +156,7 @@ export class SelfRepository {
): Promise<ConversationProtocol[]> {
this.logger.info('Supported protocols will get updated to:', supportedProtocols);
await this.selfService.putSupportedProtocols(supportedProtocols);
await this.userRepository.updateUserSupportedProtocols(this.userState.self().qualifiedId, supportedProtocols);
await this.userRepository.updateUserSupportedProtocols(this.selfUser.qualifiedId, supportedProtocols);
return supportedProtocols;
}

Expand All @@ -150,9 +165,8 @@ export class SelfRepository {
* 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
*/
public async refreshSelfSupportedProtocols(): Promise<ConversationProtocol[]> {
const selfUser = this.userState.self();
const localSupportedProtocols = selfUser.supportedProtocols();
public readonly refreshSelfSupportedProtocols = async (): Promise<ConversationProtocol[]> => {
const localSupportedProtocols = this.selfUser.supportedProtocols();

this.logger.info('Evaluating self supported protocols, currently supported protocols:', localSupportedProtocols);
const refreshedSupportedProtocols = await this.evaluateSelfSupportedProtocols();
Expand All @@ -171,7 +185,7 @@ export class SelfRepository {
}

return this.updateSelfSupportedProtocols(refreshedSupportedProtocols);
}
};

/**
* Will initialise the intervals for checking (and updating if necessary) self supported protocols.
Expand All @@ -198,4 +212,11 @@ export class SelfRepository {
key: SELF_SUPPORTED_PROTOCOLS_CHECK_KEY,
});
}

public async deleteSelfUserClient(clientId: string, password?: string): Promise<ClientEntity[]> {
const clients = this.clientRepository.deleteClient(clientId, password);

await this.refreshSelfSupportedProtocols();
return clients;
}
}
9 changes: 5 additions & 4 deletions src/script/view_model/ActionsViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {PrimaryModal, removeCurrentModal, usePrimaryModalState} from 'Components
import {t} from 'Util/LocalizerUtil';
import {isBackendError} from 'Util/TypePredicateUtil';

import type {ClientRepository, ClientEntity} from '../client';
import type {ClientEntity} from '../client';
import type {ConnectionRepository} from '../connection/ConnectionRepository';
import type {ConversationRepository} from '../conversation/ConversationRepository';
import type {MessageRepository} from '../conversation/MessageRepository';
Expand All @@ -37,11 +37,12 @@ import type {Message} from '../entity/message/Message';
import type {User} from '../entity/User';
import type {IntegrationRepository} from '../integration/IntegrationRepository';
import type {ServiceEntity} from '../integration/ServiceEntity';
import {SelfRepository} from '../self/SelfRepository';
import {UserState} from '../user/UserState';

export class ActionsViewModel {
constructor(
private readonly clientRepository: ClientRepository,
private readonly selfRepository: SelfRepository,
private readonly connectionRepository: ConnectionRepository,
private readonly conversationRepository: ConversationRepository,
private readonly integrationRepository: IntegrationRepository,
Expand Down Expand Up @@ -152,7 +153,7 @@ export class ActionsViewModel {
const isTemporary = clientEntity.isTemporary();
if (isSSO || isTemporary) {
// Temporary clients and clients of SSO users don't require a password to be removed
return this.clientRepository.deleteClient(clientEntity.id, undefined);
return this.selfRepository.deleteSelfUserClient(clientEntity.id, undefined);
}

return new Promise<void>(resolve => {
Expand All @@ -171,7 +172,7 @@ export class ActionsViewModel {
if (!isSending) {
isSending = true;
try {
await this.clientRepository.deleteClient(clientEntity.id, password);
await this.selfRepository.deleteSelfUserClient(clientEntity.id, password);
removeCurrentModal();
resolve();
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/script/view_model/MainViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class MainViewModel {
};

this.actions = new ActionsViewModel(
repositories.client,
repositories.self,
repositories.connection,
repositories.conversation,
repositories.integration,
Expand Down

0 comments on commit 95c49f0

Please sign in to comment.