action_cable_client 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5764cc7e98e88de5996d3ea6e32633b72aa97e15
4
+ data.tar.gz: 99d9baa73bed7eb3b7288021e38ce01494bef4f7
5
+ SHA512:
6
+ metadata.gz: 5dea9401dfe69bcab3d7da3c20576ef1beca22517fdd8c4b4afbf920279ecab7a6f2f55cccf48d1f90b6c6814b65173759c1e7a0aad54c6512909508a7429f01
7
+ data.tar.gz: a4defa4899914dbcb0c55cb331585d382b19e63eb62dbec73d6b8b611bda04118dc79c28da9e2d5bb100dcea139591c889bd998e6395b2dc372f02d6203fe612
@@ -0,0 +1 @@
1
+ * Initial Work
@@ -0,0 +1,41 @@
1
+ # Action Cable Client
2
+ [![Build Status](https://travis-ci.org/NullVoxPopuli/action_cable_client.svg?branch=master)](https://travis-ci.org/NullVoxPopuli/action_cable_client)
3
+ [![Code Climate](https://codeclimate.com/github/NullVoxPopuli/action_cable_client/badges/gpa.svg)](https://codeclimate.com/github/NullVoxPopuli/action_cable_client)
4
+ [![Test Coverage](https://codeclimate.com/github/NullVoxPopuli/action_cable_client/badges/coverage.svg)](https://codeclimate.com/github/NullVoxPopuli/action_cable_client/coverage)
5
+
6
+ There are quite a few WebSocket Servers out there. The popular ones seem to be: [faye-websocket-ruby](https://github.com/faye/faye-websocket-ruby), [em-websockets](https://github.com/igrigorik/em-websocket). The client-only websocket gems are kinda hard to find, and are only raw websocket support. Rails has a thin layer on top of web sockets to help manage subscribing to channels and send/receive on channels.
7
+
8
+ This gem is a wrapper around [em-websocket-client](https://github.com/mwylde/em-websocket-client/), and supports the Rails Action Cable protocol.
9
+
10
+ ## Usage
11
+
12
+ ```ruby
13
+ require 'action_cable_client'
14
+
15
+ EventMachine.run do
16
+
17
+ uri = "ws://localhost:3000/cable/"
18
+ client = ActionCableClient.new(uri, 'RoomChannel')
19
+ # the connected callback is required, as it triggers
20
+ # the actual subscribing to the channel but it can just be
21
+ # client.connected {}
22
+ client.connected { puts 'successfully connected.' }
23
+
24
+ # called whenever a message is received from the server
25
+ client.received do | message |
26
+ puts message
27
+ end
28
+
29
+ # adds to a queue that is purged upon receiving of
30
+ # a ping from the server
31
+ client.perform('speak', { message: 'hello from amc' })
32
+ end
33
+ ```
34
+
35
+ This example is compatible with [this version of a small Rails app with Action Cable](https://github.com/NullVoxPopuli/mesh-relay/tree/2ed88928d91d82b88b7878fcb97e3bd81977cfe8)
36
+
37
+ The available hooks to tie in to are:
38
+ - `disconnected {}`
39
+ - `connected {}`
40
+ - `errored { |msg| }`
41
+ - `received { |msg }`
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+ # required gems
3
+ require 'em-websocket-client'
4
+ require 'forwardable'
5
+ require 'active_support/core_ext/string'
6
+ require 'json'
7
+
8
+ # local files
9
+ require 'action_cable_client/message_factory'
10
+ require 'action_cable_client/message'
11
+
12
+ class ActionCableClient
13
+ extend Forwardable
14
+
15
+ class Commands
16
+ SUBSCRIBE = 'subscribe'
17
+ MESSAGE = 'message'
18
+ end
19
+
20
+ attr_reader :_websocket_client, :_uri, :_channel_name
21
+ attr_reader :_message_factory
22
+ # The queue should store entries in the format:
23
+ # [ action, data ]
24
+ attr_accessor :message_queue, :subscribed
25
+
26
+ alias_method :subscribed?, :subscribed
27
+
28
+ def_delegator :_websocket_client, :disconnect, :disconnected
29
+ def_delegator :_websocket_client, :errback, :errored
30
+ def_delegator :_websocket_client, :connection_completed, :connected?
31
+ def_delegator :_websocket_client, :send_msg, :send_msg
32
+
33
+ # @param [String] uri - e.g.: ws://domain:port
34
+ # @param [String] channel - the name of the channel on the Rails server
35
+ # e.g.: RoomChannel
36
+ def initialize(uri, channel = '')
37
+ @_channel_name = channel
38
+ @_uri = uri
39
+ @message_queue = []
40
+ @subscribed = false
41
+
42
+ @_message_factory = MessageFactory.new(channel)
43
+ # NOTE:
44
+ # EventMachine::WebSocketClient
45
+ # https://github.com/mwylde/em-websocket-client/blob/master/lib/em-websocket-client.rb
46
+ # is a subclass of
47
+ # https://github.com/eventmachine/eventmachine/blob/master/lib/em/connection.rb
48
+ @_websocket_client = EventMachine::WebSocketClient.connect(_uri)
49
+ end
50
+
51
+ # @param [String] action - how the message is being sent
52
+ # @param [Hash] data - the message to be sent to the channel
53
+ def perform(action, data)
54
+ message_queue.push([action, data])
55
+ end
56
+
57
+ # callback for received messages as well as
58
+ # what triggers depleting the message queue
59
+ #
60
+ # @param [Boolean] skip_pings - by default, messages
61
+ # with the identifier '_ping' are skipped
62
+ #
63
+ # @example
64
+ # client = ActionCableClient.new(uri, 'RoomChannel')
65
+ # client.received do |message|
66
+ # # the received message will be JSON
67
+ # puts message
68
+ # end
69
+ def received(skip_pings = true)
70
+ _websocket_client.stream do | message |
71
+ string = message.data
72
+ json = JSON.parse(string)
73
+
74
+ if is_ping?(json)
75
+ check_for_subscribe_confirmation(json) unless subscribed?
76
+ yield(json) unless skip_pings
77
+ else
78
+ yield(json)
79
+ end
80
+ deplete_queue if subscribed?
81
+ end
82
+ end
83
+
84
+ # callback when the client connects to the server
85
+ #
86
+ # @example
87
+ # client = ActionCableClient.new(uri, 'RoomChannel')
88
+ # client.connected do
89
+ # # do things after the client is connected to the server
90
+ # end
91
+ def connected
92
+ _websocket_client.callback do
93
+ subscribe
94
+ yield
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ # {"identifier" => "_ping","type" => "confirm_subscription"}
101
+ def check_for_subscribe_confirmation(message)
102
+ message_type = message[Message::TYPE_KEY]
103
+ if Message::TYPE_CONFIRM_SUBSCRIPTION == message_type
104
+ self.subscribed = true
105
+ end
106
+ end
107
+
108
+ # {"identifier" => "_ping","message" => 1460201942}
109
+ # {"identifier" => "_ping","type" => "confirm_subscription"}
110
+ def is_ping?(message)
111
+ message_identifier = message[Message::IDENTIFIER_KEY]
112
+ Message::IDENTIFIER_PING == message_identifier
113
+ end
114
+
115
+ def subscribe
116
+ msg = _message_factory.create(Commands::SUBSCRIBE)
117
+ _websocket_client.send_msg(msg.to_json)
118
+ end
119
+
120
+ def deplete_queue
121
+ while (message_queue.size > 0)
122
+ action, data = message_queue.pop
123
+ msg = _message_factory.create(Commands::MESSAGE, action, data)
124
+ send_msg(msg.to_json)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ class ActionCableClient
3
+ class Message
4
+ IDENTIFIER_KEY = 'identifier'.freeze
5
+ IDENTIFIER_PING = '_ping'.freeze
6
+ # Type is never sent, but is received
7
+ # TODO: find a better place for this constant
8
+ TYPE_KEY = 'type'.freeze
9
+ TYPE_CONFIRM_SUBSCRIPTION = 'confirm_subscription'.freeze
10
+
11
+
12
+ attr_reader :_command, :_identifier, :_data
13
+
14
+ # @param [String] command - the type of message that this is
15
+ # @param [Hash] identifier - the channel we are subscribed to
16
+ # @param [Hash] data - the data to be sent in this message
17
+ def initialize(command, identifier, data)
18
+ @_command = command
19
+ @_identifier = identifier
20
+ @_data = data
21
+ end
22
+
23
+ def to_json
24
+ hash = {
25
+ command: _command,
26
+ identifier: _identifier.to_json
27
+ }
28
+
29
+ hash[:data] = _data.to_json if _data.present?
30
+
31
+ hash.to_json
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ class ActionCableClient
3
+ class MessageFactory
4
+ attr_reader :_channel
5
+
6
+ # @param [String] channel - the name of the subscribed channel
7
+ def initialize(channel)
8
+ @_channel = channel
9
+ end
10
+
11
+ # @param [String] command - the type of message that this is
12
+ # @param [String] action - the action that is performed to send this message
13
+ # @param [Hash] message - the data to send
14
+ def create(command, action = '', message = nil)
15
+ data = build_data(action, message)
16
+ Message.new(command, identifier, data)
17
+ end
18
+
19
+ # @param [String] action - the action that is performed to send this message
20
+ # @param [Hash] message - the data to send
21
+ # @return [Hash] The data that will be included in the message
22
+ def build_data(action, message)
23
+ message.merge(action: action) if message.is_a?(Hash)
24
+ end
25
+
26
+ # the ending result should look like
27
+ # "{"channel":"RoomChannel"}" but that's up to
28
+ # the Mesage to format it
29
+ def identifier
30
+ { channel: _channel }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class ActionCableClient
3
+ VERSION = '1.0'
4
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: action_cable_client
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - L. Preston Sego III
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
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: em-websocket-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: codeclimate-test-reporter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: A ruby client for interacting with Rails' ActionCable
98
+ email: LPSego3+dev@gmail.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - README.md
105
+ - lib/action_cable_client.rb
106
+ - lib/action_cable_client/message.rb
107
+ - lib/action_cable_client/message_factory.rb
108
+ - lib/action_cable_client/version.rb
109
+ homepage: https://github.com/NullVoxPopuli/action_cable_client
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: 2.3.0
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.5.1
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: ActionCableClient-1.0
133
+ test_files: []