diff --git a/app/__tests__/helpers/Utils.test.ts b/app/__tests__/helpers/Utils.test.ts new file mode 100644 index 000000000..b808ff063 --- /dev/null +++ b/app/__tests__/helpers/Utils.test.ts @@ -0,0 +1,31 @@ +import { expirationOverrideInMinutes } from '../../src/helpers/utils' + +import MockDate from 'mockdate' + +jest.useFakeTimers({ legacyFakeTimers: true }) + +describe('Helpers', () => { + beforeAll(() => { + // Set the date to a fixed point in time + MockDate.set('2024-07-09T21:56:44.200Z') + }) + + afterAll(() => { + // Reset the date to the current date after tests + MockDate.reset() + }) + + test('computes expiration override correctly', () => { + const dateInThePast = new Date('2024-07-09T21:23:44.200Z') + const value = expirationOverrideInMinutes(dateInThePast, 60) + + expect(value).toBe(27) + }) + + test('ignore distant expirations ', () => { + const dateInThePast = new Date('2024-07-09T19:23:44.200Z') + const value = expirationOverrideInMinutes(dateInThePast, 60) + + expect(value).toBe(0) + }) +}) diff --git a/app/container-imp.ts b/app/container-imp.ts index 36b029517..f9e3352cd 100644 --- a/app/container-imp.ts +++ b/app/container-imp.ts @@ -30,10 +30,18 @@ import { import { DependencyContainer } from 'tsyringe' import { autoDisableRemoteLoggingIntervalInMinutes } from './src/constants' +import { expirationOverrideInMinutes } from './src/helpers/utils' import Developer from './src/screens/Developer' import Preface from './src/screens/Preface' import Terms, { TermsVersion } from './src/screens/Terms' -import { BCLocalStorageKeys, BCState, DismissPersonCredentialOffer, IASEnvironment, initialState } from './src/store' +import { + BCLocalStorageKeys, + BCState, + DismissPersonCredentialOffer, + IASEnvironment, + RemoteDebuggingState, + initialState, +} from './src/store' export class AppContainer implements Container { private _container: DependencyContainer @@ -51,6 +59,20 @@ export class AppContainer implements Container { public init(): Container { this.log?.info(`Initializing BC Wallet App container`) + const logOptions: RemoteLoggerOptions = { + lokiUrl: Config.REMOTE_LOGGING_URL, + lokiLabels: { + application: getApplicationName().toLowerCase(), + job: 'react-native-logs', + version: `${getVersion()}-${getBuildNumber()}`, + system: `${getSystemName()} v${getSystemVersion()}`, + }, + autoDisableRemoteLoggingIntervalInMinutes, + } + + const logger = new RemoteLogger(logOptions) + logger.startEventListeners() + // Here you can register any component to override components in core package // Example: Replacing button in core with custom button this._container.registerInstance(TOKENS.SCREEN_PREFACE, Preface) @@ -151,7 +173,7 @@ export class AppContainer implements Container { let tours = initialState.tours let onboarding = initialState.onboarding let personCredOfferDissmissed = initialState.dismissPersonCredentialOffer - let environment = initialState.developer.environment + let { environment, remoteDebugging } = initialState.developer await Promise.all([ loadLoginAttempt().then((data) => { @@ -168,6 +190,7 @@ export class AppContainer implements Container { (val) => (personCredOfferDissmissed = val) ), loadState(BCLocalStorageKeys.Environment, (val) => (environment = val)), + loadState(BCLocalStorageKeys.RemoteDebugging, (val) => (remoteDebugging = val)), ]) const state: BCState = { ...initialState, @@ -180,23 +203,31 @@ export class AppContainer implements Container { developer: { ...initialState.developer, environment, + remoteDebugging: { + enabledAt: remoteDebugging.enabledAt ? new Date(remoteDebugging.enabledAt) : undefined, + sessionId: remoteDebugging.sessionId, + }, }, } + + const { enabledAt, sessionId } = state.developer.remoteDebugging + if (enabledAt) { + const override = expirationOverrideInMinutes(enabledAt, autoDisableRemoteLoggingIntervalInMinutes) + + if (override > 0) { + logger.remoteLoggingEnabled = true + logger.sessionId = sessionId + logger.overrideCurrentAutoDisableExpiration(override) + + logger.info( + `Remote logging enabled, last enabled at ${enabledAt}, session id: ${logger.sessionId}. Expiration override is ${override} minutes` + ) + } + } + dispatch({ type: DispatchAction.STATE_DISPATCH, payload: [state] }) }) - const logOptions: RemoteLoggerOptions = { - lokiUrl: Config.REMOTE_LOGGING_URL, - lokiLabels: { - application: getApplicationName().toLowerCase(), - job: 'react-native-logs', - version: `${getVersion()}-${getBuildNumber()}`, - system: `${getSystemName()} v${getSystemVersion()}`, - }, - autoDisableRemoteLoggingIntervalInMinutes, - } - const logger = new RemoteLogger(logOptions) - logger.startEventListeners() this._container.registerInstance(TOKENS.UTIL_LOGGER, logger) return this diff --git a/app/jest.config.js b/app/jest.config.js index 1f290d7a5..b6334e9ee 100644 --- a/app/jest.config.js +++ b/app/jest.config.js @@ -23,12 +23,6 @@ module.exports = { 'node_modules\\/(?!(.*react-native.*)|(uuid)|(@aries-framework\\/core)|(@aries-framework\\/anoncreds)|(@hyperledger\\/aries-bifold-core))', ], testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$', - testPathIgnorePatterns: [ - '\\.snap$', - '/node_modules/', - '/lib', - '/__tests__/contexts/', - '/__tests__/helpers/', - ], + testPathIgnorePatterns: ['\\.snap$', '/node_modules/', '/lib', '/__tests__/contexts/'], cacheDirectory: '.jest/cache', } diff --git a/app/src/helpers/utils.ts b/app/src/helpers/utils.ts index ec156d1c0..12d3afeef 100644 --- a/app/src/helpers/utils.ts +++ b/app/src/helpers/utils.ts @@ -9,3 +9,19 @@ export const openLink = async (url: string) => { await Linking.openURL(url) } } + +export const expirationOverrideInMinutes = ( + enabledAt: Date, + autoDisableRemoteLoggingIntervalInMinutes: number +): number => { + const now = Date.now() + const enabledAtTime = enabledAt.getTime() + const autoDisableIntervalInMilliseconds = autoDisableRemoteLoggingIntervalInMinutes * 60000 + + if (enabledAtTime < now - autoDisableIntervalInMilliseconds) { + return 0 + } + + const diffInMinutes = Math.floor((now - enabledAtTime) / 60000) + return autoDisableRemoteLoggingIntervalInMinutes - diffInMinutes +} diff --git a/app/src/screens/Developer.tsx b/app/src/screens/Developer.tsx index 10395f851..38c72f466 100644 --- a/app/src/screens/Developer.tsx +++ b/app/src/screens/Developer.tsx @@ -8,7 +8,7 @@ import { DeviceEventEmitter, Modal, StyleSheet, Switch, Text, Pressable, View, S import { SafeAreaView } from 'react-native-safe-area-context' import Icon from 'react-native-vector-icons/MaterialIcons' -import { BCState } from '../store' +import { BCState, BCDispatchAction } from '../store' import IASEnvironment from './IASEnvironment' import RemoteLogWarning from './RemoteLogWarning' @@ -199,10 +199,18 @@ const Settings: React.FC = () => { setEnableWalletNaming((previousState) => !previousState) } - const toggleRemoteLoggingWarningSwitch = () => { + const toggleRemoteLoggingSwitch = () => { if (remoteLoggingEnabled) { - DeviceEventEmitter.emit(RemoteLoggerEventTypes.ENABLE_REMOTE_LOGGING, false) - setRemoteLoggingEnabled(false) + const remoteLoggingEnabled = false + + DeviceEventEmitter.emit(RemoteLoggerEventTypes.ENABLE_REMOTE_LOGGING, remoteLoggingEnabled) + setRemoteLoggingEnabled(remoteLoggingEnabled) + + dispatch({ + type: BCDispatchAction.REMOTE_DEBUGGING_STATUS_UPDATE, + payload: [{ enabled: remoteLoggingEnabled, expireAt: undefined }], + }) + return } @@ -210,7 +218,13 @@ const Settings: React.FC = () => { } const onEnableRemoteLoggingPressed = () => { - DeviceEventEmitter.emit(RemoteLoggerEventTypes.ENABLE_REMOTE_LOGGING, true) + const remoteLoggingEnabled = true + DeviceEventEmitter.emit(RemoteLoggerEventTypes.ENABLE_REMOTE_LOGGING, remoteLoggingEnabled) + dispatch({ + type: BCDispatchAction.REMOTE_DEBUGGING_STATUS_UPDATE, + payload: [{ enabledAt: new Date(), sessionId: logger.sessionId }], + }) + setRemoteLoggingEnabled(remoteLoggingEnabled) setRemoteLoggingWarningModalVisible(false) navigation.navigate(Screens.Home as never) @@ -393,7 +407,7 @@ const Settings: React.FC = () => { trackColor={{ false: ColorPallet.grayscale.lightGrey, true: ColorPallet.brand.primaryDisabled }} thumbColor={remoteLoggingEnabled ? ColorPallet.brand.primary : ColorPallet.grayscale.mediumGrey} ios_backgroundColor={ColorPallet.grayscale.lightGrey} - onValueChange={toggleRemoteLoggingWarningSwitch} + onValueChange={toggleRemoteLoggingSwitch} value={remoteLoggingEnabled} disabled={!store.authentication.didAuthenticate} /> diff --git a/app/src/screens/Splash.tsx b/app/src/screens/Splash.tsx index 15103e9b8..533925320 100644 --- a/app/src/screens/Splash.tsx +++ b/app/src/screens/Splash.tsx @@ -369,11 +369,11 @@ const Splash = () => { // If we haven't migrated to Aries Askar yet, we need to do this before we initialize the agent. if (!didMigrateToAskar(store.migration)) { - newAgent.config.logger.debug('Agent not updated to Aries Askar, updating...') + logger.debug('Agent not updated to Aries Askar, updating...') await migrateToAskar(credentials.id, credentials.key, newAgent) - newAgent.config.logger.debug('Successfully finished updating agent to Aries Askar') + logger.debug('Successfully finished updating agent to Aries Askar') // Store that we migrated to askar. dispatch({ type: DispatchAction.DID_MIGRATE_TO_ASKAR, diff --git a/app/src/store.tsx b/app/src/store.tsx index ebdde08ea..8bb021024 100644 --- a/app/src/store.tsx +++ b/app/src/store.tsx @@ -13,9 +13,14 @@ export interface IASEnvironment { iasPortalUrl: string attestationInviteUrl: string } + +export type RemoteDebuggingState = { + enabledAt?: Date + sessionId?: number +} export interface Developer { environment: IASEnvironment - remoteLoggingEnabled: boolean + remoteDebugging: RemoteDebuggingState } export interface DismissPersonCredentialOffer { @@ -35,11 +40,19 @@ enum DismissPersonCredentialOfferDispatchAction { PERSON_CREDENTIAL_OFFER_DISMISSED = 'dismissPersonCredentialOffer/personCredentialOfferDismissed', } -export type BCDispatchAction = DeveloperDispatchAction | DismissPersonCredentialOfferDispatchAction +enum RemoteDebuggingDispatchAction { + REMOTE_DEBUGGING_STATUS_UPDATE = 'remoteDebugging/enable', +} + +export type BCDispatchAction = + | DeveloperDispatchAction + | DismissPersonCredentialOfferDispatchAction + | RemoteDebuggingDispatchAction export const BCDispatchAction = { ...DeveloperDispatchAction, ...DismissPersonCredentialOfferDispatchAction, + ...RemoteDebuggingDispatchAction, } export const iasEnvironments: Array = [ @@ -69,9 +82,14 @@ export const iasEnvironments: Array = [ }, ] +const remoteDebuggingState: RemoteDebuggingState = { + enabledAt: undefined, + sessionId: undefined, +} + const developerState: Developer = { environment: iasEnvironments[0], - remoteLoggingEnabled: false, + remoteDebugging: remoteDebuggingState, } const dismissPersonCredentialOfferState: DismissPersonCredentialOffer = { @@ -82,6 +100,7 @@ export enum BCLocalStorageKeys { PersonCredentialOfferDismissed = 'PersonCredentialOfferDismissed', Environment = 'Environment', GenesisTransactions = 'GenesisTransactions', + RemoteDebugging = 'RemoteDebugging', } export const initialState: BCState = { @@ -92,6 +111,19 @@ export const initialState: BCState = { const bcReducer = (state: BCState, action: ReducerAction): BCState => { switch (action.type) { + case RemoteDebuggingDispatchAction.REMOTE_DEBUGGING_STATUS_UPDATE: { + const { enabledAt, sessionId } = (action.payload || []).pop() + const developer = { ...state.developer, remoteDebugging: { enabledAt, sessionId } } + const newState = { ...state, developer } + + if (enabledAt) { + AsyncStorage.setItem(BCLocalStorageKeys.RemoteDebugging, JSON.stringify(developer.remoteDebugging)) + } else { + AsyncStorage.removeItem(BCLocalStorageKeys.RemoteDebugging) + } + + return newState + } case DeveloperDispatchAction.UPDATE_ENVIRONMENT: { const environment: IASEnvironment = (action?.payload || []).pop() const developer = { ...state.developer, environment }