action_cable_client 1.0

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