pelargir / auto-session-timeout

Provides automatic session timeout in a Rails application.
MIT License
126 stars 63 forks source link

Improvement for project/Inspiration for others(?): Stimulus Controller #45

Open fluser opened 10 months ago

fluser commented 10 months ago

Hello and thank you very much for this gem.

This is not a bug - it is more kind of a documentation improvement or suggestion to create a Node.js package.

I am using Rails 7.1 with Hotwire Stimulus-Rails/Turbo-Rails and ended up writing my own Stimulus controller for AutoSessionTimeout, based on the provided JS.

I experimented a bit with Stimulus and came up with this solution.

My Stimulus controller provides:

This is my first attempt to dig into JS - if there are any mistakes or possible improvements, please let me know :-)

Stimulus controllers customizations (thanks to #16):

app/javascript/controllers/index.js

[...]
import AutoSessionTimeoutController from "./auto_session_timeout_controller"
application.register("auto-session-timeout", AutoSessionTimeoutController)
[...]

app/javascript/controllers/auto_session_timeout_controller.js

import {Controller} from "@hotwired/stimulus"

// Connects to data-controller="auto-session-timeout"
export default class extends Controller {

    initialize() {
        this.intervalId = 0;
        this.waitUntil = 0

        this.frequency = Number(this.element.dataset.astFrequency) || 60;
        this.active_path = String(this.element.dataset.astActivePath)
        this.keep_alive_path = String(this.element.dataset.astKeepAlivePath)

        super.initialize();
    }

    connect() {
        if (this.active_path === 'undefined') {
            console.log('auto-session-timeout message:')
            console.log('ast-active-path is not defined - value: ' + this.active_path);
            return;
        }

        if (this.keep_alive_path === 'undefined') {
            console.log('auto-session-timeout message:')
            console.log('ast-ping-path is not defined - value: ' + this.keep_alive_path);
            return;
        }

        this.intervalId = setInterval(this.check_timeout.bind(this), (this.frequency * 1000));
    }

    disconnect() {
        clearInterval(this.intervalId);
    }

    check_return_value(event) {
        var status = event.target.status;
        var response = event.target.response;

        if (status === 200 && (response === false || response === 'false' || response === null)) {
            location.reload();
        }
    }

    check_timeout() {
        const request = new XMLHttpRequest();

        request.onload = this.check_return_value.bind(this);

        request.open('GET', this.active_path, true);
        request.responseType = 'json';
        request.send();
    }

    keep_alive() {
        const request = new XMLHttpRequest();

        if (Date.now() >= this.waitUntil) {
            this.waitUntil = Date.now() + 1000 * this.frequency;
        } else {
            return;
        }

        request.onload = this.check_return_value.bind(this);

        request.open('GET', this.keep_alive_path, true);
        request.responseType = 'json';
        request.send();
    }
}

Helper method:

app/helpers/application_helper.rb

  def auto_session_timeout_settings
    if user_signed_in?
      data_action = [
        'keypress->auto-session-timeout#keep_alive',
        'scroll@window->auto-session-timeout#keep_alive'
      ]

      {
        data: {
          controller: 'auto-session-timeout',
          'ast-frequency': 5,
          'ast-active-path': active_path,
          'ast-keep-alive-path': keep_alive_path,
          action: data_action.join(' ')
        }
      }
    else
      {}
    end
  end

View:

app/views/layouts/application.html.slim

[...]
body *auto_session_timeout_settings
  [...]

Routes:

config/routes.rb

[...]
  devise_for :users, controllers: { sessions: "users/sessions" }

  devise_scope :user do
    get "active", to: "users/sessions#active"
    get "keep-alive", to: "users/sessions#active"
    # Not used - just for completion
    # get "timeout", to: "users/sessions#timeout"
  end
[...]

Application Controller (thanks to #16 )

[...]
  # has to be after auto_session_timeout so that prepend will not be overwritten.                                                                                                                                  
  # Required: Login will not work without
  # > Can't verify CSRF token authenticity.
  protect_from_forgery with: :exception, prepend: true
[...]
pelargir commented 10 months ago

Thanks! This is great stuff. I'm not really sure how/where to incorporate this into the project, though. Perhaps you could fork the project, modify it, and create a Node.js package yourself?