'How can I initiate user logout after 5 minutes of inactivity?

I am trying to log out and invalidate the user token after 5 minutes of inactivity.

When the application is on 'active' stage I am using the react-native-user-inactivity and set the timeForInactivity for 5 minutes. However, when the application runs on the background it has an expected behavior, especially when I set it to 5 minutes.

I was using 1 minute for testing and it was running fine, it used to call the API and invalidates the token correctly after the minute has passed. However, when I have increased it to 5 minutes, it was not firing until the application stage changed to 'active'.

I have similar behavior when I use the setTimeout for 5 minutes on AppState change event.

I have only tried it iOS but I have read somewhere that on Android it is giving an error when setting a timeout for more than 2 minutes. Is that correct?

What is the best way to invalidate the user token: 1. When the application is in the background 2. Just before closing the application?



Solution 1:[1]

On move to background (AppState), you could store the timestamp in AsyncStorage (or something else that persists). On move to foreground, check if the current date is longer than 5 minutes ago. If > 5 minutes, logout. I don't think you need a library for that. Is that what you are looking for?

Solution 2:[2]

You can use the PanResponder from react-native package.

I have used custom navigator to pass the panhandler events through screenProps to all the screen. Checks below function, you might need to change the code, as per your requirement. This did work for me.

import React, { Component } from 'react';
import { Button, PanResponder, Text, View, StyleSheet } from 'react-native';
import { Constants } from 'expo';

// NOTE: you probably want this to be more than 2 seconds?
let TIME_TO_WAIT_FOR_INACTIVITY_MS = 1000;

// NOTE: this is how often you check whether the inactivity threshold has passed
const INACTIVITY_CHECK_INTERVAL_MS = 1000;

const LOG_OUT_POP = 4 * 60 * 1000;
//240,000 sec, 4 min

const LOG_OUT_USER = 60 * 1000;
// 60,000 , 1 min

export default class App extends Component {
  state = {};
  _lastInteraction = new Date();
  _panResponder = {};
  _timeIntervalForLoggingOut;

  componentWillMount() {
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
      onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
      onStartShouldSetPanResponderCapture: () => false,
      onMoveShouldSetPanResponderCapture: () => false,
      onPanResponderTerminationRequest: () => true,
      onShouldBlockNativeResponder: () => false,
    });

    this._maybeStartWatchingForInactivity();
  }

  _maybeStartWatchingForInactivity = () => {
    if (this._inactivityTimer) {
      return;
    }
    /**
     * - Issue https://github.com/facebook/react-native/issues/12981
     * - to over the android performance issue of setTimeout, we have used setInterval and kept checking the time difference every secound.
     */
    this._inactivityTimer = setInterval(() => {
      if (LOG_OUT_POP <= TIME_TO_WAIT_FOR_INACTIVITY_MS) {
        // show Alert timer
        console.log('ALERT TIMER 4 Minutes');
        clearInterval(this._inactivityTimer);

        /* 
        * Start new timer for loggin out user
        * Dont' forget to all clearInterval
        */

        this.timeIntervalForLoggingOut = setInterval(() => {
          if (LOG_OUT_USER <= TIME_TO_WAIT_FOR_INACTIVITY_MS) {
            // show Alert timer
            console.log('LOG OUT USER');
            clearInterval(this._inactivityTimer);
            clearInterval(this._timeIntervalForLoggingOut);
            //
          } else {
            this._setIsInactive();
          }
        }, INACTIVITY_CHECK_INTERVAL_MS);

        /* */


        //
      } else {
        this._setIsInactive();
      }
    }, INACTIVITY_CHECK_INTERVAL_MS);
  };

  // NOTE: you almost certainly want to throttle this so it only fires
  // every second or so!
  _setIsActive = () => {
    // this._lastInteraction = new Date();
    console.log(TIME_TO_WAIT_FOR_INACTIVITY_MS);
    TIME_TO_WAIT_FOR_INACTIVITY_MS += 1000;
    if (this.state.timeWentInactive) {
      this.setState({ timeWentInactive: null });
    }
    this._maybeStartWatchingForInactivity();
  };

  _setIsInactive = () => {
    this.setState({ timeWentInactive: new Date() });
    clearInterval(this._inactivityTimer);
    this._inactivityTimer = null;
  };

  render() {
    return (
      <View
        style={styles.container}
        collapsable={false}
        {...this._panResponder.panHandlers}>
        <Text style={styles.paragraph}>
          Put your app here{' '}
          {this.state.timeWentInactive &&
            `(inactive at: ${this.state.timeWentInactive})`}
        </Text>

        <Button
          title="Here is a button for some reason"
          onPress={() => alert('hi')}
        />
      </View>
    );
  }

  handleStartShouldSetPanResponder = () => {
    this._setIsActive();
    return false;
  };

  handleMoveShouldSetPanResponder = () => {
    this._setIsActive();
    return false;
  };
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#ecf0f1',
  },
  paragraph: {
    margin: 24,
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
    color: '#34495e',
  },
});

References:

  1. https://reactnavigation.org/docs/en/custom-navigators.html
  2. https://medium.com/@benjaminwfox/share-state-between-screens-with-custom-navigators-in-react-navigation-62a34e3c7f97
  3. The app is crashing when I navigate while going back using pop()

Original: https://snack.expo.io/SyGLEApZb

Tweaks: https://snack.expo.io/@ashish9342/warn-about-inactivity

Solution 3:[3]

Try this

Modify as per your need

import { useCallback, useEffect, useRef, useState } from 'react'
import { AppState } from 'react-native'
import { useLazyApiData, useStores } from '../../data/store'

const inactiveTimeInMinute = 10
const millisecToMins = 60000

export const InactivityHandler = (navigationReset: () => void): any => {
  const appState = useRef(AppState.currentState)
  const [appStateVisibl, setAppStateVisible] = useState(appState.current)
  console.log(appStateVisibl)

  const { inactiveTime, resetInactivityTimer, updateInactivityTime, logout } = useStores((store) => ({
    inactiveTime: store?.user?.inactiveTime,
    resetInactivityTimer: store?.user?.resetInactivityTimer,
    updateInactivityTime: store?.user?.updateInactivityTime,
    logout: store?.user?.session?.logout,
  }))

  const [logoutAPI, { state: logoutapistatehandler }] = useLazyApiData(logout)

  useEffect(() => {
    if (logoutapistatehandler === 'fulfilled' || logoutapistatehandler === 'rejected') {
      navigationReset()
    }
  })

  const onAppStateChange = useCallback(
    (nextAppState: any) => {
      if (appState.current.match(/inactive|background/)) {
        updateInactivityTime(Date.now())
      }
      if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
        const differenceInElapsedTime = Date.now() - inactiveTime
        const backgroundElapsedTime = Math.floor(differenceInElapsedTime / millisecToMins)
        // var ELLAPSED_SECOND = (differenceInElapsedTime % 60000) / 1000 //For testing use second
        if (backgroundElapsedTime >= inactiveTimeInMinute) {
          logoutAPI()
        } else {
          resetInactivityTimer()
        }
      }
      appState.current = nextAppState
      setAppStateVisible(appState.current)
    },
    [inactiveTime, logoutAPI, resetInactivityTimer, updateInactivityTime],
  )

  useEffect(() => {
    AppState.addEventListener('change', onAppStateChange)
    return () => {
      AppState.removeEventListener('change', onAppStateChange)
    }
  }, [onAppStateChange])
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 martwetzels
Solution 2 Ashish Singh Rawat
Solution 3 Rishav Kumar