loco-rails 3.0.6 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/channels/loco/notification_center_channel.rb +32 -28
- data/app/controllers/loco/notification_center_controller.rb +36 -33
- data/app/jobs/loco/sender_job.rb +1 -1
- data/app/jobs/loco/uuid_job.rb +25 -22
- data/app/models/loco/notification.rb +35 -31
- data/app/services/loco/notification/fetcher.rb +44 -42
- data/lib/generators/loco/file_injector/file_injector_generator.rb +3 -12
- data/lib/generators/loco/initializer/templates/initializer.rb +5 -5
- data/lib/generators/loco/notification_center/USAGE +1 -1
- data/lib/generators/loco/notification_center/templates/services/loco/notification_center.rb +2 -2
- data/lib/loco-rails.rb +8 -0
- data/lib/loco/broadcaster.rb +56 -66
- data/lib/loco/config.rb +9 -8
- data/lib/loco/emitter.rb +6 -5
- data/lib/loco/hub.rb +11 -8
- data/lib/loco/sender.rb +26 -18
- data/lib/loco/version.rb +1 -1
- data/lib/loco/ws_connected_resources_manager.rb +14 -9
- data/lib/loco/ws_connection_manager.rb +46 -42
- data/lib/loco/ws_connection_storage.rb +6 -6
- metadata +47 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14ddadb549243851cfae8f6260dbe08b6813392116f03b83d4618615598ec2d3
|
4
|
+
data.tar.gz: c5cdfc9d981ce63a5e8237cb794d7eb2a9d8435941070bba84f65f188e08e215
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0921bb8ffff692bede6999ae0dc2de20d1822c0ca3b5a8bac0aabcd50909a12369a6987da6b309356e4306dfd3891aea85adff8c418cce1eacabc4bd73348509'
|
7
|
+
data.tar.gz: f8d9d259ae99c421bcbdbf03df5fa1878ae6dd590eb835d6c432a6c6e91b103df5d5efbcdfe9daff6ae1d4fbb2bab1c193d3416b1e6213b7e134842d7f2dea48
|
@@ -4,58 +4,62 @@ module Loco
|
|
4
4
|
class NotificationCenterChannel < ApplicationCable::Channel
|
5
5
|
def subscribed
|
6
6
|
return unless loco_permissions.is_a?(Array)
|
7
|
+
|
7
8
|
stream_for_resources
|
8
9
|
return if loco_permissions.compact.size > 1
|
10
|
+
|
9
11
|
SenderJob.perform_later @uuid, loco: { start_ajax_polling: true }
|
10
12
|
end
|
11
13
|
|
12
14
|
def unsubscribed
|
13
15
|
loco_permissions.each do |resource|
|
14
16
|
next if resource.nil? || resource.is_a?(String)
|
17
|
+
|
15
18
|
UuidJob.perform_later serialize_resource(resource), @uuid, 'del'
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
19
|
-
def receive
|
22
|
+
def receive(data)
|
20
23
|
update_connections if data['loco'] && data['loco']['connection_check']
|
21
|
-
NotificationCenter.new.
|
24
|
+
NotificationCenter.new.received_message permissions, data
|
22
25
|
end
|
23
26
|
|
24
27
|
protected
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
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
|
36
38
|
end
|
37
39
|
end
|
40
|
+
end
|
38
41
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
def stream_for_resource(resource)
|
43
|
+
identifier = WsConnectionManager.new(resource).identifier
|
44
|
+
stream_from "loco:notification_center:#{identifier}"
|
45
|
+
end
|
43
46
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end.to_h
|
47
|
+
def permissions
|
48
|
+
loco_permissions.compact.index_by do |o|
|
49
|
+
o.class.name.downcase.to_sym
|
48
50
|
end
|
51
|
+
end
|
49
52
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
UuidJob.perform_later serialize_resource(val), @uuid, 'update'
|
54
|
-
end
|
55
|
-
end
|
53
|
+
def update_connections
|
54
|
+
permissions.each do |key, val|
|
55
|
+
next if key == :string
|
56
56
|
|
57
|
-
|
58
|
-
{ 'class' => resource.class.name, 'id' => resource.id }
|
57
|
+
UuidJob.perform_later serialize_resource(val), @uuid, 'update'
|
59
58
|
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def serialize_resource(resource)
|
62
|
+
{ 'class' => resource.class.name, 'id' => resource.id }
|
63
|
+
end
|
60
64
|
end
|
61
65
|
end
|
@@ -16,43 +16,46 @@ module Loco
|
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
]
|
19
|
+
def fetch_notifications
|
20
|
+
if params[:synced_at].blank?
|
21
|
+
render json: [[], Time.current.iso8601(6)]
|
22
|
+
return
|
29
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
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
def notif_fetcher_args
|
32
|
+
{
|
33
|
+
synced_at: params[:synced_at],
|
34
|
+
permissions: permissions,
|
35
|
+
recipient_token: params[:token]
|
36
|
+
}
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
process_loco_permissions
|
43
|
-
end
|
39
|
+
def permissions
|
40
|
+
return [] unless defined? loco_permissions
|
41
|
+
return loco_permissions if params[:uuid].blank?
|
44
42
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
43
|
+
process_loco_permissions
|
44
|
+
end
|
45
|
+
|
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
|
56
57
|
end
|
58
|
+
loco_permissions - resources_to_del + resources_to_add.uniq
|
59
|
+
end
|
57
60
|
end
|
58
61
|
end
|
data/app/jobs/loco/sender_job.rb
CHANGED
data/app/jobs/loco/uuid_job.rb
CHANGED
@@ -4,9 +4,10 @@ module Loco
|
|
4
4
|
class UuidJob < ActiveJob::Base
|
5
5
|
queue_as :loco
|
6
6
|
|
7
|
-
def perform
|
7
|
+
def perform(serialized_resource, uuid, action)
|
8
8
|
ws_conn_manager = init_ws_conn_manager serialized_resource
|
9
9
|
return unless ws_conn_manager
|
10
|
+
|
10
11
|
case action
|
11
12
|
when 'add'
|
12
13
|
add ws_conn_manager, uuid
|
@@ -19,30 +20,32 @@ module Loco
|
|
19
20
|
|
20
21
|
protected
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def add(ws_conn_manager, uuid)
|
24
|
+
ws_conn_manager.add uuid
|
25
|
+
WsConnectedResourcesManager.add ws_conn_manager.identifier
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
WsConnectedResourcesManager.del ws_conn_manager.identifier
|
31
|
-
end
|
28
|
+
def del(ws_conn_manager, uuid)
|
29
|
+
ws_conn_manager.del uuid
|
30
|
+
return if ws_conn_manager.connected_uuids.any?
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
WsConnectedResourcesManager.add ws_conn_manager.identifier
|
36
|
-
end
|
32
|
+
WsConnectedResourcesManager.del ws_conn_manager.identifier
|
33
|
+
end
|
37
34
|
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
def update(ws_conn_manager, uuid)
|
36
|
+
ws_conn_manager.update uuid
|
37
|
+
WsConnectedResourcesManager.add ws_conn_manager.identifier
|
38
|
+
end
|
41
39
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
40
|
+
def deserialize_resource(hash)
|
41
|
+
hash['class'].constantize.find_by id: hash['id']
|
42
|
+
end
|
43
|
+
|
44
|
+
def init_ws_conn_manager(serialized_resource)
|
45
|
+
resource = deserialize_resource serialized_resource
|
46
|
+
return unless resource
|
47
|
+
|
48
|
+
WsConnectionManager.new resource
|
49
|
+
end
|
47
50
|
end
|
48
51
|
end
|
@@ -17,7 +17,7 @@ module Loco
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def obj=
|
20
|
+
def obj=(val)
|
21
21
|
if val.instance_of? Class
|
22
22
|
self.obj_class = val.to_s
|
23
23
|
else
|
@@ -27,9 +27,10 @@ module Loco
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def recipient=
|
30
|
+
def recipient=(val)
|
31
31
|
return if val.nil?
|
32
32
|
return if val == :all
|
33
|
+
|
33
34
|
if val.is_a? String
|
34
35
|
self.recipient_token = val
|
35
36
|
elsif val.instance_of? Class
|
@@ -40,10 +41,11 @@ module Loco
|
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
43
|
-
def recipient
|
44
|
+
def recipient(opts = {})
|
44
45
|
return recipient_token if recipient_token
|
45
46
|
return unless regular_recipient?
|
46
47
|
return class_recipient unless recipient_id
|
48
|
+
|
47
49
|
obj_recipient opts[:shallow]
|
48
50
|
end
|
49
51
|
|
@@ -58,40 +60,42 @@ module Loco
|
|
58
60
|
|
59
61
|
private
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
-
|
63
|
+
def regular_recipient?
|
64
|
+
recipient_class && recipient_id
|
65
|
+
end
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
def class_recipient
|
68
|
+
recipient_class.constantize
|
69
|
+
end
|
68
70
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
71
|
+
def obj_recipient(shallow = false)
|
72
|
+
if shallow
|
73
|
+
recipient_class.constantize.new id: recipient_id
|
74
|
+
else
|
75
|
+
recipient_class.constantize.find recipient_id
|
75
76
|
end
|
77
|
+
end
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
if obj.new_record?
|
81
|
-
self.event = 'creating'
|
82
|
-
else
|
83
|
-
set_event_for_persisted_obj
|
84
|
-
end
|
85
|
-
end
|
79
|
+
def set_event
|
80
|
+
return if event.present?
|
81
|
+
return if obj.instance_of? Class
|
86
82
|
|
87
|
-
|
88
|
-
self.event =
|
83
|
+
if obj.new_record?
|
84
|
+
self.event = 'creating'
|
85
|
+
else
|
86
|
+
set_event_for_persisted_obj
|
89
87
|
end
|
88
|
+
end
|
90
89
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
90
|
+
def set_event_for_persisted_obj
|
91
|
+
self.event = obj.created_at == obj.updated_at ? 'created' : 'updated'
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_data
|
95
|
+
self.data ||= {}
|
96
|
+
return if obj.nil?
|
97
|
+
|
98
|
+
self.data.merge!(id: obj.id)
|
99
|
+
end
|
96
100
|
end
|
97
101
|
end
|
@@ -32,56 +32,58 @@ module Loco
|
|
32
32
|
|
33
33
|
private
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
def sync_time
|
36
|
+
Time.zone.parse @synced_at
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
def default_scope
|
40
|
+
Notification.order('created_at ASC')
|
41
|
+
.where('created_at > ?', sync_time)
|
42
|
+
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
notifications = notifications_for_all
|
48
|
-
notifications += notifications_behind_permissions
|
49
|
-
notifications += notifications_behind_token if @recipient_token
|
50
|
-
@notifications = notifications.sort_by(&:created_at)[0, max_size]
|
51
|
-
end
|
44
|
+
# OPTIMIZE: one query
|
45
|
+
def notifications
|
46
|
+
return @notifications if @notifications
|
52
47
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
).first max_size
|
59
|
-
end
|
48
|
+
notifications = notifications_for_all
|
49
|
+
notifications += notifications_behind_permissions
|
50
|
+
notifications += notifications_behind_token if @recipient_token
|
51
|
+
@notifications = notifications.sort_by(&:created_at)[0, max_size]
|
52
|
+
end
|
60
53
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
54
|
+
def notifications_for_all
|
55
|
+
default_scope.where(
|
56
|
+
recipient_class: nil,
|
57
|
+
recipient_id: nil,
|
58
|
+
recipient_token: nil
|
59
|
+
).first max_size
|
60
|
+
end
|
69
61
|
|
70
|
-
|
71
|
-
|
62
|
+
def notifications_behind_permissions
|
63
|
+
notifications = []
|
64
|
+
@permissions.each do |resource|
|
65
|
+
next unless resource
|
66
|
+
|
67
|
+
notifications += notification_for_resource resource
|
72
68
|
end
|
69
|
+
notifications
|
70
|
+
end
|
73
71
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
sql = "#{cond1} OR #{cond2}"
|
83
|
-
default_scope.where(sql, klass, resource.id, klass).first max_size
|
72
|
+
def notifications_behind_token
|
73
|
+
default_scope.where(recipient_token: @recipient_token).first max_size
|
74
|
+
end
|
75
|
+
|
76
|
+
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
|
84
80
|
end
|
81
|
+
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
|
86
|
+
end
|
85
87
|
end
|
86
88
|
end
|
87
89
|
end
|
@@ -5,30 +5,21 @@ module Loco
|
|
5
5
|
source_root File.expand_path('templates', __dir__)
|
6
6
|
|
7
7
|
def routes
|
8
|
-
file_path = Rails.root.join 'config
|
8
|
+
file_path = Rails.root.join 'config/routes.rb'
|
9
9
|
line = %( mount Loco::Engine => '/notification-center'\n\n)
|
10
10
|
str = "Rails.application.routes.draw do\n"
|
11
11
|
inject_into_file file_path, line, after: str
|
12
12
|
end
|
13
13
|
|
14
14
|
def application_controller
|
15
|
-
file_path = Rails.root.join
|
16
|
-
'app',
|
17
|
-
'controllers',
|
18
|
-
'application_controller.rb'
|
19
|
-
)
|
15
|
+
file_path = Rails.root.join 'app/controllers/application_controller.rb'
|
20
16
|
data = File.read find_in_source_paths('application_controller.rb')
|
21
17
|
after_line = "class ApplicationController < ActionController::Base\n"
|
22
18
|
inject_into_file file_path, data, after: after_line
|
23
19
|
end
|
24
20
|
|
25
21
|
def connection
|
26
|
-
file_path = Rails.root.join
|
27
|
-
'app',
|
28
|
-
'channels',
|
29
|
-
'application_cable',
|
30
|
-
'connection.rb'
|
31
|
-
)
|
22
|
+
file_path = Rails.root.join 'app/channels/application_cable/connection.rb'
|
32
23
|
data = File.read find_in_source_paths('connection.rb')
|
33
24
|
inject_into_class file_path, 'Connection', data
|
34
25
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
Loco
|
4
|
-
silence_logger
|
5
|
-
notifications_size
|
6
|
-
app_name
|
7
|
-
|
3
|
+
Loco.configure do |c|
|
4
|
+
c.silence_logger = false # false by default
|
5
|
+
c.notifications_size = 100 # 100 by default
|
6
|
+
c.app_name = "loco_#{Rails.env}" # your app's name (required for namespacing)
|
7
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
Description:
|
2
|
-
Creates a notification center where you can receive
|
2
|
+
Creates a notification center where you can receive messages sent by JS clients through WebSockets.
|
3
3
|
|
4
4
|
Example:
|
5
5
|
rails generate loco:notification_center
|
data/lib/loco-rails.rb
CHANGED
data/lib/loco/broadcaster.rb
CHANGED
@@ -4,7 +4,7 @@ module Loco
|
|
4
4
|
class Broadcaster
|
5
5
|
attr_reader :obj, :event, :recipients, :data, :notifications
|
6
6
|
|
7
|
-
def initialize
|
7
|
+
def initialize(obj, event = nil, opts = {})
|
8
8
|
recipient_key = opts[:for] ? :for : :to
|
9
9
|
@obj = obj
|
10
10
|
@event = event
|
@@ -15,15 +15,6 @@ module Loco
|
|
15
15
|
@conn_res_manager = WsConnectedResourcesManager.new @recipients.compact
|
16
16
|
end
|
17
17
|
|
18
|
-
def signals
|
19
|
-
notifications
|
20
|
-
end
|
21
|
-
|
22
|
-
def prepare
|
23
|
-
init_notifications if notifications.empty?
|
24
|
-
prepare_notifications
|
25
|
-
end
|
26
|
-
|
27
18
|
def emit
|
28
19
|
init_notifications if notifications.empty?
|
29
20
|
send_notifications
|
@@ -36,77 +27,76 @@ module Loco
|
|
36
27
|
|
37
28
|
private
|
38
29
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
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
|
+
)
|
48
38
|
end
|
39
|
+
end
|
49
40
|
|
50
|
-
|
51
|
-
|
52
|
-
|
41
|
+
def send_notifications
|
42
|
+
notifications.each do |notification|
|
43
|
+
notification.save!
|
44
|
+
next if notification.recipient_id.nil?
|
53
45
|
|
54
|
-
|
55
|
-
|
56
|
-
notification.save!
|
57
|
-
next if notification.recipient_id.nil?
|
58
|
-
shallow_recipient = notification.recipient shallow: true
|
59
|
-
next unless @conn_res_manager.connected? shallow_recipient
|
60
|
-
send_via_ws notification
|
61
|
-
end
|
62
|
-
end
|
46
|
+
shallow_recipient = notification.recipient shallow: true
|
47
|
+
next unless @conn_res_manager.connected? shallow_recipient
|
63
48
|
|
64
|
-
|
65
|
-
recipient = notification.recipient shallow: true
|
66
|
-
data = { loco: { notification: notification.compact } }
|
67
|
-
SenderJob.perform_later recipient, data
|
68
|
-
@sent_via_ws += 1
|
49
|
+
send_via_ws notification
|
69
50
|
end
|
51
|
+
end
|
70
52
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
74
69
|
|
75
|
-
|
76
|
-
|
77
|
-
fetch_identifiers.each do |ident|
|
78
|
-
Loco::WsConnectionManager.new(ident).connected_uuids.each do |uuid|
|
79
|
-
next if uuids.include? uuid
|
80
|
-
uuids << uuid
|
81
|
-
SenderJob.perform_later uuid, loco: { xhr_notifications: true }
|
82
|
-
end
|
70
|
+
uuids << uuid
|
71
|
+
SenderJob.perform_later uuid, loco: { xhr_notifications: true }
|
83
72
|
end
|
84
73
|
end
|
74
|
+
end
|
85
75
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
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 }
|
92
81
|
end
|
82
|
+
end
|
93
83
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
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
|
98
87
|
end
|
88
|
+
end
|
99
89
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
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
|
96
|
+
else
|
97
|
+
uniq_recipients.include? str.split(':').first
|
109
98
|
end
|
110
99
|
end
|
100
|
+
end
|
111
101
|
end
|
112
102
|
end
|
data/lib/loco/config.rb
CHANGED
@@ -2,28 +2,29 @@
|
|
2
2
|
|
3
3
|
module Loco
|
4
4
|
class Config
|
5
|
+
CONFIGURATION = Struct.new :silence_logger, :notifications_size, :app_name, :redis_instance
|
6
|
+
|
5
7
|
cattr_accessor(:silence_logger) { false }
|
6
8
|
cattr_accessor(:notifications_size) { 100 }
|
7
9
|
cattr_accessor(:app_name) { 'loco' }
|
8
10
|
cattr_accessor(:redis_instance) { nil }
|
9
11
|
|
10
|
-
def self.configure
|
11
|
-
self.silence_logger =
|
12
|
-
if
|
13
|
-
|
14
|
-
|
15
|
-
self.app_name = opts[:app_name] if opts[:app_name]
|
16
|
-
configure_redis opts[:redis_instance]
|
12
|
+
def self.configure(config)
|
13
|
+
self.silence_logger = config.silence_logger if config.silence_logger
|
14
|
+
self.notifications_size = config.notifications_size if config.notifications_size
|
15
|
+
self.app_name = config.app_name if config.app_name
|
16
|
+
configure_redis config.redis_instance
|
17
17
|
ensure
|
18
18
|
true
|
19
19
|
end
|
20
20
|
|
21
|
-
def self.configure_redis
|
21
|
+
def self.configure_redis(redis_instance)
|
22
22
|
if redis_instance
|
23
23
|
self.redis_instance = redis_instance
|
24
24
|
return
|
25
25
|
end
|
26
26
|
return unless defined? Redis
|
27
|
+
|
27
28
|
Redis.current.get 'random_redis_key'
|
28
29
|
self.redis_instance = Redis.current
|
29
30
|
rescue Redis::CannotConnectError
|
data/lib/loco/emitter.rb
CHANGED
@@ -2,25 +2,26 @@
|
|
2
2
|
|
3
3
|
module Loco
|
4
4
|
module Emitter
|
5
|
-
def emit
|
5
|
+
def emit(obj, event = nil, opts = {})
|
6
6
|
Broadcaster.new(obj, event, opts).emit
|
7
7
|
end
|
8
8
|
|
9
|
-
def emit_to
|
9
|
+
def emit_to(recipient, data)
|
10
10
|
Sender.new(recipient, data).emit
|
11
11
|
end
|
12
12
|
|
13
|
-
def add_hub
|
13
|
+
def add_hub(name, members = [])
|
14
14
|
Hub.new(name, members).save
|
15
15
|
end
|
16
16
|
|
17
|
-
def get_hub
|
17
|
+
def get_hub(name)
|
18
18
|
Hub.get name
|
19
19
|
end
|
20
20
|
|
21
|
-
def del_hub
|
21
|
+
def del_hub(name)
|
22
22
|
hub = Hub.get name
|
23
23
|
return false if hub.nil?
|
24
|
+
|
24
25
|
hub.destroy
|
25
26
|
end
|
26
27
|
end
|
data/lib/loco/hub.rb
CHANGED
@@ -6,15 +6,16 @@ module Loco
|
|
6
6
|
|
7
7
|
attr_reader :raw_members
|
8
8
|
|
9
|
-
def initialize
|
9
|
+
def initialize(name, members = [])
|
10
10
|
@name = "#{PREFIX}#{name}"
|
11
11
|
@raw_members = members.map { |m| serialize m }
|
12
12
|
end
|
13
13
|
|
14
14
|
class << self
|
15
|
-
def get
|
15
|
+
def get(name)
|
16
16
|
hub = WsConnectionStorage.current.get "#{PREFIX}#{name}"
|
17
17
|
return nil if hub.blank?
|
18
|
+
|
18
19
|
new name, JSON.parse(hub)
|
19
20
|
end
|
20
21
|
end
|
@@ -23,17 +24,19 @@ module Loco
|
|
23
24
|
@name.split(PREFIX).last
|
24
25
|
end
|
25
26
|
|
26
|
-
def add_member
|
27
|
+
def add_member(member)
|
27
28
|
serialized = serialize member
|
28
29
|
return raw_members if raw_members.include? serialized
|
30
|
+
|
29
31
|
raw_members << serialized
|
30
32
|
save
|
31
33
|
raw_members
|
32
34
|
end
|
33
35
|
|
34
|
-
def del_member
|
36
|
+
def del_member(member)
|
35
37
|
serialized = serialize member
|
36
38
|
return nil unless raw_members.include? serialized
|
39
|
+
|
37
40
|
raw_members.delete serialized
|
38
41
|
save
|
39
42
|
serialized
|
@@ -49,7 +52,7 @@ module Loco
|
|
49
52
|
self
|
50
53
|
end
|
51
54
|
|
52
|
-
def include?
|
55
|
+
def include?(resource)
|
53
56
|
raw_members.include? serialize(resource)
|
54
57
|
end
|
55
58
|
|
@@ -62,8 +65,8 @@ module Loco
|
|
62
65
|
|
63
66
|
private
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
+
def serialize(member)
|
69
|
+
WsConnectionManager.new(member).identifier
|
70
|
+
end
|
68
71
|
end
|
69
72
|
end
|
data/lib/loco/sender.rb
CHANGED
@@ -2,35 +2,43 @@
|
|
2
2
|
|
3
3
|
module Loco
|
4
4
|
class Sender
|
5
|
-
def initialize
|
5
|
+
def initialize(recipient, data = {})
|
6
6
|
@recipients = [*recipient]
|
7
7
|
@data = data
|
8
8
|
end
|
9
9
|
|
10
10
|
def emit
|
11
11
|
uuids.each do |uuid|
|
12
|
-
NotificationCenterChannel.broadcast_to
|
12
|
+
NotificationCenterChannel.broadcast_to(uuid, payload)
|
13
13
|
end
|
14
|
+
@data[:loco][:idempotency_key]
|
14
15
|
end
|
15
16
|
|
16
17
|
private
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
19
|
+
def uuids
|
20
|
+
@recipients.map do |r|
|
21
|
+
if r.is_a? String
|
22
|
+
r
|
23
|
+
elsif r.is_a? Hub
|
24
|
+
recipients_from_hub r
|
25
|
+
else
|
26
|
+
WsConnectionManager.new(r).connected_uuids
|
27
|
+
end
|
28
|
+
end.flatten.uniq
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def recipients_from_hub(hub)
|
32
|
+
hub.raw_members.map do |m|
|
33
|
+
WsConnectionManager.new(m).connected_uuids
|
34
|
+
end.flatten.uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
def payload
|
38
|
+
@data[:loco] ||= {}
|
39
|
+
@data[:loco][:idempotency_key] ||= @data[:idempotency_key] || SecureRandom.hex
|
40
|
+
@data.delete(:idempotency_key)
|
41
|
+
@data
|
42
|
+
end
|
35
43
|
end
|
36
44
|
end
|
data/lib/loco/version.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Loco
|
4
4
|
class WsConnectedResourcesManager
|
5
|
-
def initialize
|
5
|
+
def initialize(resources)
|
6
6
|
@resources = resources
|
7
7
|
@connected_resources = nil
|
8
8
|
end
|
@@ -11,19 +11,22 @@ module Loco
|
|
11
11
|
def identifiers
|
12
12
|
val = WsConnectionStorage.current.get key
|
13
13
|
return [] if val.blank?
|
14
|
+
|
14
15
|
JSON.parse val
|
15
16
|
end
|
16
17
|
|
17
|
-
def add
|
18
|
+
def add(identifier)
|
18
19
|
ids = identifiers
|
19
20
|
return if ids.include? identifier
|
21
|
+
|
20
22
|
ids << identifier
|
21
23
|
WsConnectionStorage.current.set key, ids.to_json
|
22
24
|
end
|
23
25
|
|
24
|
-
def del
|
26
|
+
def del(identifier)
|
25
27
|
ids = identifiers
|
26
28
|
return unless ids.include? identifier
|
29
|
+
|
27
30
|
ids.delete identifier
|
28
31
|
WsConnectionStorage.current.set key, ids.to_json
|
29
32
|
end
|
@@ -35,14 +38,16 @@ module Loco
|
|
35
38
|
|
36
39
|
def connected_resources
|
37
40
|
return @connected_resources if @connected_resources
|
41
|
+
|
38
42
|
@resources.each do |resource|
|
39
43
|
next if WsConnectionManager.new(resource).connected_uuids.empty?
|
44
|
+
|
40
45
|
add resource
|
41
46
|
end
|
42
47
|
@connected_resources || []
|
43
48
|
end
|
44
49
|
|
45
|
-
def connected?
|
50
|
+
def connected?(resource)
|
46
51
|
connected_resources.map do |res|
|
47
52
|
WsConnectionManager.new(res).identifier
|
48
53
|
end.include? WsConnectionManager.new(resource).identifier
|
@@ -50,10 +55,10 @@ module Loco
|
|
50
55
|
|
51
56
|
private
|
52
57
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
+
def add(resource)
|
59
|
+
@connected_resources ||= []
|
60
|
+
@connected_resources << resource
|
61
|
+
@connected_resources.uniq!
|
62
|
+
end
|
58
63
|
end
|
59
64
|
end
|
@@ -2,16 +2,17 @@
|
|
2
2
|
|
3
3
|
module Loco
|
4
4
|
class WsConnectionManager
|
5
|
-
def initialize
|
5
|
+
def initialize(resource)
|
6
6
|
@resource = resource
|
7
7
|
end
|
8
8
|
|
9
9
|
def identifier
|
10
10
|
return @resource if @resource.is_a?(String)
|
11
|
+
|
11
12
|
"#{@resource.class.name.downcase}:#{@resource.id}"
|
12
13
|
end
|
13
14
|
|
14
|
-
def connected?
|
15
|
+
def connected?(uuid)
|
15
16
|
connected_uuids.include? uuid
|
16
17
|
end
|
17
18
|
|
@@ -19,17 +20,17 @@ module Loco
|
|
19
20
|
data.find_all { |_, v| v.is_a? String }.to_h.keys
|
20
21
|
end
|
21
22
|
|
22
|
-
def add
|
23
|
+
def add(uuid)
|
23
24
|
update uuid
|
24
25
|
check_connections
|
25
26
|
end
|
26
27
|
|
27
|
-
def del
|
28
|
+
def del(uuid)
|
28
29
|
save(data.tap { |h| h.delete uuid })
|
29
30
|
check_connections
|
30
31
|
end
|
31
32
|
|
32
|
-
def update
|
33
|
+
def update(uuid)
|
33
34
|
save(data.tap { |h| h[uuid] = current_time })
|
34
35
|
end
|
35
36
|
|
@@ -39,51 +40,54 @@ module Loco
|
|
39
40
|
|
40
41
|
protected
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
JSON.parse serialized_uuids
|
46
|
-
end
|
43
|
+
def data
|
44
|
+
serialized_uuids = WsConnectionStorage.current.get identifier
|
45
|
+
return {} if serialized_uuids.blank?
|
47
46
|
|
48
|
-
|
49
|
-
|
50
|
-
end
|
47
|
+
JSON.parse serialized_uuids
|
48
|
+
end
|
51
49
|
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
def uuids
|
51
|
+
data.keys
|
52
|
+
end
|
55
53
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
[uuid, val]
|
60
|
-
end.to_h.compact
|
61
|
-
save hash
|
62
|
-
end
|
54
|
+
def save(hash)
|
55
|
+
WsConnectionStorage.current.set identifier, hash.to_json
|
56
|
+
end
|
63
57
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
val = check_connection_str uuid, val
|
68
|
-
when Hash
|
69
|
-
uuid, val = check_connection_hash uuid, val
|
70
|
-
end
|
58
|
+
def check_connections
|
59
|
+
hash = data.to_a.map do |arr|
|
60
|
+
uuid, val = check_connection arr.first, arr.last
|
71
61
|
[uuid, val]
|
72
|
-
end
|
62
|
+
end.to_h.compact
|
63
|
+
save hash
|
64
|
+
end
|
73
65
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
66
|
+
def check_connection(uuid, val)
|
67
|
+
case val
|
68
|
+
when String
|
69
|
+
val = check_connection_str uuid, val
|
70
|
+
when Hash
|
71
|
+
uuid, val = check_connection_hash uuid, val
|
78
72
|
end
|
73
|
+
[uuid, val]
|
74
|
+
end
|
79
75
|
|
80
|
-
|
81
|
-
|
82
|
-
[nil, nil]
|
83
|
-
end
|
76
|
+
def check_connection_str(uuid, val)
|
77
|
+
return val if Time.zone.parse(val) >= 3.minutes.ago
|
84
78
|
|
85
|
-
|
86
|
-
|
87
|
-
|
79
|
+
SenderJob.perform_later uuid, loco: { connection_check: true }
|
80
|
+
{ 'check' => current_time }
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_connection_hash(uuid, val)
|
84
|
+
return [uuid, val] if Time.zone.parse(val['check']) >= 5.seconds.ago
|
85
|
+
|
86
|
+
[nil, nil]
|
87
|
+
end
|
88
|
+
|
89
|
+
def current_time
|
90
|
+
Time.current.iso8601(6)
|
91
|
+
end
|
88
92
|
end
|
89
93
|
end
|
@@ -16,7 +16,7 @@ module Loco
|
|
16
16
|
@storage = Config.redis_instance || {}
|
17
17
|
end
|
18
18
|
|
19
|
-
def get
|
19
|
+
def get(key)
|
20
20
|
case @storage
|
21
21
|
when Hash
|
22
22
|
storage[proper_key(key)]
|
@@ -25,7 +25,7 @@ module Loco
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def set
|
28
|
+
def set(key, val)
|
29
29
|
case @storage
|
30
30
|
when Hash
|
31
31
|
storage[proper_key(key)] = val
|
@@ -34,7 +34,7 @@ module Loco
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
def del
|
37
|
+
def del(key)
|
38
38
|
case @storage
|
39
39
|
when Hash
|
40
40
|
storage.delete proper_key(key)
|
@@ -45,8 +45,8 @@ module Loco
|
|
45
45
|
|
46
46
|
protected
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
def proper_key(key)
|
49
|
+
"#{Config.app_name}:#{key}"
|
50
|
+
end
|
51
51
|
end
|
52
52
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: loco-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zbigniew Humeniuk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: loco-rails-core
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 1.7.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: jbuilder
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.10.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.10.0
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: listen
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,14 +120,14 @@ dependencies:
|
|
106
120
|
requirements:
|
107
121
|
- - "~>"
|
108
122
|
- !ruby/object:Gem::Version
|
109
|
-
version: 0.5.
|
123
|
+
version: 0.5.3
|
110
124
|
type: :development
|
111
125
|
prerelease: false
|
112
126
|
version_requirements: !ruby/object:Gem::Requirement
|
113
127
|
requirements:
|
114
128
|
- - "~>"
|
115
129
|
- !ruby/object:Gem::Version
|
116
|
-
version: 0.5.
|
130
|
+
version: 0.5.3
|
117
131
|
- !ruby/object:Gem::Dependency
|
118
132
|
name: puma
|
119
133
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,6 +198,34 @@ dependencies:
|
|
184
198
|
- - "~>"
|
185
199
|
- !ruby/object:Gem::Version
|
186
200
|
version: '3.142'
|
201
|
+
- !ruby/object:Gem::Dependency
|
202
|
+
name: source_maps_fixer
|
203
|
+
requirement: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - ">="
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: '0'
|
208
|
+
type: :development
|
209
|
+
prerelease: false
|
210
|
+
version_requirements: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '0'
|
215
|
+
- !ruby/object:Gem::Dependency
|
216
|
+
name: will_paginate
|
217
|
+
requirement: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - "~>"
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: 3.1.8
|
222
|
+
type: :development
|
223
|
+
prerelease: false
|
224
|
+
version_requirements: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - "~>"
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: 3.1.8
|
187
229
|
description: Rails is awesome, but modern web needs Loco-motive.
|
188
230
|
email:
|
189
231
|
- hello@artofcode.co
|
@@ -245,7 +287,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
245
287
|
- !ruby/object:Gem::Version
|
246
288
|
version: '0'
|
247
289
|
requirements: []
|
248
|
-
rubygems_version: 3.
|
290
|
+
rubygems_version: 3.1.2
|
249
291
|
signing_key:
|
250
292
|
specification_version: 4
|
251
293
|
summary: Framework on top of Rails.
|