Class: WhereIsWaldo::Adapters::RedisAdapter

Inherits:
BaseAdapter
  • Object
show all
Defined in:
app/services/where_is_waldo/adapters/redis_adapter.rb

Instance Method Summary collapse

Instance Method Details

#cleanup(timeout: nil) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'app/services/where_is_waldo/adapters/redis_adapter.rb', line 103

def cleanup(timeout: nil)
  threshold = Time.current.to_i - (timeout || default_timeout)
  cleaned = 0

  # Get stale subject IDs
  stale_subject_ids = redis.zrangebyscore(online_subjects_key, "-inf", threshold)

  stale_subject_ids.each do |subject_id|
    # Check if any sessions are still active
    session_ids = redis.smembers(subject_sessions_key(subject_id))
    all_stale = session_ids.all? do |sid|
      data = get_presence_data(sid)
      !data || data["last_heartbeat"].to_i < threshold
    end

    if all_stale
      disconnect_subject(subject_id)
      cleaned += 1
    end
  end

  # Remove stale entries from sorted set
  redis.zremrangebyscore(online_subjects_key, "-inf", threshold)

  cleaned
end

#connect(session_id:, subject_id:, metadata: {}) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'app/services/where_is_waldo/adapters/redis_adapter.rb', line 6

def connect(session_id:, subject_id:, metadata: {})
  now = Time.current.to_i

  presence_data = {
    session_id: session_id,
    subject_id: subject_id,
    connected_at: now,
    last_heartbeat: now,
    tab_visible: true,
    subject_active: true,
    last_activity: now,
    metadata: 
  }

  redis.multi do |tx|
    # Store session data
    tx.set(session_key(session_id), presence_data.to_json, ex: ttl)

    # Add to online subjects sorted set with timestamp as score
    tx.zadd(online_subjects_key, now, subject_id)

    # Add to subject's sessions set
    tx.sadd(subject_sessions_key(subject_id), session_id)

    # Map session to subject for reverse lookup
    tx.set(session_subject_key(session_id), subject_id, ex: ttl)
  end

  true
rescue StandardError => e
  Rails.logger.error "[WhereIsWaldo] Redis connect failed: #{e.message}"
  false
end

#disconnect(session_id: nil, subject_id: nil) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
# File 'app/services/where_is_waldo/adapters/redis_adapter.rb', line 40

def disconnect(session_id: nil, subject_id: nil)
  if session_id
    disconnect_session(session_id)
  elsif subject_id
    disconnect_subject(subject_id)
  end

  true
rescue StandardError => e
  Rails.logger.error "[WhereIsWaldo] Redis disconnect failed: #{e.message}"
  false
end

#heartbeat(session_id:, tab_visible: true, subject_active: true, last_activity_at: nil, metadata: {}) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'app/services/where_is_waldo/adapters/redis_adapter.rb', line 53

def heartbeat(session_id:, tab_visible: true, subject_active: true, last_activity_at: nil, metadata: {})
  data = get_presence_data(session_id)
  return false unless data

  now = Time.current.to_i
  data["last_heartbeat"] = now
  data["tab_visible"] = tab_visible
  data["subject_active"] = subject_active
  # Update last_activity: use JS timestamp if provided, otherwise use current time when active
  if last_activity_at
    data["last_activity"] = (last_activity_at / 1000.0).to_i
  elsif subject_active
    data["last_activity"] = now
  end
  data["metadata"] = data["metadata"].merge() if .present?

  redis.multi do |tx|
    tx.set(session_key(session_id), data.to_json, ex: ttl)
    tx.zadd(online_subjects_key, now, data["subject_id"])
  end

  true
rescue StandardError => e
  Rails.logger.error "[WhereIsWaldo] Redis heartbeat failed: #{e.message}"
  false
end

#online_subject_ids(timeout: nil) ⇒ Object



80
81
82
83
84
85
# File 'app/services/where_is_waldo/adapters/redis_adapter.rb', line 80

def online_subject_ids(timeout: nil)
  threshold = Time.current.to_i - (timeout || default_timeout)

  # Get subject IDs with heartbeat after threshold
  redis.zrangebyscore(online_subjects_key, threshold, "+inf").map(&:to_i)
end

#session_status(session_id) ⇒ Object



96
97
98
99
100
101
# File 'app/services/where_is_waldo/adapters/redis_adapter.rb', line 96

def session_status(session_id)
  data = get_presence_data(session_id)
  return nil unless data

  build_presence_hash(data)
end

#sessions_for_subject(subject_id) ⇒ Object



87
88
89
90
91
92
93
94
# File 'app/services/where_is_waldo/adapters/redis_adapter.rb', line 87

def sessions_for_subject(subject_id)
  session_ids = redis.smembers(subject_sessions_key(subject_id))

  session_ids.filter_map do |sid|
    data = get_presence_data(sid)
    build_presence_hash(data) if data
  end
end