action_channels 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2a86a68b94af2eef6fcb59c71974a5906905c130
4
+ data.tar.gz: f24eb7b22f1eeafe33183e2686f591a427bf0801
5
+ SHA512:
6
+ metadata.gz: 65665230caaa80943f0d94cd0e561403fb71cbec5b9aae2bfb8bf3446109595c2e91e07bae8c59300eb1eacb557cad59a8c1ac180ee34ff6a637919c042d4dfb
7
+ data.tar.gz: 3f74d7b19fe79a651d50cfb57e8f437f7bf1f140dbadf0e605dddb9def7eec3ff19342cd711331d5d4a842d54797f0b7501f4e42da3ce1f86fbd66edb30e29f0
data/.editorconfig ADDED
@@ -0,0 +1,21 @@
1
+ # EditorConfig helps developers define and maintain consistent
2
+ # coding styles between different editors and IDEs
3
+ # editorconfig.org
4
+
5
+ root = true
6
+
7
+
8
+ [*]
9
+
10
+ # Change these settings to your own preference
11
+ indent_style = space
12
+ indent_size = 2
13
+
14
+ # We recommend you to keep these unchanged
15
+ end_of_line = lf
16
+ charset = utf-8
17
+ trim_trailing_whitespace = true
18
+ insert_final_newline = true
19
+
20
+ [*.md]
21
+ trim_trailing_whitespace = false
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in action_channels.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ action_channels (0.0.1)
5
+ nio4r-websocket
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.3)
11
+ nio4r (2.2.0)
12
+ nio4r-websocket (0.6.2)
13
+ nio4r (>= 1.2.1, < 3.0)
14
+ websocket-driver (~> 0.7)
15
+ rake (10.5.0)
16
+ rspec (3.7.0)
17
+ rspec-core (~> 3.7.0)
18
+ rspec-expectations (~> 3.7.0)
19
+ rspec-mocks (~> 3.7.0)
20
+ rspec-core (3.7.1)
21
+ rspec-support (~> 3.7.0)
22
+ rspec-expectations (3.7.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.7.0)
25
+ rspec-mocks (3.7.0)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.7.0)
28
+ rspec-support (3.7.1)
29
+ websocket-driver (0.7.0)
30
+ websocket-extensions (>= 0.1.0)
31
+ websocket-extensions (0.1.3)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ action_channels!
38
+ bundler (~> 1.16)
39
+ rake (~> 10.0)
40
+ rspec (~> 3.0)
41
+
42
+ BUNDLED WITH
43
+ 1.16.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 German Antsiferov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # Action channels
2
+
3
+ 1. The server runs on web sockets.
4
+ 2. The project contains a minimum of dependencies.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "action_channels/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "action_channels"
8
+ spec.version = ActionChannels::VERSION
9
+ spec.authors = ["German Antsiferov"]
10
+ spec.email = ["dxdy@bk.ru"]
11
+
12
+ spec.summary = %q{Easy server on websockets.}
13
+ spec.description = %q{Easy server on websockets.}
14
+ spec.homepage = "https://github.com/mr-dxdy/action_channels"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_runtime_dependency 'nio4r-websocket'
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.16"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ end
@@ -0,0 +1,19 @@
1
+ require 'set'
2
+
3
+ require "action_channels/version"
4
+ require "action_channels/errors"
5
+ require "action_channels/message"
6
+ require "action_channels/message_senders"
7
+ require "action_channels/channel"
8
+ require "action_channels/channel_repository"
9
+ require "action_channels/server"
10
+
11
+ module ActionChannels
12
+ class << self
13
+ attr_accessor :logger
14
+
15
+ def configure(&block)
16
+ yield(self)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
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(name:, message_sender: MessageSenders::WebSocket.new)
15
+ @name = name
16
+ @clients = Set.new
17
+ @message_sender = message_sender
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
+ ActionChannels.logger.info "The channel ##{self.name} added a client"
27
+ end
28
+
29
+ def remove_client(client)
30
+ clients.delete client
31
+ ActionChannels.logger.info "The channel ##{self.name} removed a client"
32
+ end
33
+
34
+ def notify_all(message)
35
+ clients.each do |client|
36
+ send_message client, message
37
+ end
38
+ end
39
+
40
+ def process_message(message)
41
+ ActionChannels.logger.debug "The channel ##{self.name} received a message #{message.inspect}"
42
+
43
+ case message.type
44
+ when 'subscribe'
45
+ on_subscribe message.author, message.details
46
+ when 'unsubscribe'
47
+ on_unsubscribe message.author, message.details
48
+ when 'publish'
49
+ on_publish message.author, message.details
50
+ else
51
+ on_unknown_type_message message.author, message.type
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def on_subscribe(subscriber, details)
58
+ add_client(subscriber)
59
+ send_message subscriber, Message.new(channel: name, type: 'subscribed')
60
+ end
61
+
62
+ def on_unsubscribe(subscriber, details)
63
+ remove_client(subscriber)
64
+ send_message subscriber, Message.new(channel: name, type: 'unsubscribed')
65
+ end
66
+
67
+ def on_publish(speaker, details)
68
+ notify_all Message.new(channel: name, type: 'news', details: details)
69
+ send_message speaker, Message.new(channel: name, type: 'published')
70
+ end
71
+
72
+ def on_unknown_type_message(user, type)
73
+ send_message user, Message.new(channel: name, type: 'invalid_message', details: { error: "Unknown type of message: #{type}" })
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,37 @@
1
+ module ActionChannels
2
+ class ChannelRepository
3
+ def initialize(channels = [])
4
+ @channels = Set.new channels
5
+ end
6
+
7
+ def all
8
+ channels.to_a
9
+ end
10
+
11
+ def find_by_name(channel_name)
12
+ channels.find { |channel| channel.name.eql? channel_name }
13
+ end
14
+
15
+ def find_by_name_or_create(channel_name)
16
+ find_by_name(channel_name) || create(channel_name)
17
+ end
18
+
19
+ def add(channel)
20
+ channels.add channel
21
+ end
22
+
23
+ def delete(channel)
24
+ channels.delete channel
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :channels
30
+
31
+ def create(channel_name)
32
+ channel = Channel.new name: channel_name
33
+ channels << channel
34
+ channel
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ module ActionChannels
2
+ module Errors
3
+ class Base < StandardError; end
4
+ class NotParseMessage < Base; end
5
+ end
6
+ end
@@ -0,0 +1,43 @@
1
+ require 'json'
2
+
3
+ module ActionChannels
4
+ class Message
5
+ attr_accessor(
6
+ :channel, # [String]
7
+ :type, # [String]
8
+ :author, # [WebSocket::Driver]
9
+ :details # Hash
10
+ )
11
+
12
+ class << self
13
+ def parse(message_raw)
14
+ command_json = JSON.parse message_raw
15
+
16
+ new(
17
+ channel: command_json['channel'],
18
+ type: command_json['type'],
19
+ details: command_json.fetch('details', {})
20
+ )
21
+ rescue JSON::ParserError => exp
22
+ raise Errors::NotParseMessage, exp.message
23
+ end
24
+
25
+ def parse_and_setup_author(message_raw, author)
26
+ command = parse(message_raw)
27
+ command.author = author
28
+ command
29
+ end
30
+ end
31
+
32
+ def initialize(channel:, type:, author: nil, details: {})
33
+ @channel = channel
34
+ @type = type
35
+ @author = author
36
+ @details = details
37
+ end
38
+
39
+ def to_raw
40
+ JSON.generate(channel: channel, type: type, details: details)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,8 @@
1
+ require "action_channels/message_senders/base"
2
+ require "action_channels/message_senders/websocket"
3
+ require "action_channels/message_senders/buffer"
4
+
5
+ module ActionChannels
6
+ module MessageSenders
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module ActionChannels
2
+ module MessageSenders
3
+ class Base
4
+ def do_send(receiver, message)
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ module ActionChannels
2
+ module MessageSenders
3
+ class Buffer < Base
4
+ def do_send(receiver, message)
5
+ queue << [receiver, message]
6
+ end
7
+
8
+ def queue
9
+ @queue ||= []
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module ActionChannels
2
+ module MessageSenders
3
+ class WebSocket < Base
4
+ def do_send(receiver, message)
5
+ receiver.text message.to_raw
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ require 'nio/websocket'
2
+
3
+ module ActionChannels
4
+ class Server
5
+ attr_reader :port
6
+
7
+ def initialize(port:)
8
+ @port = port
9
+ end
10
+
11
+ def run
12
+ NIO::WebSocket.listen port: port do |client|
13
+ on_message = lambda do |event|
14
+ ActionChannels.logger.info "Received new message: #{event.data}"
15
+
16
+ message = Message.parse_and_setup_author event.data, client
17
+ process_message message
18
+ end
19
+
20
+ on_close = lambda do |event|
21
+ ActionChannels.logger.info "Client closed connection."
22
+ channel_repository.all.each { |channel| channel.remove_client(client) }
23
+ end
24
+
25
+ client.on :message, on_message
26
+ client.on :close, on_close
27
+ end
28
+
29
+ ActionChannels.logger.info "Server started on port: #{port}"
30
+ end
31
+
32
+ def channel_repository
33
+ @channel_repository ||= ChannelRepository.new
34
+ end
35
+
36
+ private
37
+
38
+ def process_message(message)
39
+ channel = channel_repository.find_by_name_or_create message.channel
40
+ channel.process_message message
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module ActionChannels
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: action_channels
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - German Antsiferov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-02-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nio4r-websocket
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: Easy server on websockets.
70
+ email:
71
+ - dxdy@bk.ru
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".editorconfig"
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".ruby-version"
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - action_channels.gemspec
86
+ - lib/action_channels.rb
87
+ - lib/action_channels/channel.rb
88
+ - lib/action_channels/channel_repository.rb
89
+ - lib/action_channels/errors.rb
90
+ - lib/action_channels/message.rb
91
+ - lib/action_channels/message_senders.rb
92
+ - lib/action_channels/message_senders/base.rb
93
+ - lib/action_channels/message_senders/buffer.rb
94
+ - lib/action_channels/message_senders/websocket.rb
95
+ - lib/action_channels/server.rb
96
+ - lib/action_channels/version.rb
97
+ homepage: https://github.com/mr-dxdy/action_channels
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.6.13
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Easy server on websockets.
121
+ test_files: []