action_channels 0.1.3 → 0.2.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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDczOTM0NGIzOGZiYzJhNDkzNzQ3ZTc4ZDQ1NjU2NDhkZGEzYzFkNQ==
4
+ NDNlYzcxNjljYTNiNWJmNjk4ZDkyYjU1ZWU5ZGVhYTczNTA2MmVmYQ==
5
5
  data.tar.gz: !binary |-
6
- Yzk0ODhhNzVkMjNlOTkxMWE2ZWFmMDJhNzRmOWVkNDY0MDQyYmIxNw==
6
+ NzlkYzg3MWNlZGZjNjNlOGY4YTIyZTRlNWQ5N2Y2ZTdiZWFiYTYyMA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDIzODE4ZWVmOTEyOGRhZjJlMzkxMGEzMjQ2YWIxMTZkZjdiNmYxNzMwNjJi
10
- ZDA3NDk1OTgyMTdkZDI5NmE0YWZmMGM0NWRmZmM1MzkzNTBjOThlMmQyNDU0
11
- YzgyNjI3ZmQ1MGVmN2MxZTU4OThiMTQ5ZjA3ZGU0NWVjYWM5ZmE=
9
+ MDBlOWUyZjEyYTkzOTE2ZDk2YWEzZjU4MDZlNzYwNDRkMWQzYTRiZTJiODY2
10
+ M2RhZGZmMTkyOTNjNWUwZDBjN2NiNzc2MWExMWQyODMwNWY2YWZiNmIzNGY1
11
+ NzZlOTljNTU0MzM3NWM5ZDlmYTkzZWMwMmQ0YThkYTEyMDcxZWU=
12
12
  data.tar.gz: !binary |-
13
- ZDMyMDFkZTY3YWNjZGNiNTY4OTIzOWE4NmJiZjZjNjk1NzFiY2M0YTdlMDI1
14
- OGJhYjUwNzFkMWFiNGY1YzMxMDIyYWViOGRmZjljZjRkYjA5NmQwYTcyMzg4
15
- MmI3YjdmZTc3NjJmYmMyMjZkM2Q3NGM4YmY0ZDVhZGEyOTYwZDc=
13
+ NTM4MDFiMTlmNjNkNTU1MDVjN2Q2MzUyMzgxMDk1OGJlMTIxZTM3ZDIwMmY2
14
+ MmEyMTUxMTA5NWZjZDY0ZjUxYzNjNDc2YTUwYTQzZDQyMmYxNWI0OTU1YmM4
15
+ YTFlMTI2OGMwMjI5NGY0MGI1MjUzYThjNmUwOWE2NGYwMTdmZDg=
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  /examples/*.log
10
+ action_channels.log
10
11
  /examples/tmp/
11
12
  Gemfile.lock
12
13
 
data/README.md CHANGED
@@ -1,4 +1,27 @@
1
1
  # Action channels
2
2
 
3
- 1. The server runs on web sockets.
4
- 2. The project contains a minimum of dependencies.
3
+ ## Example
4
+
5
+ ``` ruby
6
+ require 'action_channels'
7
+
8
+ class PingPongChannel < ActionChannels::Channels::Base
9
+ def process_custom_message(message)
10
+ case message.type
11
+ when 'ping'
12
+ send_message message.author, ActionChannels::Message.new(channel: name, type: 'pong')
13
+ when 'pong'
14
+ send_message message.author, ActionChannels::Message.new(channel: name, type: 'ping')
15
+ else
16
+ on_unknown_type_message message
17
+ end
18
+ end
19
+ end
20
+
21
+ server = ActionChannels::Server.new(
22
+ port: 3050,
23
+ channels: [PingPongChannel.new(name: 'ping-pong')]
24
+ )
25
+
26
+ server.run
27
+ ```
@@ -1,19 +1,27 @@
1
1
  require 'set'
2
+ require 'logger'
3
+ require 'nio/websocket'
2
4
 
3
5
  require "action_channels/version"
4
6
  require "action_channels/errors"
7
+ require "action_channels/driver"
5
8
  require "action_channels/message"
6
9
  require "action_channels/message_senders"
7
- require "action_channels/channel"
10
+ require "action_channels/client"
11
+ require "action_channels/channels"
8
12
  require "action_channels/channel_repository"
9
13
  require "action_channels/server"
10
14
 
11
15
  module ActionChannels
12
16
  class << self
13
- attr_accessor :logger
17
+ attr_writer :logger
14
18
 
15
19
  def configure(&block)
16
20
  yield(self)
17
21
  end
22
+
23
+ def logger
24
+ @logger ||= Logger.new('action_channels.log')
25
+ end
18
26
  end
19
27
  end
@@ -29,7 +29,7 @@ module ActionChannels
29
29
  attr_reader :channels
30
30
 
31
31
  def create(channel_name)
32
- channel = Channel.new name: channel_name
32
+ channel = Channels::NewsChannel.new name: channel_name
33
33
  channels << channel
34
34
  channel
35
35
  end
@@ -0,0 +1,7 @@
1
+ require 'action_channels/channels/base'
2
+ require 'action_channels/channels/news_channel'
3
+
4
+ module ActionChannels
5
+ module Channels
6
+ end
7
+ end
@@ -0,0 +1,89 @@
1
+ module ActionChannels
2
+ module Channels
3
+ class Base
4
+ attr_reader :name, :clients
5
+ attr_accessor :message_sender
6
+
7
+ def initialize(attrs)
8
+ @name = attrs.fetch(:name)
9
+ @clients = Set.new
10
+ @message_sender = attrs.fetch :message_sender, MessageSenders::WebSocket.new
11
+ end
12
+
13
+ def send_message(receiver, message)
14
+ message_sender.do_send(receiver, message)
15
+ end
16
+
17
+ def add_client(client)
18
+ clients << client
19
+
20
+ ActionChannels.logger.info "The channel ##{self.name} added a client"
21
+ ActionChannels.logger.debug "Count of client of channel ##{self.name} is #{self.clients.count}."
22
+ end
23
+
24
+ def remove_client(client)
25
+ clients.delete client
26
+
27
+ ActionChannels.logger.info "The channel ##{self.name} removed a client"
28
+ ActionChannels.logger.debug "Count of client of channel ##{self.name} is #{self.clients.count}."
29
+ end
30
+
31
+ def notify_all(message)
32
+ clients.each do |client|
33
+ send_message client, message
34
+ end
35
+ end
36
+
37
+ def process_message(message)
38
+ ActionChannels.logger.debug "The channel ##{self.name} received a message #{message.inspect}"
39
+
40
+ if message.systemic?
41
+ process_system_message message
42
+ else
43
+ process_custom_message message
44
+ end
45
+ end
46
+
47
+ def process_system_message(message)
48
+ case message.type
49
+ when 'subscribe'
50
+ on_subscribe message.author, message.details
51
+ when 'unsubscribe'
52
+ on_unsubscribe message.author, message.details
53
+ else
54
+ end
55
+ end
56
+
57
+ def process_custom_message(message)
58
+ # nothing
59
+ end
60
+
61
+ def after_message_subscribe(subscriber, details)
62
+ # nothing
63
+ end
64
+
65
+ def after_message_unsubscribe(subscriber, details)
66
+ # nothing
67
+ end
68
+
69
+ private
70
+
71
+ def on_subscribe(subscriber, details)
72
+ add_client(subscriber)
73
+ send_message subscriber, Message.new(channel: name, type: 'subscribed')
74
+ after_message_subscribe subscriber, details
75
+ end
76
+
77
+ def on_unsubscribe(subscriber, details)
78
+ remove_client(subscriber)
79
+ send_message subscriber, Message.new(channel: name, type: 'unsubscribed')
80
+ after_message_unsubscribe subscriber, details
81
+ end
82
+
83
+ def on_unknown_type_message(message)
84
+ user, type = message.author, message.type
85
+ send_message user, Message.new(channel: name, type: 'invalid_message', details: { error: "Unknown type of message: #{type}" })
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,21 @@
1
+ module ActionChannels
2
+ module Channels
3
+ class NewsChannel < Base
4
+ def process_custom_message(message)
5
+ case message.type
6
+ when 'publish'
7
+ on_publish message.author, message.details
8
+ else
9
+ on_unknown_type_message message
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def on_publish(speaker, details)
16
+ notify_all Message.new(channel: name, type: 'news', details: details)
17
+ send_message speaker, Message.new(channel: name, type: 'published')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,77 @@
1
+ require 'timeout'
2
+
3
+ module ActionChannels
4
+ class Client
5
+ class << self
6
+ def send_message(url, message, options = {})
7
+ client = new(url, options)
8
+ client.start
9
+ client.send_message(message)
10
+ client.stop
11
+
12
+ true
13
+ end
14
+ end
15
+
16
+ attr_reader :url, :message_sender
17
+
18
+ def initialize(url, options = {})
19
+ @url = url
20
+ self.is_connected = false
21
+ @message_sender = options.fetch :message_sender, MessageSenders::WebSocket.new
22
+ end
23
+
24
+ def start(waits_at = 20)
25
+ return true if connected?
26
+
27
+ do_connect
28
+
29
+ Timeout::timeout(waits_at, Errors::NotConnected) do
30
+ until connected? do
31
+ sleep 1
32
+ end
33
+ end
34
+
35
+ true
36
+ end
37
+
38
+ def stop
39
+ if connected?
40
+ connect.close
41
+ connect = nil
42
+ end
43
+
44
+ true
45
+ end
46
+
47
+ def send_message(message)
48
+ return false unless connected?
49
+
50
+ message_sender.do_send(connect, message)
51
+
52
+ true
53
+ end
54
+
55
+ def connected?
56
+ is_connected
57
+ end
58
+
59
+ private
60
+
61
+ attr_accessor :is_connected, :connect
62
+
63
+ def do_connect
64
+ client = self
65
+
66
+ ::NIO::WebSocket.connect(url) do |websocket|
67
+ on_open = lambda do |_|
68
+ client.send 'connect=', websocket
69
+ client.send 'is_connected=', true
70
+ end
71
+
72
+ websocket.on :open, on_open
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,9 @@
1
+ require 'delegate'
2
+
3
+ module ActionChannels
4
+ class Driver < SimpleDelegator
5
+ def data
6
+ @data ||= {}
7
+ end
8
+ end
9
+ end
@@ -2,5 +2,6 @@ module ActionChannels
2
2
  module Errors
3
3
  class Base < StandardError; end
4
4
  class NotParseMessage < Base; end
5
+ class NotConnected < Base; end
5
6
  end
6
7
  end
@@ -5,18 +5,18 @@ module ActionChannels
5
5
  attr_accessor(
6
6
  :channel, # [String]
7
7
  :type, # [String]
8
- :author, # [WebSocket::Driver]
8
+ :author, # [Driver]
9
9
  :details # Hash
10
10
  )
11
11
 
12
12
  class << self
13
13
  def parse(message_raw)
14
- command_json = JSON.parse message_raw
14
+ command_json = JSON.parse message_raw, symbolize_names: true
15
15
 
16
16
  new(
17
- channel: command_json['channel'],
18
- type: command_json['type'],
19
- details: command_json.fetch('details', {})
17
+ channel: command_json[:channel],
18
+ type: command_json[:type],
19
+ details: command_json.fetch(:details, {})
20
20
  )
21
21
  rescue JSON::ParserError => exp
22
22
  raise Errors::NotParseMessage, exp.message
@@ -39,5 +39,9 @@ module ActionChannels
39
39
  def to_raw
40
40
  JSON.generate(channel: channel, type: type, details: details)
41
41
  end
42
+
43
+ def systemic?
44
+ %w(subscribe unsubscribe).include? type
45
+ end
42
46
  end
43
47
  end
@@ -1,25 +1,20 @@
1
- require 'nio/websocket'
2
-
3
1
  module ActionChannels
4
2
  class Server
5
- attr_reader :port
3
+ attr_reader :port, :channel_repository
6
4
 
7
5
  def initialize(options)
8
6
  @port = options.fetch(:port)
7
+ @channel_repository = ChannelRepository.new options.fetch(:channels, [])
9
8
  end
10
9
 
11
10
  def run
12
- NIO::WebSocket.listen port: port do |client|
13
- process_client client
11
+ NIO::WebSocket.listen port: port do |driver|
12
+ process_client Driver.new(driver)
14
13
  end
15
14
 
16
15
  ActionChannels.logger.info "Server started on port: #{port}"
17
16
  end
18
17
 
19
- def channel_repository
20
- @channel_repository ||= ChannelRepository.new
21
- end
22
-
23
18
  def process_client(client)
24
19
  client.on :message, callback_on_message(client)
25
20
  client.on :close, callback_on_close(client)
@@ -1,3 +1,3 @@
1
1
  module ActionChannels
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_channels
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - German Antsiferov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-20 00:00:00.000000000 Z
11
+ date: 2018-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -97,8 +97,12 @@ files:
97
97
  - Rakefile
98
98
  - action_channels.gemspec
99
99
  - lib/action_channels.rb
100
- - lib/action_channels/channel.rb
101
100
  - lib/action_channels/channel_repository.rb
101
+ - lib/action_channels/channels.rb
102
+ - lib/action_channels/channels/base.rb
103
+ - lib/action_channels/channels/news_channel.rb
104
+ - lib/action_channels/client.rb
105
+ - lib/action_channels/driver.rb
102
106
  - lib/action_channels/errors.rb
103
107
  - lib/action_channels/message.rb
104
108
  - lib/action_channels/message_senders.rb
@@ -1,80 +0,0 @@
1
- module ActionChannels
2
- class Channel
3
- def self.input_message_types
4
- %w(subscribe unsubscribe publish)
5
- end
6
-
7
- def self.output_message_types
8
- %w(subscribed unsubscribed published news invalid_message)
9
- end
10
-
11
- attr_reader :name, :clients
12
- attr_accessor :message_sender
13
-
14
- def initialize(attrs)
15
- @name = attrs.fetch(:name)
16
- @clients = Set.new
17
- @message_sender = attrs.fetch :message_sender, MessageSenders::WebSocket.new
18
- end
19
-
20
- def send_message(receiver, message)
21
- message_sender.do_send(receiver, message)
22
- end
23
-
24
- def add_client(client)
25
- clients << client
26
-
27
- ActionChannels.logger.info "The channel ##{self.name} added a client"
28
- ActionChannels.logger.debug "Count of client of channel ##{self.name} is #{self.clients.count}."
29
- end
30
-
31
- def remove_client(client)
32
- clients.delete client
33
-
34
- ActionChannels.logger.info "The channel ##{self.name} removed a client"
35
- ActionChannels.logger.debug "Count of client of channel ##{self.name} is #{self.clients.count}."
36
- end
37
-
38
- def notify_all(message)
39
- clients.each do |client|
40
- send_message client, message
41
- end
42
- end
43
-
44
- def process_message(message)
45
- ActionChannels.logger.debug "The channel ##{self.name} received a message #{message.inspect}"
46
-
47
- case message.type
48
- when 'subscribe'
49
- on_subscribe message.author, message.details
50
- when 'unsubscribe'
51
- on_unsubscribe message.author, message.details
52
- when 'publish'
53
- on_publish message.author, message.details
54
- else
55
- on_unknown_type_message message.author, message.type
56
- end
57
- end
58
-
59
- private
60
-
61
- def on_subscribe(subscriber, details)
62
- add_client(subscriber)
63
- send_message subscriber, Message.new(channel: name, type: 'subscribed')
64
- end
65
-
66
- def on_unsubscribe(subscriber, details)
67
- remove_client(subscriber)
68
- send_message subscriber, Message.new(channel: name, type: 'unsubscribed')
69
- end
70
-
71
- def on_publish(speaker, details)
72
- notify_all Message.new(channel: name, type: 'news', details: details)
73
- send_message speaker, Message.new(channel: name, type: 'published')
74
- end
75
-
76
- def on_unknown_type_message(user, type)
77
- send_message user, Message.new(channel: name, type: 'invalid_message', details: { error: "Unknown type of message: #{type}" })
78
- end
79
- end
80
- end