Class: Supabase::Realtime::Presence

Inherits:
Object
  • Object
show all
Defined in:
lib/supabase/realtime/presence.rb

Overview

Tracks presence state for one channel and implements the Phoenix Presence sync algorithm: presence_state replaces the local snapshot, presence_diff applies joins/leaves on top of it.

The algorithm mirrors phoenix.js’s Presence.syncState / Presence.syncDiff so callers porting from JS/Python see identical behavior.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePresence

Returns a new instance of Presence.



14
15
16
17
18
19
# File 'lib/supabase/realtime/presence.rb', line 14

def initialize
  @state = {}
  @on_sync_callbacks = []
  @on_join_callbacks = []
  @on_leave_callbacks = []
end

Instance Attribute Details

#stateObject (readonly)

Returns the value of attribute state.



12
13
14
# File 'lib/supabase/realtime/presence.rb', line 12

def state
  @state
end

Instance Method Details

#any_callbacks?Boolean

Returns:

  • (Boolean)


109
110
111
# File 'lib/supabase/realtime/presence.rb', line 109

def any_callbacks?
  [@on_sync_callbacks, @on_join_callbacks, @on_leave_callbacks].any? { |list| !list.empty? }
end

#listObject

List every meta currently tracked, flat. Useful when callers don’t care about the per-key grouping.



90
91
92
# File 'lib/supabase/realtime/presence.rb', line 90

def list
  @state.values.flat_map { |presence| metas(presence) }
end

#on_join(&block) ⇒ Object



99
100
101
102
# File 'lib/supabase/realtime/presence.rb', line 99

def on_join(&block)
  @on_join_callbacks << block
  self
end

#on_leave(&block) ⇒ Object



104
105
106
107
# File 'lib/supabase/realtime/presence.rb', line 104

def on_leave(&block)
  @on_leave_callbacks << block
  self
end

#on_sync(&block) ⇒ Object



94
95
96
97
# File 'lib/supabase/realtime/presence.rb', line 94

def on_sync(&block)
  @on_sync_callbacks << block
  self
end

#sync_diff(diff) ⇒ Object

Subsequent presence_diff messages carry only joins/leaves to apply.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/supabase/realtime/presence.rb', line 56

def sync_diff(diff)
  joins  = diff["joins"]  || {}
  leaves = diff["leaves"] || {}

  joins.each do |key, presence|
    if @state[key]
      existing_refs = metas(@state[key]).map { |m| m["phx_ref"] }
      new_metas     = metas(presence).reject { |m| existing_refs.include?(m["phx_ref"]) }
      @state[key]   = { "metas" => metas(@state[key]) + new_metas }
    else
      @state[key] = presence
    end
  end

  leaves.each do |key, presence|
    next unless @state[key]

    leaving_refs   = metas(presence).map { |m| m["phx_ref"] }
    remaining      = metas(@state[key]).reject { |m| leaving_refs.include?(m["phx_ref"]) }
    if remaining.empty?
      @state.delete(key)
    else
      @state[key] = { "metas" => remaining }
    end
  end

  emit_joins(joins)
  emit_leaves(leaves)
  @on_sync_callbacks.each(&:call)
  @state
end

#sync_state(new_state) ⇒ Object

The first presence_state message after joining sends the full state. Any local metas we already have for a key but the server doesn’t are emitted as leaves; anything new is emitted as a join.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/supabase/realtime/presence.rb', line 24

def sync_state(new_state)
  joins  = {}
  leaves = {}

  @state.each do |key, presence|
    leaves[key] = presence unless new_state.key?(key)
  end

  new_state.each do |key, new_presence|
    current = @state[key]
    if current
      joined = []
      left   = []
      current_refs = metas(current).map { |m| m["phx_ref"] }
      new_refs     = metas(new_presence).map { |m| m["phx_ref"] }
      joined = metas(new_presence).reject { |m| current_refs.include?(m["phx_ref"]) }
      left   = metas(current).reject     { |m| new_refs.include?(m["phx_ref"]) }
      joins[key]  = { "metas" => joined } unless joined.empty?
      leaves[key] = { "metas" => left }   unless left.empty?
    else
      joins[key] = new_presence
    end
  end

  @state = deep_copy(new_state)
  emit_joins(joins)
  emit_leaves(leaves)
  @on_sync_callbacks.each(&:call)
  @state
end