loco-rails 4.0.0 → 6.0.0

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: 14ddadb549243851cfae8f6260dbe08b6813392116f03b83d4618615598ec2d3
4
- data.tar.gz: c5cdfc9d981ce63a5e8237cb794d7eb2a9d8435941070bba84f65f188e08e215
3
+ metadata.gz: 479391be7cf538ec1e33167e15508617bbd6312f607204185f0be6f12bba0372
4
+ data.tar.gz: 1365a880d79a4e838a6101de9bd5ec7678f4f2be74bd3be9570f898d3949ccf0
5
5
  SHA512:
6
- metadata.gz: '0921bb8ffff692bede6999ae0dc2de20d1822c0ca3b5a8bac0aabcd50909a12369a6987da6b309356e4306dfd3891aea85adff8c418cce1eacabc4bd73348509'
7
- data.tar.gz: f8d9d259ae99c421bcbdbf03df5fa1878ae6dd590eb835d6c432a6c6e91b103df5d5efbcdfe9daff6ae1d4fbb2bab1c193d3416b1e6213b7e134842d7f2dea48
6
+ metadata.gz: e7d596e1289162d46b33bb68355032e64c125ed80e82a2be7203ccb6dd5095bdf511a82f71be2346188e45d7232c654c84b0d0234fc260f3e15ea2c211a0627a
7
+ data.tar.gz: 7a3dd738a162abd503389b5e46173f3d68bd3de72c672a1df5ea94eb7cc3e168d40a07f809841d50de5f70111265fd730a6db28c3a1e1800bd167956963d8bd5
@@ -5,61 +5,37 @@ module Loco
5
5
  def subscribed
6
6
  return unless loco_permissions.is_a?(Array)
7
7
 
8
- stream_for_resources
9
- return if loco_permissions.compact.size > 1
8
+ @uuid, *signed_in_resources = PermissionsPresenter.signed_in(loco_permissions)
9
+ return unless @uuid.is_a?(String)
10
10
 
11
- SenderJob.perform_later @uuid, loco: { start_ajax_polling: true }
11
+ stream_from("loco:notification_center:#{@uuid}")
12
+ broadcast_to(@uuid, loco: { uuid: @uuid })
13
+ signed_in_resources.each { |resource| WsConnectionManager.new(resource).add(@uuid) }
12
14
  end
13
15
 
14
16
  def unsubscribed
15
- loco_permissions.each do |resource|
16
- next if resource.nil? || resource.is_a?(String)
17
-
18
- UuidJob.perform_later serialize_resource(resource), @uuid, 'del'
17
+ PermissionsPresenter.signed_in(loco_permissions, except: :uuid).each do |resource|
18
+ WsConnectionManager.new(resource).del(@uuid)
19
19
  end
20
20
  end
21
21
 
22
- def receive(data)
23
- update_connections if data['loco'] && data['loco']['connection_check']
24
- NotificationCenter.new.received_message permissions, data
25
- end
26
-
27
- protected
28
-
29
- def stream_for_resources
30
- loco_permissions.compact.each do |resource|
31
- if resource.is_a? String
32
- @uuid = resource
33
- stream_for_resource resource
34
- SenderJob.perform_later @uuid, loco: { uuid: @uuid }
35
- else
36
- UuidJob.perform_later serialize_resource(resource), @uuid, 'add'
37
- stream_for_resource resource
38
- end
22
+ def receive(payload)
23
+ if payload.dig('loco', 'pong')
24
+ update_connections
25
+ broadcast_to(@uuid, loco: { ping: true })
39
26
  end
40
- end
27
+ return if payload.keys == ['loco']
41
28
 
42
- def stream_for_resource(resource)
43
- identifier = WsConnectionManager.new(resource).identifier
44
- stream_from "loco:notification_center:#{identifier}"
29
+ indexed_permissions = PermissionsPresenter.indexed(loco_permissions)
30
+ NotificationCenter.new.received_message(indexed_permissions, payload)
45
31
  end
46
32
 
47
- def permissions
48
- loco_permissions.compact.index_by do |o|
49
- o.class.name.downcase.to_sym
50
- end
51
- end
33
+ protected
52
34
 
53
35
  def update_connections
54
- permissions.each do |key, val|
55
- next if key == :string
56
-
57
- UuidJob.perform_later serialize_resource(val), @uuid, 'update'
36
+ PermissionsPresenter.indexed(loco_permissions, except: :uuid).each do |_, resource|
37
+ WsConnectionManager.new(resource).update(@uuid)
58
38
  end
59
39
  end
60
-
61
- def serialize_resource(resource)
62
- { 'class' => resource.class.name, 'id' => resource.id }
63
- end
64
40
  end
65
41
  end
@@ -21,41 +21,16 @@ module Loco
21
21
  render json: [[], Time.current.iso8601(6)]
22
22
  return
23
23
  end
24
- fetcher = Notification::Fetcher.new notif_fetcher_args
25
- render json: [
26
- fetcher.formatted_notifications,
27
- fetcher.next_sync_time.iso8601(6)
28
- ]
29
- end
30
-
31
- def notif_fetcher_args
32
- {
33
- synced_at: params[:synced_at],
34
- permissions: permissions,
35
- recipient_token: params[:token]
36
- }
24
+ fetcher = Notification::Fetcher.new({ synced_at: params[:synced_at],
25
+ permissions: permissions,
26
+ recipient_token: params[:token] })
27
+ render json: [fetcher.formatted_notifications, fetcher.next_sync_time.iso8601(6)]
37
28
  end
38
29
 
39
30
  def permissions
40
- return [] unless defined? loco_permissions
41
- return loco_permissions if params[:uuid].blank?
42
-
43
- process_loco_permissions
44
- end
31
+ return [] unless defined?(loco_permissions)
45
32
 
46
- def process_loco_permissions
47
- resources_to_del = []
48
- resources_to_add = []
49
- loco_permissions.each do |resource|
50
- next if resource.nil?
51
-
52
- ws_conn_manager = WsConnectionManager.new resource
53
- next unless ws_conn_manager.connected?(params[:uuid])
54
-
55
- resources_to_del << resource
56
- resources_to_add << resource.class
57
- end
58
- loco_permissions - resources_to_del + resources_to_add.uniq
33
+ loco_permissions
59
34
  end
60
35
  end
61
36
  end
@@ -5,7 +5,7 @@ module Loco
5
5
  queue_as :loco
6
6
 
7
7
  def perform(recipient, data)
8
- Sender.new(recipient, data).emit
8
+ Sender.(recipient, data)
9
9
  end
10
10
  end
11
11
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  module Loco
4
4
  class Notification < ApplicationRecord
5
+ FOR_OBJ_SQL_TMPL = 'recipient_class = ? AND recipient_id = ?'
6
+ FOR_CLASS_SQL_TMPL = 'recipient_class = ? AND recipient_id IS NULL'
7
+
5
8
  attr_reader :obj
6
9
 
7
10
  serialize :data, JSON
@@ -31,9 +34,9 @@ module Loco
31
34
  return if val.nil?
32
35
  return if val == :all
33
36
 
34
- if val.is_a? String
37
+ if val.is_a?(String)
35
38
  self.recipient_token = val
36
- elsif val.instance_of? Class
39
+ elsif val.instance_of?(Class)
37
40
  self.recipient_class = val.to_s
38
41
  else
39
42
  self.recipient_class = val.class.name
@@ -41,17 +44,14 @@ module Loco
41
44
  end
42
45
  end
43
46
 
44
- def recipient(opts = {})
45
- return recipient_token if recipient_token
46
- return unless regular_recipient?
47
- return class_recipient unless recipient_id
48
-
49
- obj_recipient opts[:shallow]
50
- end
51
-
52
- def prepare
53
- set_event
54
- set_data
47
+ def recipient(shallow: false)
48
+ if !recipient_token.nil?
49
+ recipient_token
50
+ elsif regular_recipient?
51
+ init_recipient(shallow)
52
+ elsif !recipient_class.nil?
53
+ recipient_class.constantize
54
+ end
55
55
  end
56
56
 
57
57
  def compact
@@ -61,41 +61,42 @@ module Loco
61
61
  private
62
62
 
63
63
  def regular_recipient?
64
- recipient_class && recipient_id
64
+ !recipient_class.nil? && !recipient_id.nil?
65
65
  end
66
66
 
67
- def class_recipient
68
- recipient_class.constantize
69
- end
70
-
71
- def obj_recipient(shallow = false)
67
+ def init_recipient(shallow)
72
68
  if shallow
73
- recipient_class.constantize.new id: recipient_id
69
+ recipient_class.constantize.new(id: recipient_id)
74
70
  else
75
- recipient_class.constantize.find recipient_id
71
+ recipient_class.constantize.find(recipient_id)
76
72
  end
77
73
  end
78
74
 
75
+ def prepare
76
+ set_event
77
+ set_data
78
+ end
79
+
79
80
  def set_event
80
81
  return if event.present?
81
- return if obj.instance_of? Class
82
+ return if obj.instance_of?(Class)
82
83
 
83
- if obj.new_record?
84
- self.event = 'creating'
85
- else
86
- set_event_for_persisted_obj
87
- end
84
+ self.event = if obj.new_record?
85
+ 'creating'
86
+ else
87
+ event_for_persisted_obj
88
+ end
88
89
  end
89
90
 
90
- def set_event_for_persisted_obj
91
- self.event = obj.created_at == obj.updated_at ? 'created' : 'updated'
91
+ def event_for_persisted_obj
92
+ obj.created_at == obj.updated_at ? 'created' : 'updated'
92
93
  end
93
94
 
94
95
  def set_data
95
96
  self.data ||= {}
96
97
  return if obj.nil?
97
98
 
98
- self.data.merge!(id: obj.id)
99
+ self.data = data.merge(id: obj.id)
99
100
  end
100
101
  end
101
102
  end
@@ -5,17 +5,12 @@ module Loco
5
5
  class Fetcher
6
6
  attr_accessor :max_size
7
7
 
8
- def initialize(
9
- synced_at:,
10
- permissions: [],
11
- recipient_token: nil,
12
- max_size: nil
13
- )
14
- @synced_at = synced_at
15
- @permissions = permissions
16
- @recipient_token = recipient_token
8
+ def initialize(opts)
9
+ @synced_at = opts[:synced_at]
10
+ @permissions = (opts[:permissions] || []).compact
11
+ @recipient_token = opts[:recipient_token]
17
12
  @notifications = nil
18
- @max_size = max_size || Loco::Config.notifications_size
13
+ @max_size = opts[:max_size] || Loco::Config.notifications_size
19
14
  end
20
15
 
21
16
  def formatted_notifications
@@ -56,17 +51,11 @@ module Loco
56
51
  recipient_class: nil,
57
52
  recipient_id: nil,
58
53
  recipient_token: nil
59
- ).first max_size
54
+ ).first(max_size)
60
55
  end
61
56
 
62
57
  def notifications_behind_permissions
63
- notifications = []
64
- @permissions.each do |resource|
65
- next unless resource
66
-
67
- notifications += notification_for_resource resource
68
- end
69
- notifications
58
+ @permissions.inject([]) { |arr, resource| arr + notification_for_resource(resource) }
70
59
  end
71
60
 
72
61
  def notifications_behind_token
@@ -74,15 +63,13 @@ module Loco
74
63
  end
75
64
 
76
65
  def notification_for_resource(resource)
77
- if resource.instance_of? Class
78
- sql = 'recipient_class = ? AND recipient_id IS NULL'
79
- return default_scope.where(sql, resource.to_s).first max_size
66
+ if resource.instance_of?(Class)
67
+ return default_scope.where(Notification::FOR_CLASS_SQL_TMPL, resource.to_s)
68
+ .first(max_size)
80
69
  end
81
70
  klass = resource.class.name
82
- cond1 = '(recipient_class = ? AND recipient_id = ?)'
83
- cond2 = '(recipient_class = ? AND recipient_id IS NULL)'
84
- sql = "#{cond1} OR #{cond2}"
85
- default_scope.where(sql, klass, resource.id, klass).first max_size
71
+ sql = "(#{Notification::FOR_OBJ_SQL_TMPL}) OR (#{Notification::FOR_CLASS_SQL_TMPL})"
72
+ default_scope.where(sql, klass, resource.id, klass).first(max_size)
86
73
  end
87
74
  end
88
75
  end
@@ -1,6 +1,7 @@
1
1
  identified_by :loco_permissions
2
2
 
3
3
  def connect
4
+ reject_unauthorized_connection unless current_user || current_admin
4
5
  # loco_permissions should be the same as in application_controller.rb
5
6
  # + SecureRandom.uuid is mandatory at 1st position
6
7
  self.loco_permissions = [SecureRandom.uuid, current_user, current_admin]
@@ -2,99 +2,51 @@
2
2
 
3
3
  module Loco
4
4
  class Broadcaster
5
- attr_reader :obj, :event, :recipients, :data, :notifications
6
-
7
- def initialize(obj, event = nil, opts = {})
8
- recipient_key = opts[:for] ? :for : :to
9
- @obj = obj
10
- @event = event
11
- @recipients = opts[recipient_key] ? [*opts[recipient_key]] : [nil]
12
- @data = opts[:data]
13
- @notifications = []
14
- @sent_via_ws = 0
15
- @conn_res_manager = WsConnectedResourcesManager.new @recipients.compact
16
- end
17
-
18
- def emit
19
- init_notifications if notifications.empty?
20
- send_notifications
21
- if notify_about_xhr_notifications?
22
- notify_about_xhr_notifications
23
- else
24
- set_sync_time_via_ws
5
+ class << self
6
+ def call(obj, event, recipients: nil, payload: nil)
7
+ payload ||= {}
8
+ payload[:loco] = { idempotency_key: SecureRandom.hex }
9
+ send_notifications(obj, event, process_recipients(recipients), payload)
25
10
  end
26
- end
27
11
 
28
- private
12
+ private
29
13
 
30
- def init_notifications
31
- recipients.each do |recipient|
32
- @notifications << Notification.new(
33
- obj: obj,
34
- event: event,
35
- recipient: recipient,
36
- data: data
37
- )
14
+ def process_recipients(recipients)
15
+ recipients = [:all] if recipients.nil?
16
+ recipients = [recipients] unless recipients.is_a?(Array)
17
+ recipients = recipients.map { |e| e.nil? ? :all : e }
18
+ recipients = [:all] if recipients.include?(:all)
19
+ recipients
38
20
  end
39
- end
40
-
41
- def send_notifications
42
- notifications.each do |notification|
43
- notification.save!
44
- next if notification.recipient_id.nil?
45
21
 
46
- shallow_recipient = notification.recipient shallow: true
47
- next unless @conn_res_manager.connected? shallow_recipient
48
-
49
- send_via_ws notification
50
- end
51
- end
52
-
53
- def send_via_ws(notification)
54
- recipient = notification.recipient shallow: true
55
- data = { loco: { notification: notification.compact } }
56
- SenderJob.perform_later recipient, data
57
- @sent_via_ws += 1
58
- end
59
-
60
- def notify_about_xhr_notifications?
61
- @sent_via_ws < notifications.size
62
- end
63
-
64
- def notify_about_xhr_notifications
65
- uuids = []
66
- fetch_identifiers.each do |ident|
67
- Loco::WsConnectionManager.new(ident).connected_uuids.each do |uuid|
68
- next if uuids.include? uuid
69
-
70
- uuids << uuid
71
- SenderJob.perform_later uuid, loco: { xhr_notifications: true }
22
+ def send_notifications(obj, event, recipients, payload)
23
+ recipients.each do |recipient|
24
+ notification = Notification.create!(
25
+ obj: obj,
26
+ event: event,
27
+ recipient: recipient,
28
+ data: payload
29
+ )
30
+ sync_time = notification.created_at.iso8601(6)
31
+ send_notification(keify_recipient(recipient), notification, sync_time)
72
32
  end
73
33
  end
74
- end
75
-
76
- def set_sync_time_via_ws
77
- sync_time = notifications.last.created_at.iso8601(6)
78
- notifications.each do |notification|
79
- recipient = notification.recipient shallow: true
80
- SenderJob.perform_later recipient, loco: { sync_time: sync_time }
81
- end
82
- end
83
34
 
84
- def notifications_recipients
85
- notifications.map { |n| n.recipient shallow: true }.map do |o|
86
- o.instance_of?(Class) ? o.to_s.downcase : nil
35
+ def keify_recipient(recipient)
36
+ case recipient
37
+ when String then { 'token' => recipient }
38
+ when Class then { 'class' => recipient.name }
39
+ else recipient
40
+ end
87
41
  end
88
- end
89
42
 
90
- def fetch_identifiers
91
- recipients = notifications_recipients
92
- uniq_recipients = recipients.compact.uniq
93
- Loco::WsConnectedResourcesManager.identifiers.find_all do |str|
94
- if recipients.include? nil
95
- true
43
+ def send_notification(recipient, notification, sync_time)
44
+ if notification.recipient_id
45
+ Sender.(recipient, loco: { notification: notification.compact })
46
+ Sender.(recipient, loco: { sync_time: sync_time })
96
47
  else
97
- uniq_recipients.include? str.split(':').first
48
+ SenderJob.perform_later(recipient, loco: { notification: notification.compact })
49
+ SenderJob.perform_later(recipient, loco: { sync_time: sync_time })
98
50
  end
99
51
  end
100
52
  end
data/lib/loco/emitter.rb CHANGED
@@ -3,23 +3,28 @@
3
3
  module Loco
4
4
  module Emitter
5
5
  def emit(obj, event = nil, opts = {})
6
- Broadcaster.new(obj, event, opts).emit
6
+ Broadcaster.(
7
+ obj,
8
+ event,
9
+ payload: opts[:payload] || opts[:data],
10
+ recipients: opts[opts[:for] ? :for : :to]
11
+ )
7
12
  end
8
13
 
9
- def emit_to(recipient, data)
10
- Sender.new(recipient, data).emit
14
+ def emit_to(recipient_s, data)
15
+ Sender.(recipient_s, data)
11
16
  end
12
17
 
13
18
  def add_hub(name, members = [])
14
- Hub.new(name, members).save
19
+ Hub.set(name, members)
15
20
  end
16
21
 
17
22
  def get_hub(name)
18
- Hub.get name
23
+ Hub.get(name)
19
24
  end
20
25
 
21
26
  def del_hub(name)
22
- hub = Hub.get name
27
+ hub = Hub.get(name)
23
28
  return false if hub.nil?
24
29
 
25
30
  hub.destroy
data/lib/loco/hub.rb CHANGED
@@ -2,71 +2,64 @@
2
2
 
3
3
  module Loco
4
4
  class Hub
5
- PREFIX = 'loco:hub:'
6
-
7
- attr_reader :raw_members
8
-
9
- def initialize(name, members = [])
10
- @name = "#{PREFIX}#{name}"
11
- @raw_members = members.map { |m| serialize m }
12
- end
5
+ PREFIX = 'hub:'
13
6
 
14
7
  class << self
8
+ def set(name, members)
9
+ new(name, members)
10
+ end
11
+
15
12
  def get(name)
16
- hub = WsConnectionStorage.current.get "#{PREFIX}#{name}"
17
- return nil if hub.blank?
13
+ return nil if WsConnectionStorage.current.type("s:#{full_name(name)}") != 'set'
18
14
 
19
- new name, JSON.parse(hub)
15
+ new(name)
20
16
  end
17
+
18
+ def full_name(val)
19
+ "#{PREFIX}#{val}"
20
+ end
21
+ end
22
+
23
+ def initialize(name, members = [])
24
+ @name = self.class.full_name(name)
25
+ members.map { |member| add_member(member) }
21
26
  end
22
27
 
23
28
  def name
24
- @name.split(PREFIX).last
29
+ full_name.split(PREFIX).last
25
30
  end
26
31
 
27
- def add_member(member)
28
- serialized = serialize member
29
- return raw_members if raw_members.include? serialized
32
+ def full_name
33
+ @name
34
+ end
30
35
 
31
- raw_members << serialized
32
- save
33
- raw_members
36
+ def add_member(member)
37
+ WsConnectionStorage.current.add(@name, WsConnectionIdentifier.(member))
34
38
  end
35
39
 
36
40
  def del_member(member)
37
- serialized = serialize member
38
- return nil unless raw_members.include? serialized
39
-
40
- raw_members.delete serialized
41
- save
42
- serialized
41
+ WsConnectionStorage.current.rem(@name, WsConnectionIdentifier.(member))
43
42
  end
44
43
 
45
44
  def destroy
46
- WsConnectionStorage.current.del @name
47
- true
45
+ WsConnectionStorage.current.members(@name).each do |member|
46
+ WsConnectionStorage.current.rem(@name, member)
47
+ end
48
48
  end
49
49
 
50
- def save
51
- WsConnectionStorage.current.set @name, raw_members.to_json
52
- self
50
+ def include?(resource)
51
+ WsConnectionStorage.current.member?(@name, WsConnectionIdentifier.(resource))
53
52
  end
54
53
 
55
- def include?(resource)
56
- raw_members.include? serialize(resource)
54
+ def raw_members
55
+ WsConnectionStorage.current.members(@name)
57
56
  end
58
57
 
59
58
  def members
60
- raw_members.map do |str|
61
- klass, id = str.split ':'
62
- klass.classify.constantize.find_by id: id
59
+ raw_members.map do |serialized|
60
+ klass, id = serialized.split(':')
61
+ klass.classify.constantize.find_by(id: id)
63
62
  end
64
63
  end
65
-
66
- private
67
-
68
- def serialize(member)
69
- WsConnectionManager.new(member).identifier
70
- end
71
64
  end
72
65
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loco
4
+ module PermissionsPresenter
5
+ module_function
6
+
7
+ def indexed(loco_permissions, opts = {})
8
+ h = signed_in(loco_permissions).index_by do |o|
9
+ o.class.name.downcase.to_sym
10
+ end
11
+ if opts[:except] == :uuid
12
+ h.reject { |k, _| k == :string }
13
+ else
14
+ h
15
+ end
16
+ end
17
+
18
+ def signed_in(loco_permissions, opts = {})
19
+ arr = loco_permissions.compact
20
+ if opts[:except] == :uuid
21
+ arr.reject { |e| e.is_a?(String) }
22
+ else
23
+ arr
24
+ end
25
+ end
26
+ end
27
+ end
File without changes