whoisonline 0.1.0 → 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: 94f2c31318e63780cdee77f18a8aaad0ff39133c1334205a50016713d9aa2de6
4
- data.tar.gz: b52a4bc959ffdfc54449cbee0cd27c74d2a5930d47487fb4f024d62e2a9c4e85
3
+ metadata.gz: 8d9ca4af0f12891b59ecbceb8666fbbb49d05043bc29bce86caeb85f1fa5d92d
4
+ data.tar.gz: d87b7d98171a1b83793d6291c5ccaaf452a372d18554b532435522938486b5b2
5
5
  SHA512:
6
- metadata.gz: 1fb7d27b02f1c7d897b412387cc8fd3236f3ad2851f0e3d164b521b54f8f51357ee0fde5cac5e0a87ace9a12776fab2c20d35ce904fc6924dc5bf1195e534444
7
- data.tar.gz: 38d09284b895047ca3107c22bfc3c7407781a2f6fe8bfd30ed48eb5e56ae0c8ba2fc9b18cbd09c20acea8ed1ab41029be8280eaa458dfa320a748771188df407
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.0"
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.0
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kapil Dev Pal
@@ -14,59 +14,59 @@ dependencies:
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: railties
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '7.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '7.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: redis
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '4.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '4.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: concurrent-ruby
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '1.2'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.2'
69
- description: Production-ready Rails 7/8 online presence tracking using Redis TTL and
69
+ description: Production-ready Rails 7+ online presence tracking using Redis TTL and
70
70
  controller auto-hook.
71
71
  email:
72
72
  - dev.kapildevpal@gmail.com
@@ -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