Skip to content

Commit

Permalink
feat: persistent remote debugging (#2054)
Browse files Browse the repository at this point in the history
Signed-off-by: Jason C. Leach <[email protected]>
  • Loading branch information
jleach authored Jul 22, 2024
1 parent 45575a6 commit 170e2c7
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 32 deletions.
31 changes: 31 additions & 0 deletions app/__tests__/helpers/Utils.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
59 changes: 45 additions & 14 deletions app/container-imp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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) => {
Expand All @@ -168,6 +190,7 @@ export class AppContainer implements Container {
(val) => (personCredOfferDissmissed = val)
),
loadState<IASEnvironment>(BCLocalStorageKeys.Environment, (val) => (environment = val)),
loadState<RemoteDebuggingState>(BCLocalStorageKeys.RemoteDebugging, (val) => (remoteDebugging = val)),
])
const state: BCState = {
...initialState,
Expand All @@ -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
Expand Down
8 changes: 1 addition & 7 deletions app/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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$',
'<rootDir>/node_modules/',
'<rootDir>/lib',
'<rootDir>/__tests__/contexts/',
'<rootDir>/__tests__/helpers/',
],
testPathIgnorePatterns: ['\\.snap$', '<rootDir>/node_modules/', '<rootDir>/lib', '<rootDir>/__tests__/contexts/'],
cacheDirectory: '.jest/cache',
}
16 changes: 16 additions & 0 deletions app/src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
26 changes: 20 additions & 6 deletions app/src/screens/Developer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -199,18 +199,32 @@ 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
}

setRemoteLoggingWarningModalVisible(true)
}

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)
Expand Down Expand Up @@ -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}
/>
Expand Down
4 changes: 2 additions & 2 deletions app/src/screens/Splash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
38 changes: 35 additions & 3 deletions app/src/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<IASEnvironment> = [
Expand Down Expand Up @@ -69,9 +82,14 @@ export const iasEnvironments: Array<IASEnvironment> = [
},
]

const remoteDebuggingState: RemoteDebuggingState = {
enabledAt: undefined,
sessionId: undefined,
}

const developerState: Developer = {
environment: iasEnvironments[0],
remoteLoggingEnabled: false,
remoteDebugging: remoteDebuggingState,
}

const dismissPersonCredentialOfferState: DismissPersonCredentialOffer = {
Expand All @@ -82,6 +100,7 @@ export enum BCLocalStorageKeys {
PersonCredentialOfferDismissed = 'PersonCredentialOfferDismissed',
Environment = 'Environment',
GenesisTransactions = 'GenesisTransactions',
RemoteDebugging = 'RemoteDebugging',
}

export const initialState: BCState = {
Expand All @@ -92,6 +111,19 @@ export const initialState: BCState = {

const bcReducer = (state: BCState, action: ReducerAction<BCDispatchAction>): 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 }
Expand Down

0 comments on commit 170e2c7

Please sign in to comment.