loco-rails 4.1.1 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6efc2ac92aece905588e66392e372f7fba0414d542b45a659cf6637e25932b4
4
- data.tar.gz: e53dbee6931104ceb192dcb677b2a927528629c620fef5a68c30afb53ff5397a
3
+ metadata.gz: 7b045cea0f5c6f123854c0a40b8953e869b18d0f97b31f332f96fd9b5d8c9d31
4
+ data.tar.gz: 9ee0e7159ede72b756f29a25adbd1982363f27f26f803f1d3f4d9c8cfbae2a3a
5
5
  SHA512:
6
- metadata.gz: a196c6b188cd04c205862ee75d1dbe35ec14607a2a49ffa659013c116789d406faaf6521861dad623cbe6ac5356155b35a91fa73967f999142007b553e3a9a77
7
- data.tar.gz: '0692ad504f2455e620352adefdf38e9327c9212601578ed9dde443c23d40a0addcfb7431950c8ad04cf47c4c82f18a472363209fc7e64c3a4c5e860933e08398'
6
+ metadata.gz: 236a3ddae5fcc7fb45f17bbfe8f0e48a8a9ac152cb83a4b53a4b9b5e4416a078ec7e4aaa65d5a31ac69a422574e63857787dac57f240f8c0397bfc42be25dfc4
7
+ data.tar.gz: 3857fd4ee3754ab803707ddeb86bc1cbaa7aead76f389202b43146ac11a76a71600a5e238a05dc1b80e6b67f7495374b20ed63ec45d653389b95126d22cc42b5
@@ -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,5 +1,3 @@
1
- include Loco::Emitter
2
-
3
1
  def loco_permissions
4
2
  # specify an array of method names which you use to determine
5
3
  # if given resource is signed-in
@@ -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,8 +2,6 @@
2
2
 
3
3
  module Loco
4
4
  class NotificationCenter
5
- include Emitter
6
-
7
5
  def received_message(permissions, data)
8
6
  # handle messages here
9
7
  end
@@ -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
@@ -1,28 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # TODO: remove in v7
3
4
  module Loco
4
5
  module Emitter
5
6
  def emit(obj, event = nil, opts = {})
6
- Broadcaster.new(obj, event, opts).emit
7
+ Loco.emit(obj, event, opts)
7
8
  end
8
9
 
9
- def emit_to(recipient, data)
10
- Sender.new(recipient, data).emit
10
+ def emit_to(recipient_s, data)
11
+ Loco.emit_to(recipient_s, data)
11
12
  end
12
13
 
13
14
  def add_hub(name, members = [])
14
- Hub.new(name, members).save
15
+ Loco.add_hub(name, members)
15
16
  end
16
17
 
17
18
  def get_hub(name)
18
- Hub.get name
19
+ Loco.get_hub(name)
19
20
  end
20
21
 
21
22
  def del_hub(name)
22
- hub = Hub.get name
23
- return false if hub.nil?
24
-
25
- hub.destroy
23
+ Loco.del_hub(name)
26
24
  end
27
25
  end
28
26
  end
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