#534 Make sure the user session does not expire
Merged 6 years ago by abompard. Opened 6 years ago by abompard.
abompard/fedora-hubs feature/heartbeat  into  develop

@@ -0,0 +1,58 @@ 

+ import React from 'react';

+ import PropTypes from 'prop-types';

+ import { connect } from 'react-redux';

+ import {

+   sendHeartbeat,

+ } from "../core/actions/heartbeat";

+ 

+ 

+ class Heartbeat extends React.PureComponent {

+ 

+   constructor(props) {

+     super(props);

+     this.timeout = null;

+     this.interval = 60 * 1000;

+     this.ping = this.ping.bind(this);

+   }

+ 

+   componentWillMount() {

+     if (this.timeout !== null) {

+       clearTimeout(this.timeout);

+     }

+     this.ping();

+   }

+ 

+   componentWillUnmount() {

+     if (this.timeout === null) {

+       return;

+     }

+     clearTimeout(this.timeout);

+     this.timeout = null;

+   }

+ 

+   ping() {

+     this.props.sendHeartbeat();

+     this.timeout = setTimeout(this.ping, this.interval);

+   }

+ 

+   render() {

+     return null;

+   }

+ }

+ Heartbeat.propTypes = {

+   connected: PropTypes.bool,

+ }

+ Heartbeat.defaultProps = {

+   connected: true,

+ }

+ 

+ 

+ const mapStateToProps = (state) => {

+   return {

+     connected: state.ui.heartbeat === "connected",

+   }

+ };

+ 

+ export default connect(mapStateToProps, {

+   sendHeartbeat,

+ })(Heartbeat);

@@ -1,6 +1,7 @@ 

  import React from 'react';

  import PropTypes from 'prop-types';

  import FlashMessages from './FlashMessages';

+ import Heartbeat from './Heartbeat';

  import LeftMenu from './LeftMenu';

  

  
@@ -28,6 +29,8 @@ 

              </div>

            </div>

          </div>

+ 

+         <Heartbeat />

        </div>

      );

    }

@@ -18,7 +18,6 @@ 

        isEnabled: true,

      }

      this.source = null;

-     this.notifTimeout = null;

      this.setupSource = this.setupSource.bind(this);

    }

  

@@ -0,0 +1,36 @@ 

+ import { backendCall } from "../utils";

+ import { addFlashMessage } from './flashMessages';

+ 

+ 

+ export const HEARTBEAT_SUCCESS = 'HEARTBEAT_SUCCESS'

+ export const HEARTBEAT_FAILURE = 'HEARTBEAT_FAILURE'

+ 

+ 

+ function heartbeatFailure(code) {

+   return {

+     type: HEARTBEAT_FAILURE,

+     code,

+   };

+ }

+ 

+ 

+ export function sendHeartbeat() {

+   return (dispatch, getState) => {

+     const state = getState();

+     const url = state.urls.heartbeat;

+     const heartbeatConnected = state.ui.heartbeat === "connected";

+     return backendCall(url).then(

+       (response) => {

+         dispatch({

+           type: HEARTBEAT_SUCCESS

+         })

+       },

+       (error) => {

+         if (error.httpStatus === 401 && heartbeatConnected) {

+           dispatch(addFlashMessage("Your session timed out, please reload the page", "error"));

+         }

+         return dispatch(heartbeatFailure(error.httpStatus));

+       }

+     );

+   }

+ }

@@ -176,7 +176,7 @@ 

          return dispatch(fetchWidgets());

        },

        error => {

-         return dispatch(moveWidgetFailure(position, oldIndex, newIndex, e.message));

+         return dispatch(moveWidgetFailure(position, oldIndex, newIndex, error.message));

        })

    }

  }

@@ -0,0 +1,17 @@ 

+ import {

+   HEARTBEAT_SUCCESS,

+   HEARTBEAT_FAILURE,

+ } from '../actions/heartbeat';

+ 

+ 

+ export default function heartbeat(state="connected", action) {

+   switch (action.type) {

+     case HEARTBEAT_SUCCESS:

+       return "connected";

+     case HEARTBEAT_FAILURE:

+       return "disconnected";

+     default:

+       return state

+   }

+ }

+ 

@@ -1,5 +1,6 @@ 

  import { combineReducers } from 'redux';

  import flashMessages from "./flashMessages";

+ import heartbeat from "./heartbeat";

  import sseReducer from "./sse";

  import {

    hubReducer,
@@ -35,6 +36,7 @@ 

    widgetConfigDialogOpen,

    hubConfigDialogOpen,

    hubConfigDialogLoading,

+   heartbeat,

  });

  

  

@@ -20,6 +20,7 @@ 

          resultPromise = resultPromise.then((content) => {

            if (isJson) {

              // It's an object describing the error.

+             content.httpStatus = response.status;

              throw content;

            } else {

              // It's already a text message.

file modified
+1
@@ -31,6 +31,7 @@ 

              "api_hub_config_suggest_users", hub_id=hub.id),

          "user": flask.url_for("api_user"),

          "allGroups": flask.url_for("groups"),

+         "heartbeat": flask.url_for("heartbeat"),

      }

      current_user = get_user_details()

      flash_messages = [

file modified
+9
@@ -29,6 +29,7 @@ 

      current_user = get_user_details()

      urls = {

          "allGroups": flask.url_for("groups"),

+         "heartbeat": flask.url_for("heartbeat"),

      }

      flash_messages = [

          {"msg": msg[1], "type": msg[0]} for msg in
@@ -76,6 +77,14 @@ 

          )

  

  

+ @app.route('/ping')

+ def heartbeat():

+     if authenticated():

+         return flask.jsonify({"status": "OK"})

+     else:

+         return flask.jsonify({"status": "ERROR"}), 401

+ 

+ 

  @app.route('/login/', methods=('GET', 'POST'))

  @app.route('/login', methods=('GET', 'POST'))

  @OIDC.require_login

file modified
+1
@@ -20,6 +20,7 @@ 

          "widgets": flask.url_for("api_hub_widgets", hub_id=stream.id),

          "availableWidgets": flask.url_for("api_widgets", hub_id=stream.id),

          "allGroups": flask.url_for("groups"),

+         "heartbeat": flask.url_for("heartbeat"),

      }

      flash_messages = [

          {"msg": msg[1], "type": msg[0]} for msg in