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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/README.md +41 -0
- data/lib/action_cable_client.rb +127 -0
- data/lib/action_cable_client/message.rb +34 -0
- data/lib/action_cable_client/message_factory.rb +33 -0
- data/lib/action_cable_client/version.rb +4 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -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
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* Initial Work
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Action Cable Client
|
2
|
+
[](https://travis-ci.org/NullVoxPopuli/action_cable_client)
|
3
|
+
[](https://codeclimate.com/github/NullVoxPopuli/action_cable_client)
|
4
|
+
[](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
|
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: []
|