Module: Upkeep::Rails::Cable::SubscriberIdentity
- Defined in:
- lib/upkeep/rails/cable/subscriber_identity.rb
Constant Summary collapse
- ANONYMOUS_PUBLIC_MODE =
"anonymous_public"- IDENTIFIED_MODE =
"identified"- CONNECTION_IDENTITY_SOURCES =
%w[Current.user cookie current_attribute session warden_user].freeze
Class Method Summary collapse
- .active_record?(value) ⇒ Boolean
- .active_record_component(name, record) ⇒ Object
- .anonymous_components ⇒ Object
- .blank?(value) ⇒ Boolean
- .component_for(name, value) ⇒ Object
- .component_for_dependency(dependency) ⇒ Object
- .connection_identity_dependency?(dependency) ⇒ Boolean
- .current_user_dependency?(dependency) ⇒ Boolean
- .decision_for(_request = nil, recorder:) ⇒ Object
- .derive(connection) ⇒ Object
- .derive_all(connection) ⇒ Object
- .derive_from_request(request, recorder:, decision: decision_for(request, recorder: recorder)) ⇒ Object
- .for_components(components) ⇒ Object
- .for_identifiers(identifiers) ⇒ Object
- .global_id_component(name, value) ⇒ Object
- .identifier_components(connection) ⇒ Object
- .identity_dependencies(recorder) ⇒ Object
- .model_component(name, identity) ⇒ Object
- .recorder_components(recorder) ⇒ Object
- .request_components(request) ⇒ Object
- .scalar?(value) ⇒ Boolean
- .scalar_component(name, value) ⇒ Object
- .session_id_for(request) ⇒ Object
Class Method Details
.active_record?(value) ⇒ Boolean
159 160 161 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 159 def active_record?(value) defined?(::ActiveRecord::Base) && value.is_a?(::ActiveRecord::Base) end |
.active_record_component(name, record) ⇒ Object
163 164 165 166 167 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 163 def active_record_component(name, record) raise UnidentifiedSubscriber, "ActionCable identifier #{name} is an unsaved record" unless record.id model_component(name, model: record.class.name, id: record.id) end |
.anonymous_components ⇒ Object
102 103 104 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 102 def anonymous_components [ scalar_component(:anonymous_public_subscription, SecureRandom.uuid) ] end |
.blank?(value) ⇒ Boolean
208 209 210 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 208 def blank?(value) value.nil? || value == "" end |
.component_for(name, value) ⇒ Object
145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 145 def component_for(name, value) raise UnidentifiedSubscriber, "ActionCable identifier #{name} is nil" if value.nil? if active_record?(value) active_record_component(name, value) elsif scalar?(value) scalar_component(name, value) elsif value.respond_to?(:to_gid_param) global_id_component(name, value) else raise UnidentifiedSubscriber, "ActionCable identifier #{name} has no canonical identity" end end |
.component_for_dependency(dependency) ⇒ Object
120 121 122 123 124 125 126 127 128 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 120 def component_for_dependency(dependency) if dependency.source == :current_attribute && current_user_dependency?(dependency) model_component(:current_user, dependency.key.fetch(:value)) elsif dependency.source == "Current.user" model_component(:current_user, dependency.) elsif dependency.source == :warden_user model_component(:"warden_#{dependency..fetch(:scope)}", dependency.key.fetch(:value)) end end |
.connection_identity_dependency?(dependency) ⇒ Boolean
116 117 118 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 116 def connection_identity_dependency?(dependency) CONNECTION_IDENTITY_SOURCES.include?(dependency.source.to_s) end |
.current_user_dependency?(dependency) ⇒ Boolean
130 131 132 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 130 def current_user_dependency?(dependency) dependency..fetch(:name) == "user" end |
.decision_for(_request = nil, recorder:) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 55 def decision_for(_request = nil, recorder:) dependencies = identity_dependencies(recorder) if dependencies.empty? Decision.new(ANONYMOUS_PUBLIC_MODE, true, nil, []) else Decision.new( IDENTIFIED_MODE, false, "identity_dependencies_present", dependencies.map { |dependency| dependency.source.to_s }.uniq.sort ) end end |
.derive(connection) ⇒ Object
22 23 24 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 22 def derive(connection) derive_all(connection).last end |
.derive_all(connection) ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 26 def derive_all(connection) identities = [] request_components = request_components(connection.request) if connection.respond_to?(:request) identifier_components = identifier_components(connection) identities << for_components(request_components) if request_components&.any? identities << for_components(Array(request_components) + identifier_components) if identifier_components.any? identities = identities.uniq(&:subscriber_id) raise UnidentifiedSubscriber, "ActionCable connection has no server identifiers" if identities.empty? identities end |
.derive_from_request(request, recorder:, decision: decision_for(request, recorder: recorder)) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 40 def derive_from_request(request, recorder:, decision: decision_for(request, recorder: recorder)) components = if decision.anonymous anonymous_components else request_components(request) + recorder_components(recorder) end if components.empty? raise UnidentifiedSubscriber, "subscription has identity dependencies but no canonical request or recorder identity" end for_components(components) end |
.for_components(components) ⇒ Object
85 86 87 88 89 90 91 92 93 94 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 85 def for_components(components) canonical_bytes = JSON.generate(components.sort_by { |component| component.fetch(:name) }) subscriber_id = "action_cable:#{Digest::SHA256.hexdigest(canonical_bytes)}" Identity.new( subscriber_id, Delivery::ActionCableAdapter.stream_name_for(subscriber_id), components ) end |
.for_identifiers(identifiers) ⇒ Object
81 82 83 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 81 def for_identifiers(identifiers) for_components(identifiers.map { |name, value| component_for(name, value) }) end |
.global_id_component(name, value) ⇒ Object
186 187 188 189 190 191 192 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 186 def global_id_component(name, value) { name: name.to_s, kind: "global_id", value: value.to_gid_param } end |
.identifier_components(connection) ⇒ Object
69 70 71 72 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 69 def identifier_components(connection) identifiers = Array(connection.identifiers) identifiers.map { |name| component_for(name, connection.public_send(name)) } end |
.identity_dependencies(recorder) ⇒ Object
106 107 108 109 110 111 112 113 114 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 106 def identity_dependencies(recorder) return [] unless recorder recorder.graph.dependency_nodes .map(&:payload) .select(&:identity?) .select { |dependency| connection_identity_dependency?(dependency) } .uniq(&:cache_key) end |
.model_component(name, identity) ⇒ Object
134 135 136 137 138 139 140 141 142 143 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 134 def model_component(name, identity) return unless identity.is_a?(Hash) && identity[:model] && identity[:id] { name: name.to_s, kind: "model", model: identity.fetch(:model), id: identity.fetch(:id).to_s } end |
.recorder_components(recorder) ⇒ Object
96 97 98 99 100 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 96 def recorder_components(recorder) identity_dependencies(recorder) .filter_map { |dependency| component_for_dependency(dependency) } .uniq end |
.request_components(request) ⇒ Object
74 75 76 77 78 79 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 74 def request_components(request) session_id = session_id_for(request) return [] unless session_id [scalar_component(:rails_session, session_id)] end |
.scalar?(value) ⇒ Boolean
169 170 171 172 173 174 175 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 169 def scalar?(value) value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Integer) || value == true || value == false end |
.scalar_component(name, value) ⇒ Object
177 178 179 180 181 182 183 184 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 177 def scalar_component(name, value) { name: name.to_s, kind: "scalar", class: value.class.name, value: value.to_s } end |
.session_id_for(request) ⇒ Object
194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/upkeep/rails/cable/subscriber_identity.rb', line 194 def session_id_for(request) return unless request&.respond_to?(:session) session = request.session session_id = session.id if session.respond_to?(:id) session_id = session_id.public_id if session_id.respond_to?(:public_id) session_id = session_id.private_id if session_id.respond_to?(:private_id) session_id = session[:session_id] if blank?(session_id) && session.respond_to?(:[]) session_id.to_s unless blank?(session_id) rescue StandardError nil end |