diff --git a/src/script/E2EIdentity/DelayTimer/delay.ts b/src/script/E2EIdentity/DelayTimer/delay.ts index bb366f37875..951de7add5a 100644 --- a/src/script/E2EIdentity/DelayTimer/delay.ts +++ b/src/script/E2EIdentity/DelayTimer/delay.ts @@ -18,7 +18,7 @@ */ import {EnrollmentConfig} from '../E2EIdentityEnrollment'; -import {MLSStatuses, WireIdentity} from '../E2EIdentityVerification'; +import {MLSStatuses, WireIdentity, isFreshMLSSelfClient} from '../E2EIdentityVerification'; /* eslint-disable no-magic-numbers */ @@ -54,8 +54,14 @@ export function getDelayTime(gracePeriodInMs: number): number { return 0; } -export function shouldEnableSoftLock(enrollmentConfig: EnrollmentConfig, identity?: WireIdentity): boolean { - if (!enrollmentConfig.timer.isSnoozeTimeAvailable() || enrollmentConfig.isFreshMLSSelfClient) { +export async function shouldEnableSoftLock( + enrollmentConfig: EnrollmentConfig, + identity?: WireIdentity, +): Promise { + if (await isFreshMLSSelfClient()) { + return true; + } + if (!enrollmentConfig.timer.isSnoozeTimeAvailable()) { // The user has used up the entire grace period or has a fresh new client, he now needs to enroll return true; } diff --git a/src/script/E2EIdentity/E2EIdentityEnrollment.test.ts b/src/script/E2EIdentity/E2EIdentityEnrollment.test.ts index 853dce2c507..a35a62df59d 100644 --- a/src/script/E2EIdentity/E2EIdentityEnrollment.test.ts +++ b/src/script/E2EIdentity/E2EIdentityEnrollment.test.ts @@ -68,6 +68,7 @@ jest.mock('./E2EIdentityVerification', () => ({ hasActiveCertificate: jest.fn().mockResolvedValue(false), getActiveWireIdentity: jest.fn().mockResolvedValue({certificate: 'certificate data'}), isE2EIEnabled: jest.fn().mockReturnValue(true), + isFreshMLSSelfClient: jest.fn().mockResolvedValue(false), })); // These values should lead to renewalPromptTime being less than the mocked current time @@ -135,8 +136,8 @@ describe('E2EIHandler', () => { jest.spyOn(container.resolve(Core), 'enrollE2EI').mockResolvedValueOnce(true); const instance = await E2EIHandler.getInstance().initialize(params); - await instance['enroll'](); - + void instance['enroll'](); + await wait(1); expect(instance['currentStep']).toBe(E2EIHandlerStep.SUCCESS); }); @@ -146,7 +147,8 @@ describe('E2EIHandler', () => { jest.spyOn(container.resolve(UserState), 'self').mockImplementationOnce(() => user); const instance = await E2EIHandler.getInstance().initialize(params); - await instance['enroll'](); + void instance['enroll'](); + await wait(1); expect(instance['currentStep']).toBe(E2EIHandlerStep.ERROR); }); @@ -169,7 +171,8 @@ describe('E2EIHandler', () => { it('should display loading message when enroled', async () => { const handler = await E2EIHandler.getInstance().initialize(params); - await handler['enroll'](); + void handler['enroll'](); + await wait(1); expect(getModalOptions).toHaveBeenCalledWith( expect.objectContaining({ type: ModalType.LOADING, @@ -182,7 +185,8 @@ describe('E2EIHandler', () => { const handler = await E2EIHandler.getInstance().initialize(params); handler['showLoadingMessage'] = jest.fn(); - await handler['enroll'](); + void handler['enroll'](); + await wait(1); expect(getModalOptions).toHaveBeenCalledWith( expect.objectContaining({ type: ModalType.SUCCESS, @@ -195,7 +199,8 @@ describe('E2EIHandler', () => { const handler = await E2EIHandler.getInstance().initialize(params); handler['showLoadingMessage'] = jest.fn(); - await handler['enroll'](); + void handler['enroll'](); + await wait(1); expect(getModalOptions).toHaveBeenCalledWith( expect.objectContaining({ type: ModalType.ERROR, diff --git a/src/script/E2EIdentity/E2EIdentityEnrollment.ts b/src/script/E2EIdentity/E2EIdentityEnrollment.ts index ad891cad728..28463651d45 100644 --- a/src/script/E2EIdentity/E2EIdentityEnrollment.ts +++ b/src/script/E2EIdentity/E2EIdentityEnrollment.ts @@ -72,7 +72,6 @@ export type EnrollmentConfig = { timer: DelayTimerService; discoveryUrl: string; gracePeriodInMs: number; - isFreshMLSSelfClient?: boolean; }; const historyTimeMS = 28 * TimeInMillis.DAY; //HT @@ -122,7 +121,7 @@ export class E2EIHandler extends TypedEventEmitter { E2EIHandler.instance = null; } - public async initialize({discoveryUrl, gracePeriodInSeconds, isFreshMLSSelfClient = false}: E2EIHandlerParams) { + public async initialize({discoveryUrl, gracePeriodInSeconds}: E2EIHandlerParams) { if (isE2EIEnabled()) { const gracePeriodInMs = gracePeriodInSeconds * TIME_IN_MILLIS.SECOND; this.config = { @@ -133,7 +132,6 @@ export class E2EIHandler extends TypedEventEmitter { gracePeriodExpiredCallback: () => null, delayPeriodExpiredCallback: () => null, }), - isFreshMLSSelfClient, }; } return this; @@ -145,14 +143,7 @@ export class E2EIHandler extends TypedEventEmitter { // If the client already has a certificate, we don't need to start the enrollment return; } - this.showE2EINotificationMessage(); - return new Promise(resolve => { - const handleSuccess = () => { - this.off('identityUpdated', handleSuccess); - resolve(); - }; - this.on('identityUpdated', handleSuccess); - }); + return this.showE2EINotificationMessage(ModalType.ENROLL); } public async attemptRenewal(): Promise { @@ -312,10 +303,11 @@ export class E2EIHandler extends TypedEventEmitter { setTimeout(removeCurrentModal, 0); this.currentStep = E2EIHandlerStep.SUCCESS; - this.showSuccessMessage(isCertificateRenewal); - // clear the oidc service progress/data and successful enrolment await this.cleanUp(false); + + await this.showSuccessMessage(isCertificateRenewal); + this.emit('identityUpdated', {enrollmentConfig: this.config!}); } catch (error) { this.currentStep = E2EIHandlerStep.ERROR; @@ -344,19 +336,21 @@ export class E2EIHandler extends TypedEventEmitter { return; } - const {modalOptions, modalType} = getModalOptions({ - type: ModalType.SUCCESS, - hideSecondary: false, - hideClose: false, - extraParams: { - isRenewal: isCertificateRenewal, - }, - primaryActionFn: () => this.emit('identityUpdated', {enrollmentConfig: this.config!}), - secondaryActionFn: () => { - amplify.publish(WebAppEvents.PREFERENCES.MANAGE_DEVICES); - }, + return new Promise(resolve => { + const {modalOptions, modalType} = getModalOptions({ + type: ModalType.SUCCESS, + hideClose: false, + extraParams: { + isRenewal: isCertificateRenewal, + }, + primaryActionFn: resolve, + secondaryActionFn: () => { + amplify.publish(WebAppEvents.PREFERENCES.MANAGE_DEVICES); + resolve(); + }, + }); + PrimaryModal.show(modalType, modalOptions); }); - PrimaryModal.show(modalType, modalOptions); } private async showErrorMessage(): Promise { @@ -371,22 +365,26 @@ export class E2EIHandler extends TypedEventEmitter { // Clear the e2e identity progress this.coreE2EIService.clearAllProgress(); - const isSoftLockEnabled = shouldEnableSoftLock(this.config!); + const isSoftLockEnabled = await shouldEnableSoftLock(this.config!); - const {modalOptions, modalType} = getModalOptions({ - type: ModalType.ERROR, - hideClose: true, - hideSecondary: isSoftLockEnabled, - primaryActionFn: () => { - this.currentStep = E2EIHandlerStep.INITIALIZED; - void this.enroll(); - }, - secondaryActionFn: () => { - this.showE2EINotificationMessage(); - }, - }); + return new Promise(resolve => { + const {modalOptions, modalType} = getModalOptions({ + type: ModalType.ERROR, + hideClose: true, + hideSecondary: isSoftLockEnabled, + primaryActionFn: async () => { + this.currentStep = E2EIHandlerStep.INITIALIZED; + await this.enroll(); + resolve(); + }, + secondaryActionFn: async () => { + await this.showE2EINotificationMessage(ModalType.ENROLL); + resolve(); + }, + }); - PrimaryModal.show(modalType, modalOptions); + PrimaryModal.show(modalType, modalOptions); + }); } private shouldShowNotification(): boolean { @@ -403,35 +401,43 @@ export class E2EIHandler extends TypedEventEmitter { this.config?.timer.updateParams({ gracePeriodInMS: this.config.gracePeriodInMs, gracePeriodExpiredCallback: () => { - this.showE2EINotificationMessage(); + this.showE2EINotificationMessage(ModalType.ENROLL); }, delayPeriodExpiredCallback: () => { - this.showE2EINotificationMessage(); + this.showE2EINotificationMessage(ModalType.ENROLL); }, }); this.currentStep = E2EIHandlerStep.INITIALIZED; } } - private async showModal(modalType: ModalType = ModalType.ENROLL, hideSecondary = false): Promise { - // Check if config is defined and timer is available - const isSoftLockEnabled = shouldEnableSoftLock(this.config!); + private async showEnrollmentModal(modalType: ModalType.ENROLL | ModalType.CERTIFICATE_RENEWAL): Promise { + // Show the modal with the provided modal type + const disableSnooze = await shouldEnableSoftLock(this.config!); + return new Promise(resolve => { + const {modalOptions, modalType: determinedModalType} = getModalOptions({ + hideSecondary: disableSnooze, + primaryActionFn: async () => { + await this.enroll(); + resolve(); + }, + secondaryActionFn: () => { + this.currentStep = E2EIHandlerStep.SNOOZE; + this.config?.timer.delayPrompt(); + this.showSnoozeModal(); + resolve(); + }, + type: modalType, + hideClose: true, + }); + PrimaryModal.show(determinedModalType, modalOptions); + }); + } + private showSnoozeModal() { // Show the modal with the provided modal type const {modalOptions, modalType: determinedModalType} = getModalOptions({ - hideSecondary: isSoftLockEnabled || hideSecondary, - primaryActionFn: () => { - if (modalType === ModalType.SNOOZE_REMINDER) { - return undefined; - } - return this.enroll(); - }, - secondaryActionFn: () => { - this.currentStep = E2EIHandlerStep.SNOOZE; - this.config?.timer.delayPrompt(); - this.handleE2EIReminderSnooze(); - }, - type: modalType, + type: ModalType.SNOOZE_REMINDER, hideClose: true, extraParams: { delayTime: formatDelayTime(getDelayTime(this.config!.gracePeriodInMs)), @@ -440,16 +446,14 @@ export class E2EIHandler extends TypedEventEmitter { PrimaryModal.show(determinedModalType, modalOptions); } - private handleE2EIReminderSnooze(): void { - void this.showModal(ModalType.SNOOZE_REMINDER, true); - } - - public showE2EINotificationMessage(modalType: ModalType = ModalType.ENROLL): void { + public async showE2EINotificationMessage( + modalType: ModalType.CERTIFICATE_RENEWAL | ModalType.ENROLL, + disableSnooze: boolean = false, + ): Promise { // If the user has already started enrolment, don't show the notification. Instead, show the loading modal // This will occur after the redirect from the oauth provider if (this.coreE2EIService.isEnrollmentInProgress()) { - void this.enroll(); - return; + return this.enroll(); } // Early return if we shouldn't show the notification @@ -461,7 +465,7 @@ export class E2EIHandler extends TypedEventEmitter { // If the timer is not active, show the notification modal if (this.config && !this.config.timer.isDelayTimerActive()) { - void this.showModal(modalType); + return this.showEnrollmentModal(modalType); } } } diff --git a/src/script/hooks/useAppSoftLock.test.ts b/src/script/hooks/useAppSoftLock.test.ts index 67c18e0ad7d..48d34982e9d 100644 --- a/src/script/hooks/useAppSoftLock.test.ts +++ b/src/script/hooks/useAppSoftLock.test.ts @@ -17,17 +17,23 @@ * */ -import {renderHook} from '@testing-library/react'; +import {renderHook, waitFor} from '@testing-library/react'; import {useAppSoftLock} from './useAppSoftLock'; import {CallingRepository} from '../calling/CallingRepository'; -import {isE2EIEnabled, E2EIHandler} from '../E2EIdentity'; +import {E2EIHandler, isE2EIEnabled} from '../E2EIdentity'; +import {isFreshMLSSelfClient} from '../E2EIdentity/E2EIdentityVerification'; import {NotificationRepository} from '../notification/NotificationRepository'; +const isFreshMLSSelfClientMock = isFreshMLSSelfClient as jest.MockedFn; const isE2EIEnabledMock = isE2EIEnabled as jest.MockedFn; const E2EIHandlerMock = E2EIHandler as jest.Mocked; +jest.mock('../E2EIdentity/E2EIdentityVerification', () => ({ + isFreshMLSSelfClient: jest.fn(), +})); + jest.mock('../E2EIdentity', () => ({ isE2EIEnabled: jest.fn(), E2EIHandler: { @@ -59,25 +65,28 @@ describe('useAppSoftLock', () => { const {result} = renderHook(() => useAppSoftLock(callingRepository, notificationRepository)); - expect(result.current.softLockEnabled).toBe(true); - expect(callingRepository.setSoftLock).toHaveBeenCalledWith(true); - expect(notificationRepository.setSoftLock).toHaveBeenCalledWith(true); + await waitFor(() => { + expect(result.current.softLockEnabled).toBe(true); + expect(callingRepository.setSoftLock).toHaveBeenCalledWith(true); + expect(notificationRepository.setSoftLock).toHaveBeenCalledWith(true); + }); }); it('should set softLock if the device is a fresh new device', async () => { isE2EIEnabledMock.mockReturnValue(true); + isFreshMLSSelfClientMock.mockResolvedValue(true); E2EIHandlerMock.getInstance.mockReturnValue({ - on: jest.fn((eventName, callback) => - callback({enrollmentConfig: {timer: {isSnoozeTimeAvailable: () => true}, isFreshMLSSelfClient: true}}), - ), + on: jest.fn((eventName, callback) => callback({enrollmentConfig: {timer: {isSnoozeTimeAvailable: () => true}}})), off: jest.fn(), } as any); const {result} = renderHook(() => useAppSoftLock(callingRepository, notificationRepository)); - expect(result.current.softLockEnabled).toBe(true); - expect(callingRepository.setSoftLock).toHaveBeenCalledWith(true); - expect(notificationRepository.setSoftLock).toHaveBeenCalledWith(true); + await waitFor(() => { + expect(result.current.softLockEnabled).toBe(true); + expect(callingRepository.setSoftLock).toHaveBeenCalledWith(true); + expect(notificationRepository.setSoftLock).toHaveBeenCalledWith(true); + }); }); it('should not set softLock if the device is an old device and the grace period is not expireds', async () => { diff --git a/src/script/hooks/useAppSoftLock.ts b/src/script/hooks/useAppSoftLock.ts index 51ee49e77d7..797f74b8733 100644 --- a/src/script/hooks/useAppSoftLock.ts +++ b/src/script/hooks/useAppSoftLock.ts @@ -30,8 +30,8 @@ export function useAppSoftLock(callingRepository: CallingRepository, notificatio const [softLockEnabled, setSoftLockEnabled] = useState(false); const handleSoftLockActivation = useCallback( - ({enrollmentConfig, identity}: {enrollmentConfig: EnrollmentConfig; identity?: WireIdentity}) => { - const isSoftLockEnabled = shouldEnableSoftLock(enrollmentConfig, identity); + async ({enrollmentConfig, identity}: {enrollmentConfig: EnrollmentConfig; identity?: WireIdentity}) => { + const isSoftLockEnabled = await shouldEnableSoftLock(enrollmentConfig, identity); setSoftLockEnabled(isSoftLockEnabled); callingRepository.setSoftLock(isSoftLockEnabled); @@ -44,10 +44,11 @@ export function useAppSoftLock(callingRepository: CallingRepository, notificatio if (!e2eiEnabled) { return () => {}; } + const e2eiHandler = E2EIHandler.getInstance(); - E2EIHandler.getInstance().on('identityUpdated', handleSoftLockActivation); + e2eiHandler.on('identityUpdated', handleSoftLockActivation); return () => { - E2EIHandler.getInstance().off('identityUpdated', handleSoftLockActivation); + e2eiHandler.off('identityUpdated', handleSoftLockActivation); }; }, [e2eiEnabled, handleSoftLockActivation]);