4

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?

isherwood
  • 58,414
  • 16
  • 114
  • 157
PoppyAnc
  • 131
  • 1
  • 2
  • 9

3 Answers3

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?

martwetzels
  • 437
  • 5
  • 19
  • Thanks for your reply, however, I want to expire the session after 5 minutes even if the user does not move to the foreground and if its possible, when he closes the app as well. – PoppyAnc Nov 15 '18 at 06:53
  • Perhaps consider maintaining the session servers-side? Have the app check with a session token if the session is invalid. You'd make the app logoff by default. Closing a session when the user moves to background isn't hard, but performing actions while on the background is a known limitation. Some libraries do provide support for it (indirectly), but I often find ways around it. Do you have some more information on the full setup? Server - client etc. – martwetzels Nov 15 '18 at 07:54
  • Server side we have a graphql server that calls the APIs. On the server, the token is valid for more than 10 minutes and should be revoked in case the user is inactive for 5 minutes. The token should auto refresh as long as the user is using the app (touches inside the app). However, if the user is inactive - not using the app - for 5 minutes the token should be revoked and redirected to login page. This should happen if the app is in the background, foreground or closed. – PoppyAnc Nov 15 '18 at 08:42
  • To my knowledge, the connection to the server drops when in background. If you make the token invalid after 5 minutes (instead of 10), that should do the trick? If the user is active within five minutes, you can check that on a server method or expiration time on the token, you auto-renew it. Do you use any libraries to achieve any of this or did you implement it yourself? We are using a similar approach (with a longer time-window) without any restriction from frameworks or libraries. – martwetzels Nov 15 '18 at 09:34
  • Everything is implemented without any libraries, if the application goes on the background and comes up before the 5 minutes the user can continue with no problem. If the user did not make any calls to the APIs for more than 10 minutes but was keep using the application the token will be auto renewed. 0nly problem left is to revoke the token after 5 minutes of inactivity when the application is on the background or the application has been forcibly closed. – PoppyAnc Nov 15 '18 at 10:17
  • I would suggest to handle this server side. Monitor the activity of the connection on the server, invalidate token after 5 minutes of inactivity. Server side is the only place where you can fully control the connection, RN in background is tricky. – martwetzels Nov 15 '18 at 10:27
0

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

Ashish Singh Rawat
  • 1,419
  • 16
  • 30
0

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])
}
Rishav Kumar
  • 4,979
  • 1
  • 17
  • 32