whoisonline 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cfe52ed55db885ffcecbedd560e7bedcce71c1e4e89f88b46c738347a792cbd
4
- data.tar.gz: fda9b8968db797d1a4abee3d6c2024cec383497f709c880a421ce191125f7b69
3
+ metadata.gz: 8d9ca4af0f12891b59ecbceb8666fbbb49d05043bc29bce86caeb85f1fa5d92d
4
+ data.tar.gz: d87b7d98171a1b83793d6291c5ccaaf452a372d18554b532435522938486b5b2
5
5
  SHA512:
6
- metadata.gz: 0cec3f4acbff9888c28029323d31cb20e12de31b04164871c553be2bcb2d78b40bda8cf0159cec732a982552c104436b79dac6c3732189464ceb19b98130ce5f
7
- data.tar.gz: 3dff65d6deebd7dc2cd96b5d1bea5f050be32cc662817625b1bb4ecf7c6bc686388aa97ad5bfa1f8cffe3c4f1293de21b42c47a65194f882fa3bea6f311bd18f
6
+ metadata.gz: 889ddb36bc48126bac9d70df40140381cef90c66a083bc89c533e60b29d44bc117ff9bb1f4637a2f0b60fb6dd04b399fce74db9b232b32362e4a23bf88a0ff98
7
+ data.tar.gz: 102ad2155cee722972f09558454f57abc61a652453ac70a05ace23833948ced1f0191b999eb313199a6f2c17be097fd53e7b2339d74e68364199dbffcc0e259e
data/README.md CHANGED
@@ -1,20 +1,22 @@
1
1
  # WhoIsOnline
2
2
 
3
- Track who is online right now?” in Rails 7/8 using Redis TTL. No database writes, production-safe, and auto-hooks into controllers via a Rails Engine.
3
+ Track "who is online right now?" in Rails 7+ using Redis TTL. No database writes, production-safe, and auto-hooks into controllers via a Rails Engine.
4
4
 
5
5
  ## Features
6
6
  - Rails Engine auto-includes a controller concern to mark users online.
7
7
  - Works with `current_user` from any auth system (Devise, custom, etc.).
8
8
  - TTL-based presence in Redis, no tables required.
9
+ - **Automatic offline detection** when users close their browser/tab.
10
+ - **Visible-only heartbeats**: only ping while the tab is visible/active (configurable interval).
9
11
  - Throttled Redis writes to reduce load (configurable).
10
12
  - Safe SCAN-based counting; no `KEYS`.
11
- - Configurable Redis client, TTL, throttle duration, user id method, controller accessor, and namespace.
13
+ - Configurable Redis client, TTL, throttle duration, user id method, controller accessor, namespace, and heartbeat interval.
12
14
 
13
15
  ## Installation
14
16
  Add to your Gemfile:
15
17
 
16
18
  ```ruby
17
- gem "whoisonline", github: "rails-to-rescue/whoisonline"
19
+ gem "whoisonline", github: "KapilDevPal/WhoIsOnline"
18
20
  ```
19
21
 
20
22
  Or install directly:
@@ -32,13 +34,24 @@ WhoIsOnline.configure do |config|
32
34
  config.ttl = 5.minutes
33
35
  config.throttle = 60.seconds
34
36
  config.user_id_method = :id
37
+ config.heartbeat_interval = 60.seconds # client heartbeat when tab visible
35
38
  end
36
39
  ```
37
40
 
38
- The engine auto-adds a concern that runs after each controller action to mark the `current_user` as online. Nothing else is required.
41
+ The engine auto-adds a concern that runs after each controller action to mark the `current_user` as online.
42
+
43
+ **To enable offline detection when users close their browser**, add this to your main layout (e.g., `app/views/layouts/application.html.erb`):
44
+
45
+ ```erb
46
+ <%= whoisonline_offline_script %>
47
+ ```
48
+
49
+ This will automatically mark users offline when they close the browser/tab.
50
+ Heartbeats run only when the tab is visible/active (interval: `heartbeat_interval`, default 60s), keeping the user online without constant polling.
39
51
 
40
52
  ## Public API
41
53
  - `WhoIsOnline.track(user)` – mark a user online (auto-called by the controller concern).
54
+ - `WhoIsOnline.offline(user)` – mark a user offline immediately.
42
55
  - `WhoIsOnline.online?(user)` – boolean.
43
56
  - `WhoIsOnline.count` – number of online users (via SCAN).
44
57
  - `WhoIsOnline.user_ids` – array of ids (strings by default).
@@ -52,6 +65,7 @@ WhoIsOnline.configure do |config|
52
65
  config.throttle = 60.seconds # minimum time between Redis writes per user
53
66
  config.user_id_method = :id # how to pull an ID from the user object
54
67
  config.current_user_method = :current_user # method on controllers
68
+ config.heartbeat_interval = 60.seconds # heartbeat frequency while tab visible
55
69
  config.namespace = "whoisonline:user"
56
70
  config.auto_hook = true # disable if you prefer manual tracking
57
71
  config.logger = Rails.logger if defined?(Rails)
@@ -69,6 +83,9 @@ end
69
83
  # Somewhere in your controller you can also call manually:
70
84
  WhoIsOnline.track(current_user)
71
85
 
86
+ # Mark user offline (e.g., on logout)
87
+ WhoIsOnline.offline(current_user)
88
+
72
89
  # In a background job
73
90
  if WhoIsOnline.online?(user)
74
91
  # notify
@@ -79,6 +96,23 @@ end
79
96
  @online_count = WhoIsOnline.count
80
97
  ```
81
98
 
99
+ ### Layout Example
100
+ In your main layout (`app/views/layouts/application.html.erb`):
101
+
102
+ ```erb
103
+ <!DOCTYPE html>
104
+ <html>
105
+ <head>
106
+ <title>My App</title>
107
+ <%= csrf_meta_tags %>
108
+ </head>
109
+ <body>
110
+ <%= yield %>
111
+ <%= whoisonline_offline_script %>
112
+ </body>
113
+ </html>
114
+ ```
115
+
82
116
  ## Extensibility
83
117
  - Engine-based hook is easy to extend (e.g., add ActionCable broadcast).
84
118
  - Tracker service is isolated and unit-testable.
@@ -5,7 +5,7 @@ module WhoIsOnline
5
5
  DEFAULT_NAMESPACE = "whoisonline:user".freeze
6
6
 
7
7
  attr_accessor :ttl, :throttle, :user_id_method, :namespace, :auto_hook,
8
- :logger, :current_user_method
8
+ :logger, :current_user_method, :heartbeat_interval
9
9
  attr_writer :redis
10
10
 
11
11
  def initialize
@@ -16,6 +16,7 @@ module WhoIsOnline
16
16
  @namespace = DEFAULT_NAMESPACE
17
17
  @auto_hook = true
18
18
  @logger = default_logger
19
+ @heartbeat_interval = 60.seconds
19
20
  end
20
21
 
21
22
  def redis
@@ -1,5 +1,6 @@
1
1
  require "rails/engine"
2
2
  require_relative "controller"
3
+ require_relative "presence_controller"
3
4
 
4
5
  module WhoIsOnline
5
6
  class Engine < ::Rails::Engine
@@ -10,6 +11,19 @@ module WhoIsOnline
10
11
  include WhoIsOnline::Controller
11
12
  end
12
13
  end
14
+
15
+ initializer "whoisonline.routes" do |app|
16
+ app.routes.append do
17
+ post "/whoisonline/offline", to: "whoisonline/presence#offline", as: :whoisonline_offline
18
+ post "/whoisonline/heartbeat", to: "whoisonline/presence#heartbeat", as: :whoisonline_heartbeat
19
+ end
20
+ end
21
+
22
+ initializer "whoisonline.helpers" do
23
+ ActiveSupport.on_load(:action_view) do
24
+ include WhoIsOnline::ApplicationHelper
25
+ end
26
+ end
13
27
  end
14
28
  end
15
29
 
@@ -0,0 +1,37 @@
1
+ require "action_controller"
2
+
3
+ module WhoIsOnline
4
+ class PresenceController < ActionController::Base
5
+ # Skip CSRF for sendBeacon requests (they may not include CSRF token reliably)
6
+ skip_before_action :verify_authenticity_token, raise: false
7
+ protect_from_forgery with: :null_session, only: [:offline]
8
+
9
+ def offline
10
+ user = resolve_whoisonline_user
11
+ WhoIsOnline.offline(user) if user
12
+ head :ok
13
+ rescue StandardError => e
14
+ WhoIsOnline.configuration.logger&.warn("whoisonline offline failed: #{e.class} #{e.message}")
15
+ head :ok
16
+ end
17
+
18
+ def heartbeat
19
+ user = resolve_whoisonline_user
20
+ WhoIsOnline.track(user) if user
21
+ head :ok
22
+ rescue StandardError => e
23
+ WhoIsOnline.configuration.logger&.warn("whoisonline heartbeat failed: #{e.class} #{e.message}")
24
+ head :ok
25
+ end
26
+
27
+ private
28
+
29
+ def resolve_whoisonline_user
30
+ method = WhoIsOnline.configuration.current_user_method
31
+ return public_send(method) if respond_to?(method, true)
32
+
33
+ nil
34
+ end
35
+ end
36
+ end
37
+
@@ -33,6 +33,13 @@ module WhoIsOnline
33
33
  []
34
34
  end
35
35
 
36
+ def delete_presence(key)
37
+ connection.del(key)
38
+ rescue StandardError => e
39
+ log(:warn, "whoisonline delete failed: #{e.class} #{e.message}")
40
+ nil
41
+ end
42
+
36
43
  private
37
44
 
38
45
  def log(level, message)
@@ -19,6 +19,15 @@ module WhoIsOnline
19
19
  @last_write_by_user[uid] = Time.now if result
20
20
  end
21
21
 
22
+ def offline(user)
23
+ uid = extract_id(user)
24
+ return unless uid
25
+
26
+ key = presence_key(uid)
27
+ @redis_store.delete_presence(key)
28
+ @last_write_by_user.delete(uid)
29
+ end
30
+
22
31
  def online?(user)
23
32
  uid = extract_id(user)
24
33
  return false unless uid
@@ -1,5 +1,5 @@
1
1
  module WhoIsOnline
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
4
4
 
5
5
 
data/lib/whoisonline.rb CHANGED
@@ -10,7 +10,7 @@ require_relative "whoisonline/engine"
10
10
 
11
11
  module WhoIsOnline
12
12
  class << self
13
- delegate :track, :online?, :count, :user_ids, :users, to: :tracker
13
+ delegate :track, :offline, :online?, :count, :user_ids, :users, to: :tracker
14
14
 
15
15
  def tracker
16
16
  @_tracker ||= Tracker.new(configuration, redis_store)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whoisonline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kapil Dev Pal
@@ -79,6 +79,7 @@ files:
79
79
  - lib/whoisonline/configuration.rb
80
80
  - lib/whoisonline/controller.rb
81
81
  - lib/whoisonline/engine.rb
82
+ - lib/whoisonline/presence_controller.rb
82
83
  - lib/whoisonline/redis_store.rb
83
84
  - lib/whoisonline/tracker.rb
84
85
  - lib/whoisonline/version.rb