Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added new inactivity wrapper component #1283

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions packages/legacy/core/App/components/misc/InactivityWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useAgent } from '@credo-ts/react-hooks'
import React, { PropsWithChildren, useEffect, useRef } from 'react'
import { AppState, PanResponder, View } from 'react-native'
import { useAuth } from '../../contexts/auth'
import { useStore } from '../../contexts/store'
import { DispatchAction } from '../../contexts/reducers/store'
import { TOKENS, useServices } from '../../container-api'

export enum LockOutTime {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enums are somewhat of a hack in typescript. I think we favour as const like

const X = {
  Blah: 'zzzzzz',
  Blarb: 'rrrrr',
} as const

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to change that. Is there a way to still have this object act as a type when defining something like a type or interface? for example:

const MyConst = {
  Blah: 'something'
} as const

type MyType = {
  someAttribute: MyConst
}

OneMinute = 1,
ThreeMinutes = 3,
FiveMinutes = 1,
Never = 0,
}

interface InactivityWrapperProps {
al-rosenthal marked this conversation as resolved.
Show resolved Hide resolved
timeoutLength?: LockOutTime // number of minutes before timeoutAction is triggered, a value of 0 will never trigger the timeoutAction and an undefined value will default to 5 minutes
}

const InactivityWrapper: React.FC<PropsWithChildren<InactivityWrapperProps>> = ({ children, timeoutLength }) => {
const [logger] = useServices([TOKENS.UTIL_LOGGER])
useStore()
al-rosenthal marked this conversation as resolved.
Show resolved Hide resolved
const [_, dispatch] = useStore()
const { agent } = useAgent()
const { removeSavedWalletSecret } = useAuth()
const timeout_minutes = timeoutLength !== undefined ? timeoutLength : LockOutTime.OneMinute
al-rosenthal marked this conversation as resolved.
Show resolved Hide resolved
const inactivityTimer = useRef<NodeJS.Timeout | null>(null)
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
// some user interaction, reset timeout
resetInactivityTimeout(timeout_minutes)

// returns false so the PanResponder doesn't consume the touch event
return false
},
})
).current

const resetInactivityTimeout = (minutes: number) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably need to wrap all your fn inside a useCallback. Every time a component renders a function will be re-created which is a performance hit. Also, because a fn can be a dependency on a useEffect when it is re-created it will change causing a useEffect to trigger. So, in general, its good to just wrap 'em and let RN sort it out.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

functions are wrapped in callbacks

// remove existing timeout
clearTimer()

// do not start timer if timeout is set to 0
if (minutes > 0) {
// create new timeout
inactivityTimer.current = setTimeout(async () => {
lockUserOut()
}, minutesToMilliseconds(minutes))
}
}

const lockUserOut = async () => {
try {
removeSavedWalletSecret()
await agent?.wallet.close()
} catch (error) {
logger.error(`Error closing agent wallet, ${error}`)
}

dispatch({
type: DispatchAction.DID_AUTHENTICATE,
payload: [{ didAuthenticate: false }],
})

dispatch({
type: DispatchAction.LOCKOUT_UPDATED,
payload: [{ displayNotification: true }],
})
}

const clearTimer = () => {
if (inactivityTimer.current) {
clearTimeout(inactivityTimer.current)
}
}

const minutesToMilliseconds = (minutes: number) => {
al-rosenthal marked this conversation as resolved.
Show resolved Hide resolved
return minutes * 60000
}

useEffect(() => {
// Setup listener for app state changes (background/ foreground movement)
const eventSubscription = AppState.addEventListener('change', (nextAppState) => {
if (AppState.currentState === 'active' && ['inactive', 'background'].includes(nextAppState)) {
if (nextAppState === 'inactive') {
// special case for iOS devices when a prompt is shown
return
}

// remove timer
clearTimer()
}

if (AppState.currentState === 'active' && ['active'].includes(nextAppState)) {
if (nextAppState === 'inactive') {
// special case for iOS devices when a prompt is shown
return
}

// app coming into the foreground is 'user activity', restart timer
resetInactivityTimeout(timeout_minutes)
}
})

// initiate inactivity timer
resetInactivityTimeout(timeout_minutes)

return () => {
clearTimer()
eventSubscription.remove()
}
}, [])

return (
<View style={{ flex: 1 }} {...panResponder.panHandlers}>
{children}
</View>
)
}
export default InactivityWrapper
5 changes: 4 additions & 1 deletion packages/legacy/core/App/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import Scan from './screens/Scan'
import Onboarding from './screens/Onboarding'
import { PINRules, walletTimeout } from './constants'
import { CredentialListFooterProps } from './types/credential-list-footer'
import InactivityWrapper, { LockOutTime } from 'components/misc/InactivityWrapper'

export * from './navigators'
export * from './services/storage'
Expand Down Expand Up @@ -125,16 +126,18 @@ export {
credentialOfferTourSteps,
proofRequestTourSteps,
ButtonType,
IconButton,
ButtonLocation,
CheckBoxRow,
CredentialCard,
ContentGradient,
ErrorModal,
IconButton,
InactivityWrapper,
InfoTextBox,
InfoBox,
InfoBoxType,
Link,
LockOutTime,
ToastType,
toastConfig,
NetInfo,
Expand Down
Loading