action_channels 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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