From 6c26b96cdce0d69a4a459104709880850a401a92 Mon Sep 17 00:00:00 2001 From: Mo Date: Wed, 5 Oct 2022 10:08:54 -0500 Subject: [PATCH] fix(mobile): passcode timing options (#1744) --- packages/mobile/src/Lib/ApplicationState.ts | 10 +- .../src/Screens/Authenticate/Authenticate.tsx | 2 +- .../Settings/Sections/SecuritySection.tsx | 20 ++-- packages/snjs/lib/Application/Application.ts | 52 --------- .../Services/Protection/ClientInterface.ts | 13 ++- .../Services/Protection/MobileUnlockTiming.ts | 6 + .../Services/Protection/ProtectionService.ts | 27 +++-- packages/snjs/mocha/device_auth.test.js | 2 +- packages/snjs/mocha/protection.test.js | 8 +- .../javascripts/Application/Application.ts | 12 +- .../Application/ApplicationGroup.ts | 3 +- .../Panes/Security/BiometricsLock.tsx | 14 ++- .../Panes/Security/MultitaskingPrivacy.tsx | 4 +- .../Panes/Security/PasscodeLock.tsx | 108 +++++++++++++----- 14 files changed, 151 insertions(+), 130 deletions(-) diff --git a/packages/mobile/src/Lib/ApplicationState.ts b/packages/mobile/src/Lib/ApplicationState.ts index ca25eb6fe..f28b14493 100644 --- a/packages/mobile/src/Lib/ApplicationState.ts +++ b/packages/mobile/src/Lib/ApplicationState.ts @@ -483,7 +483,7 @@ export class ApplicationState extends ApplicationService { return } - const hasBiometrics = this.application.hasBiometrics() + const hasBiometrics = this.application.protections.hasBiometricsEnabled() const hasPasscode = this.application.hasPasscode() const passcodeLockImmediately = hasPasscode && this.passcodeTiming === MobileUnlockTiming.Immediately const biometricsLockImmediately = @@ -571,19 +571,19 @@ export class ApplicationState extends ApplicationService { } private async getScreenshotPrivacyEnabled(): Promise { - return this.application.getMobileScreenshotPrivacyEnabled() + return this.application.protections.getMobileScreenshotPrivacyEnabled() } private async getPasscodeTiming(): Promise { - return this.application.getMobilePasscodeTiming() + return this.application.protections.getMobilePasscodeTiming() } private async getBiometricsTiming(): Promise { - return this.application.getMobileBiometricsTiming() + return this.application.protections.getMobileBiometricsTiming() } public async setScreenshotPrivacyEnabled(enabled: boolean) { - await this.application.setMobileScreenshotPrivacyEnabled(enabled) + await this.application.protections.setMobileScreenshotPrivacyEnabled(enabled) this.screenshotPrivacyEnabled = enabled await (this.application.deviceInterface as MobileDevice).setAndroidScreenshotPrivacy(enabled) } diff --git a/packages/mobile/src/Screens/Authenticate/Authenticate.tsx b/packages/mobile/src/Screens/Authenticate/Authenticate.tsx index 8393cf501..03fed2d4b 100644 --- a/packages/mobile/src/Screens/Authenticate/Authenticate.tsx +++ b/packages/mobile/src/Screens/Authenticate/Authenticate.tsx @@ -197,7 +197,7 @@ export const Authenticate = ({ state: AuthenticationValueStateType.Pending, }) - if (await application?.getMobileScreenshotPrivacyEnabled()) { + if (application?.protections.getMobileScreenshotPrivacyEnabled()) { hide() } diff --git a/packages/mobile/src/Screens/Settings/Sections/SecuritySection.tsx b/packages/mobile/src/Screens/Settings/Sections/SecuritySection.tsx index 914c04ca9..8d0435447 100644 --- a/packages/mobile/src/Screens/Settings/Sections/SecuritySection.tsx +++ b/packages/mobile/src/Screens/Settings/Sections/SecuritySection.tsx @@ -31,21 +31,23 @@ export const SecuritySection = (props: Props) => { const [hasBiometrics, setHasBiometrics] = useState(false) const [supportsBiometrics, setSupportsBiometrics] = useState(false) const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() => - application!.getBiometricsTimingOptions(), + application!.protections.getMobileBiometricsTimingOptions(), + ) + const [passcodeTimingOptions, setPasscodeTimingOptions] = useState(() => + application!.protections.getMobilePasscodeTimingOptions(), ) - const [passcodeTimingOptions, setPasscodeTimingOptions] = useState(() => application!.getPasscodeTimingOptions()) useEffect(() => { let mounted = true const getHasScreenshotPrivacy = async () => { - const hasScreenshotPrivacyEnabled = application?.getMobileScreenshotPrivacyEnabled() + const hasScreenshotPrivacyEnabled = application?.protections.getMobileScreenshotPrivacyEnabled() if (mounted) { setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled) } } void getHasScreenshotPrivacy() const getHasBiometrics = async () => { - const appHasBiometrics = application!.hasBiometrics() + const appHasBiometrics = application!.protections.hasBiometricsEnabled() if (mounted) { setHasBiometrics(appHasBiometrics) } @@ -68,7 +70,7 @@ export const SecuritySection = (props: Props) => { useFocusEffect( useCallback(() => { if (props.hasPasscode) { - setPasscodeTimingOptions(() => application!.getPasscodeTimingOptions()) + setPasscodeTimingOptions(() => application!.protections.getMobilePasscodeTimingOptions()) } }, [application, props.hasPasscode]), ) @@ -126,12 +128,12 @@ export const SecuritySection = (props: Props) => { const setBiometricsTiming = async (timing: MobileUnlockTiming) => { await application?.getAppState().setBiometricsTiming(timing) - setBiometricsTimingOptions(() => application!.getBiometricsTimingOptions()) + setBiometricsTimingOptions(() => application!.protections.getMobileBiometricsTimingOptions()) } const setPasscodeTiming = async (timing: MobileUnlockTiming) => { await application?.getAppState().setPasscodeTiming(timing) - setPasscodeTimingOptions(() => application!.getPasscodeTimingOptions()) + setPasscodeTimingOptions(() => application!.protections.getMobilePasscodeTimingOptions()) } const onScreenshotPrivacyPress = async () => { @@ -153,14 +155,14 @@ export const SecuritySection = (props: Props) => { void disableAuthentication('biometrics') } else { setHasBiometrics(true) - await application?.enableBiometrics() + await application?.protections.enableBiometrics() await setBiometricsTiming(MobileUnlockTiming.OnQuit) props.updateProtectionsAvailable() } } const disableBiometrics = useCallback(async () => { - if (await application?.disableBiometrics()) { + if (await application?.protections.disableBiometrics()) { setHasBiometrics(false) props.updateProtectionsAvailable() } diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 36342b081..ce38c1b76 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -45,7 +45,6 @@ import { FileService, SubscriptionClientInterface, SubscriptionManager, - StorageValueModes, } from '@standardnotes/services' import { FilesClientInterface } from '@standardnotes/files' import { ComputePrivateWorkspaceIdentifier } from '@standardnotes/encryption' @@ -65,7 +64,6 @@ import { SNLog } from '../Log' import { Challenge, ChallengeResponse } from '../Services' import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions' import { ApplicationOptionsDefaults } from './Options/Defaults' -import { MobileUnlockTiming } from '@Lib/Services/Protection/MobileUnlockTiming' /** How often to automatically sync, in milliseconds */ const DEFAULT_AUTO_SYNC_INTERVAL = 30_000 @@ -899,24 +897,6 @@ export class SNApplication return this.launched } - public hasBiometrics(): boolean { - return this.protectionService.hasBiometricsEnabled() - } - - /** - * @returns whether the operation was successful or not - */ - public enableBiometrics(): boolean { - return this.protectionService.enableBiometrics() - } - - /** - * @returns whether the operation was successful or not - */ - public disableBiometrics(): Promise { - return this.protectionService.disableBiometrics() - } - public hasPasscode(): boolean { return this.protocolService.hasPasscode() } @@ -936,38 +916,6 @@ export class SNApplication return this.deinit(this.getDeinitMode(), DeinitSource.Lock) } - setBiometricsTiming(timing: MobileUnlockTiming) { - return this.protectionService.setBiometricsTiming(timing) - } - - getMobileScreenshotPrivacyEnabled(): boolean { - return this.protectionService.getMobileScreenshotPrivacyEnabled() - } - - setMobileScreenshotPrivacyEnabled(isEnabled: boolean) { - return this.protectionService.setMobileScreenshotPrivacyEnabled(isEnabled) - } - - getMobilePasscodeTiming(): MobileUnlockTiming | undefined { - return this.getValue(StorageKey.MobilePasscodeTiming, StorageValueModes.Nonwrapped) as - | MobileUnlockTiming - | undefined - } - - getMobileBiometricsTiming(): MobileUnlockTiming | undefined { - return this.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped) as - | MobileUnlockTiming - | undefined - } - - getBiometricsTimingOptions() { - return this.protectionService.getBiometricsTimingOptions() - } - - getPasscodeTimingOptions() { - return this.protectionService.getPasscodeTimingOptions() - } - isNativeMobileWeb() { return this.environment === Environment.NativeMobileWeb } diff --git a/packages/snjs/lib/Services/Protection/ClientInterface.ts b/packages/snjs/lib/Services/Protection/ClientInterface.ts index 4437de254..fc9da0c2a 100644 --- a/packages/snjs/lib/Services/Protection/ClientInterface.ts +++ b/packages/snjs/lib/Services/Protection/ClientInterface.ts @@ -1,8 +1,19 @@ import { ChallengeReason } from '@standardnotes/services' import { DecryptedItem } from '@standardnotes/models' +import { TimingDisplayOption, MobileUnlockTiming } from './MobileUnlockTiming' export interface ProtectionsClientInterface { authorizeProtectedActionForItems(files: T[], challengeReason: ChallengeReason): Promise - authorizeItemAccess(item: DecryptedItem): Promise + getMobileBiometricsTiming(): MobileUnlockTiming | undefined + getMobilePasscodeTiming(): MobileUnlockTiming | undefined + setMobileBiometricsTiming(timing: MobileUnlockTiming): void + setMobilePasscodeTiming(timing: MobileUnlockTiming): void + setMobileScreenshotPrivacyEnabled(isEnabled: boolean): void + getMobileScreenshotPrivacyEnabled(): boolean + getMobilePasscodeTimingOptions(): TimingDisplayOption[] + getMobileBiometricsTimingOptions(): TimingDisplayOption[] + hasBiometricsEnabled(): boolean + enableBiometrics(): boolean + disableBiometrics(): Promise } diff --git a/packages/snjs/lib/Services/Protection/MobileUnlockTiming.ts b/packages/snjs/lib/Services/Protection/MobileUnlockTiming.ts index dfacd6ce6..3939c7f3b 100644 --- a/packages/snjs/lib/Services/Protection/MobileUnlockTiming.ts +++ b/packages/snjs/lib/Services/Protection/MobileUnlockTiming.ts @@ -2,3 +2,9 @@ export enum MobileUnlockTiming { Immediately = 'immediately', OnQuit = 'on-quit', } + +export type TimingDisplayOption = { + title: string + key: MobileUnlockTiming + selected: boolean +} diff --git a/packages/snjs/lib/Services/Protection/ProtectionService.ts b/packages/snjs/lib/Services/Protection/ProtectionService.ts index 52a0c6ff0..666ab7f6d 100644 --- a/packages/snjs/lib/Services/Protection/ProtectionService.ts +++ b/packages/snjs/lib/Services/Protection/ProtectionService.ts @@ -18,7 +18,7 @@ import { } from '@standardnotes/services' import { ProtectionsClientInterface } from './ClientInterface' import { ContentType } from '@standardnotes/common' -import { MobileUnlockTiming } from './MobileUnlockTiming' +import { MobileUnlockTiming, TimingDisplayOption } from './MobileUnlockTiming' export enum ProtectionEvent { UnprotectedSessionBegan = 'UnprotectedSessionBegan', @@ -64,8 +64,8 @@ export const ProtectionSessionDurations = [ */ export class SNProtectionService extends AbstractService implements ProtectionsClientInterface { private sessionExpiryTimeout = -1 - private mobilePasscodeTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.Immediately - private mobileBiometricsTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.Immediately + private mobilePasscodeTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit + private mobileBiometricsTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit constructor( private protocolService: EncryptionService, @@ -87,8 +87,8 @@ export class SNProtectionService extends AbstractService implem override handleApplicationStage(stage: ApplicationStage): Promise { if (stage === ApplicationStage.LoadedDatabase_12) { this.updateSessionExpiryTimer(this.getSessionExpiryDate()) - this.mobilePasscodeTiming = this.getPasscodeTiming() - this.mobileBiometricsTiming = this.getBiometricsTiming() + this.mobilePasscodeTiming = this.getMobilePasscodeTiming() + this.mobileBiometricsTiming = this.getMobileBiometricsTiming() } return Promise.resolve() } @@ -229,7 +229,7 @@ export class SNProtectionService extends AbstractService implem }) } - getPasscodeTimingOptions() { + getMobilePasscodeTimingOptions(): TimingDisplayOption[] { return [ { title: 'Immediately', @@ -244,7 +244,7 @@ export class SNProtectionService extends AbstractService implem ] } - getBiometricsTimingOptions() { + getMobileBiometricsTimingOptions(): TimingDisplayOption[] { return [ { title: 'Immediately', @@ -259,25 +259,32 @@ export class SNProtectionService extends AbstractService implem ] } - private getBiometricsTiming(): MobileUnlockTiming | undefined { + getMobileBiometricsTiming(): MobileUnlockTiming | undefined { return this.storageService.getValue( StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped, + MobileUnlockTiming.OnQuit, ) } - private getPasscodeTiming(): MobileUnlockTiming | undefined { + getMobilePasscodeTiming(): MobileUnlockTiming | undefined { return this.storageService.getValue( StorageKey.MobilePasscodeTiming, StorageValueModes.Nonwrapped, + MobileUnlockTiming.OnQuit, ) } - async setBiometricsTiming(timing: MobileUnlockTiming) { + setMobileBiometricsTiming(timing: MobileUnlockTiming): void { this.storageService.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped) this.mobileBiometricsTiming = timing } + setMobilePasscodeTiming(timing: MobileUnlockTiming): void { + this.storageService.setValue(StorageKey.MobilePasscodeTiming, timing, StorageValueModes.Nonwrapped) + this.mobilePasscodeTiming = timing + } + setMobileScreenshotPrivacyEnabled(isEnabled: boolean) { return this.storageService.setValue(StorageKey.MobileScreenshotPrivacyEnabled, isEnabled, StorageValueModes.Default) } diff --git a/packages/snjs/mocha/device_auth.test.js b/packages/snjs/mocha/device_auth.test.js index a4dcd0fdc..9b8c19d1f 100644 --- a/packages/snjs/mocha/device_auth.test.js +++ b/packages/snjs/mocha/device_auth.test.js @@ -62,7 +62,7 @@ describe('device authentication', function () { const passcode = 'foobar' const wrongPasscode = 'barfoo' await application.addPasscode(passcode) - await application.protectionService.enableBiometrics() + await application.protections.enableBiometrics() expect(await application.hasPasscode()).to.equal(true) expect((await application.protectionService.createLaunchChallenge()).prompts.length).to.equal(2) expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.WrapperOnly) diff --git a/packages/snjs/mocha/protection.test.js b/packages/snjs/mocha/protection.test.js index e0c393a72..afcfbf2f3 100644 --- a/packages/snjs/mocha/protection.test.js +++ b/packages/snjs/mocha/protection.test.js @@ -311,7 +311,7 @@ describe('protections', function () { it('no account, no passcode, biometrics', async function () { application = await Factory.createInitAppWithFakeCrypto() - await application.enableBiometrics() + await application.protections.enableBiometrics() expect(application.hasProtectionSources()).to.be.true }) @@ -324,7 +324,7 @@ describe('protections', function () { it('no account, passcode, biometrics', async function () { application = await Factory.createInitAppWithFakeCrypto() await application.addPasscode('passcode') - await application.enableBiometrics() + await application.protections.enableBiometrics() expect(application.hasProtectionSources()).to.be.true }) @@ -345,7 +345,7 @@ describe('protections', function () { email: UuidGenerator.GenerateUuid(), password: UuidGenerator.GenerateUuid(), }) - await application.enableBiometrics() + await application.protections.enableBiometrics() expect(application.hasProtectionSources()).to.be.true }) @@ -372,7 +372,7 @@ describe('protections', function () { }) Factory.handlePasswordChallenges(application, password) await application.addPasscode('passcode') - await application.enableBiometrics() + await application.protections.enableBiometrics() expect(application.hasProtectionSources()).to.be.true }) }) diff --git a/packages/web/src/javascripts/Application/Application.ts b/packages/web/src/javascripts/Application/Application.ts index 310546234..f3b7cab13 100644 --- a/packages/web/src/javascripts/Application/Application.ts +++ b/packages/web/src/javascripts/Application/Application.ts @@ -32,7 +32,7 @@ import { PrefDefaults } from '@/Constants/PrefDefaults' type WebServices = { viewControllerManager: ViewControllerManager desktopService?: DesktopManager - autolockService: AutolockService + autolockService?: AutolockService archiveService: ArchiveManager themeService: ThemeManager io: IOService @@ -237,7 +237,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter async handleMobileGainingFocusEvent(): Promise {} async handleMobileLosingFocusEvent(): Promise { - if (this.getMobileScreenshotPrivacyEnabled()) { + if (this.protections.getMobileScreenshotPrivacyEnabled()) { this.mobileDevice().stopHidingMobileInterfaceFromScreenshots() } @@ -245,7 +245,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter } async handleMobileResumingFromBackgroundEvent(): Promise { - if (this.getMobileScreenshotPrivacyEnabled()) { + if (this.protections.getMobileScreenshotPrivacyEnabled()) { this.mobileDevice().hideMobileInterfaceFromScreenshots() } } @@ -256,10 +256,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter return } - const hasBiometrics = this.hasBiometrics() + const hasBiometrics = this.protections.hasBiometricsEnabled() const hasPasscode = this.hasPasscode() - const passcodeTiming = await this.getMobilePasscodeTiming() - const biometricsTiming = await this.getMobileBiometricsTiming() + const passcodeTiming = this.protections.getMobilePasscodeTiming() + const biometricsTiming = this.protections.getMobileBiometricsTiming() const passcodeLockImmediately = hasPasscode && passcodeTiming === MobileUnlockTiming.Immediately const biometricsLockImmediately = hasBiometrics && biometricsTiming === MobileUnlockTiming.Immediately diff --git a/packages/web/src/javascripts/Application/ApplicationGroup.ts b/packages/web/src/javascripts/Application/ApplicationGroup.ts index a4d951ebb..f9c292de8 100644 --- a/packages/web/src/javascripts/Application/ApplicationGroup.ts +++ b/packages/web/src/javascripts/Application/ApplicationGroup.ts @@ -34,7 +34,6 @@ const createApplication = ( const archiveService = new ArchiveManager(application) const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop) const internalEventBus = new InternalEventBus() - const autolockService = new AutolockService(application, internalEventBus) const themeService = new ThemeManager(application, internalEventBus) application.setWebServices({ @@ -42,7 +41,7 @@ const createApplication = ( archiveService, desktopService: isDesktopDevice(device) ? new DesktopManager(application, device) : undefined, io, - autolockService, + autolockService: application.isNativeMobileWeb() ? undefined : new AutolockService(application, internalEventBus), themeService, }) diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Security/BiometricsLock.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Security/BiometricsLock.tsx index dec998720..bd551a435 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Security/BiometricsLock.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Security/BiometricsLock.tsx @@ -16,10 +16,12 @@ type Props = { const BiometricsLock = ({ application }: Props) => { const [hasBiometrics, setHasBiometrics] = useState(false) const [supportsBiometrics, setSupportsBiometrics] = useState(false) - const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() => application.getBiometricsTimingOptions()) + const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() => + application.protections.getMobileBiometricsTimingOptions(), + ) useEffect(() => { - const appHasBiometrics = application.hasBiometrics() + const appHasBiometrics = application.protections.hasBiometricsEnabled() setHasBiometrics(appHasBiometrics) const hasBiometricsSupport = async () => { @@ -32,12 +34,12 @@ const BiometricsLock = ({ application }: Props) => { }, [application]) const setBiometricsTimingValue = async (timing: MobileUnlockTiming) => { - await application.setBiometricsTiming(timing) - setBiometricsTimingOptions(() => application.getBiometricsTimingOptions()) + application.protections.setMobileBiometricsTiming(timing) + setBiometricsTimingOptions(() => application.protections.getMobileBiometricsTimingOptions()) } const disableBiometrics = useCallback(async () => { - if (await application.disableBiometrics()) { + if (await application.protections.disableBiometrics()) { setHasBiometrics(false) } }, [application]) @@ -47,7 +49,7 @@ const BiometricsLock = ({ application }: Props) => { await disableBiometrics() } else { setHasBiometrics(true) - application.enableBiometrics() + application.protections.enableBiometrics() await setBiometricsTimingValue(MobileUnlockTiming.OnQuit) } } diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Security/MultitaskingPrivacy.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Security/MultitaskingPrivacy.tsx index 4d4fe87cc..ba6088333 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Security/MultitaskingPrivacy.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Security/MultitaskingPrivacy.tsx @@ -16,7 +16,7 @@ const MultitaskingPrivacy = ({ application }: Props) => { const [hasScreenshotPrivacy, setHasScreenshotPrivacy] = useState(false) useEffect(() => { - const hasScreenshotPrivacyEnabled = application.getMobileScreenshotPrivacyEnabled() + const hasScreenshotPrivacyEnabled = application.protections.getMobileScreenshotPrivacyEnabled() setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled) }, [application]) @@ -24,7 +24,7 @@ const MultitaskingPrivacy = ({ application }: Props) => { const enable = !hasScreenshotPrivacy setHasScreenshotPrivacy(enable) - application.setMobileScreenshotPrivacyEnabled(enable) + application.protections.setMobileScreenshotPrivacyEnabled(enable) ;(application.deviceInterface as MobileDeviceInterface).setAndroidScreenshotPrivacy(enable) } diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Security/PasscodeLock.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Security/PasscodeLock.tsx index daab3703a..f56b6b984 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Security/PasscodeLock.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Security/PasscodeLock.tsx @@ -12,7 +12,7 @@ import { WebApplication } from '@/Application/Application' import { preventRefreshing } from '@/Utils' import { alertDialog } from '@standardnotes/ui-services' import { FormEvent, useCallback, useEffect, useRef, useState } from 'react' -import { ApplicationEvent } from '@standardnotes/snjs' +import { ApplicationEvent, MobileUnlockTiming } from '@standardnotes/snjs' import { observer } from 'mobx-react-lite' import { ViewControllerManager } from '@/Controllers/ViewControllerManager' import { Title, Text } from '@/Components/Preferences/PreferencesComponents/Content' @@ -28,8 +28,8 @@ type Props = { } const PasscodeLock = ({ application, viewControllerManager }: Props) => { + const isNativeMobileWeb = application.isNativeMobileWeb() const keyStorageInfo = StringUtils.keyStorageInfo(application) - const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions() const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } = viewControllerManager.accountMenuController @@ -44,6 +44,10 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => { const [canAddPasscode, setCanAddPasscode] = useState(!application.isEphemeralSession()) const [hasPasscode, setHasPasscode] = useState(application.hasPasscode()) + const [mobilePasscodeTimingOptions, setMobilePasscodeTimingOptions] = useState(() => + application.protections.getMobilePasscodeTimingOptions(), + ) + const handleAddPassCode = () => { setShowPasscodeForm(true) setIsPasscodeFocused(true) @@ -53,8 +57,8 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => { handleAddPassCode() } - const reloadAutoLockInterval = useCallback(async () => { - const interval = await application.getAutolockService().getAutoLockInterval() + const reloadDesktopAutoLockInterval = useCallback(async () => { + const interval = await application.getAutolockService()!.getAutoLockInterval() setSelectedAutoLockInterval(interval) }, [application]) @@ -77,19 +81,27 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => { setIsBackupEncrypted(encryptionEnabled) }, [application, setEncryptionStatusString, setIsBackupEncrypted, setIsEncryptionEnabled]) - const selectAutoLockInterval = async (interval: number) => { + const selectDesktopAutoLockInterval = async (interval: number) => { if (!(await application.authorizeAutolockIntervalChange())) { return } - await application.getAutolockService().setAutoLockInterval(interval) - reloadAutoLockInterval().catch(console.error) + + await application.getAutolockService()!.setAutoLockInterval(interval) + reloadDesktopAutoLockInterval().catch(console.error) + } + + const setMobilePasscodeTiming = (timing: MobileUnlockTiming) => { + application.protections.setMobilePasscodeTiming(timing) + setMobilePasscodeTimingOptions(application.protections.getMobilePasscodeTimingOptions()) } const removePasscodePressed = async () => { await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => { if (await application.removePasscode()) { - await application.getAutolockService().deleteAutolockPreference() - await reloadAutoLockInterval() + if (!isNativeMobileWeb) { + await application.getAutolockService()?.deleteAutolockPreference() + await reloadDesktopAutoLockInterval() + } refreshEncryptionStatus() } }) @@ -141,11 +153,11 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => { refreshEncryptionStatus() }, [refreshEncryptionStatus]) - // `reloadAutoLockInterval` gets interval asynchronously, therefore we call `useEffect` to set initial - // value of `selectedAutoLockInterval` useEffect(() => { - reloadAutoLockInterval().catch(console.error) - }, [reloadAutoLockInterval]) + if (!isNativeMobileWeb) { + reloadDesktopAutoLockInterval().catch(console.error) + } + }, [reloadDesktopAutoLockInterval, isNativeMobileWeb]) useEffect(() => { if (isPasscodeFocused) { @@ -154,7 +166,6 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => { } }, [isPasscodeFocused]) - // Add the required event observers useEffect(() => { const removeKeyStatusChangedObserver = application.addEventObserver(async () => { setCanAddPasscode(!application.isEphemeralSession()) @@ -229,7 +240,7 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => { - {hasPasscode && ( + {hasPasscode && !isNativeMobileWeb && ( <>
@@ -237,22 +248,57 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => { Autolock The autolock timer begins when the window or tab loses focus.
- {passcodeAutoLockOptions.map((option) => { - return ( - selectAutoLockInterval(option.value)} - > - {option.label} - - ) - })} + {application + .getAutolockService()! + .getAutoLockIntervalOptions() + .map((option) => { + return ( + selectDesktopAutoLockInterval(option.value)} + > + {option.label} + + ) + })} +
+ +
+ + )} + + {hasPasscode && isNativeMobileWeb && ( + <> +
+ + + Passcode Autolock +
+
+
Require Passcode
+ {mobilePasscodeTimingOptions.map((option) => { + return ( + { + void setMobilePasscodeTiming(option.key as MobileUnlockTiming) + }} + > + {option.title} + + ) + })} +