deepstream 0.1.7 → 0.2.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 +4 -4
- data/.gitmodules +3 -0
- data/README.md +4 -4
- data/Rakefile +7 -0
- data/deepstream.gemspec +9 -1
- data/lib/deepstream.rb +1 -295
- data/lib/deepstream/ack_timeout_registry.rb +17 -0
- data/lib/deepstream/client.rb +205 -0
- data/lib/deepstream/constants.rb +75 -0
- data/lib/deepstream/error_handler.rb +30 -0
- data/lib/deepstream/event_handler.rb +75 -0
- data/lib/deepstream/exceptions.rb +4 -0
- data/lib/deepstream/helpers.rb +55 -0
- data/lib/deepstream/list.rb +31 -0
- data/lib/deepstream/message.rb +34 -0
- data/lib/deepstream/record.rb +64 -0
- data/lib/deepstream/record_handler.rb +68 -0
- metadata +102 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6cd95fc9f274116e724233dc418e5341bb600504
|
4
|
+
data.tar.gz: 2ba1ca7d05528b7221f264ed6ff133a10ca26425
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e8b66da27b17102bd5b52acb98c72638f95df219c767c2b4d5029ca7695f33084c74194cd453bfc2ba3e54d76b79085fc87d1c7e7f667e46c99badd449bb356
|
7
|
+
data.tar.gz: 7d0196f7f8afc7be326827ef1b93496cc2066374ed4f60279ae58e01863eb6bfaa51be66403ce990d9d30a8a809145ebd8c78686f2c909cae459d2afe0c92b9c
|
data/.gitmodules
ADDED
data/README.md
CHANGED
@@ -18,7 +18,7 @@ ds.emit 'my_event'
|
|
18
18
|
# or
|
19
19
|
ds.emit 'my_event', foo: 'bar', bar: 'foo'
|
20
20
|
# or
|
21
|
-
ds.emit 'my_event', {
|
21
|
+
ds.emit 'my_event', {foo: 'bar', bar: 'foo'}, timeout: 3
|
22
22
|
# or
|
23
23
|
ds.emit 'my_event', nil, timeout: 3
|
24
24
|
|
@@ -41,7 +41,7 @@ foo.bar = 'bar'
|
|
41
41
|
foo.set('bar', 'bar')
|
42
42
|
|
43
43
|
# Set the whole record
|
44
|
-
foo.set(foo: 'foo', bar: 1
|
44
|
+
foo.set(foo: 'foo', bar: 1)
|
45
45
|
|
46
46
|
# Get a list
|
47
47
|
foo = ds.get_list('bar')
|
@@ -53,7 +53,7 @@ foo.add('foo')
|
|
53
53
|
foo.remove('foo')
|
54
54
|
|
55
55
|
# Show record names on the list
|
56
|
-
foo.keys
|
56
|
+
foo.keys
|
57
57
|
|
58
58
|
# Access records on the list
|
59
|
-
foo.all
|
59
|
+
foo.all
|
data/Rakefile
ADDED
data/deepstream.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "deepstream"
|
7
|
-
spec.version = "0.
|
7
|
+
spec.version = "0.2.0"
|
8
8
|
spec.authors = ["Currency-One S.A."]
|
9
9
|
spec.email = ["piotr.szczudlak@currency-one.com"]
|
10
10
|
|
@@ -13,8 +13,16 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://github.com/Currency-One/deepstream-ruby"
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.files += Dir['lib/**/*.rb']
|
16
17
|
spec.bindir = "exe"
|
17
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
19
|
spec.require_paths = ["lib"]
|
20
|
+
spec.required_ruby_version = '>= 2.3.0'
|
19
21
|
spec.license = "Apache-2.0"
|
22
|
+
spec.add_runtime_dependency 'celluloid-websocket-client', '~> 0'
|
23
|
+
spec.add_development_dependency 'cucumber'
|
24
|
+
spec.add_development_dependency 'reel'
|
25
|
+
spec.add_development_dependency 'pry'
|
26
|
+
spec.add_development_dependency 'rspec-expectations'
|
27
|
+
spec.add_development_dependency 'rake'
|
20
28
|
end
|
data/lib/deepstream.rb
CHANGED
@@ -11,298 +11,4 @@
|
|
11
11
|
# See the License for the specific language governing permissions and
|
12
12
|
# limitations under the License.
|
13
13
|
|
14
|
-
require '
|
15
|
-
require 'json'
|
16
|
-
require 'timeout'
|
17
|
-
|
18
|
-
module Deepstream end
|
19
|
-
|
20
|
-
class Deepstream::Record
|
21
|
-
def initialize(client, name, data, version)
|
22
|
-
@client, @name, @data, @version = client, name, data, version
|
23
|
-
end
|
24
|
-
|
25
|
-
def get_name
|
26
|
-
@name
|
27
|
-
end
|
28
|
-
|
29
|
-
def set(*args)
|
30
|
-
if args.size == 1
|
31
|
-
if @client._write('R', 'U', @name, (@version += 1), JSON.dump(args[0]))
|
32
|
-
@data = OpenStruct.new(args[0])
|
33
|
-
end
|
34
|
-
else
|
35
|
-
@client._write('R', 'P', @name, (@version += 1), args[0][0..-2], @client._typed(args[1]))
|
36
|
-
@data[args[0][0..-2]] = args[1]
|
37
|
-
end
|
38
|
-
rescue => e
|
39
|
-
print "unable to set\n"
|
40
|
-
print "Error: ", e.message, "\n" if @client.verbose
|
41
|
-
end
|
42
|
-
|
43
|
-
def _patch(version, field, value)
|
44
|
-
@version = version.to_i
|
45
|
-
@data[field] = value
|
46
|
-
end
|
47
|
-
|
48
|
-
def _update(version, data)
|
49
|
-
@version = version.to_i
|
50
|
-
@data = data
|
51
|
-
end
|
52
|
-
|
53
|
-
def method_missing(name, *args)
|
54
|
-
unless @data.is_a?(Array)
|
55
|
-
set(name, *args) if name[-1] == '='
|
56
|
-
@data[name] || @data[name[-1]]
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def inspect
|
61
|
-
"Deepstream::Record (#{@version}) #{@name} #{@data.to_h}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class Deepstream::List < Deepstream::Record
|
66
|
-
def add(record_name)
|
67
|
-
@data = [] unless @data.is_a?(Array)
|
68
|
-
unless @data.include? record_name
|
69
|
-
@data.push record_name
|
70
|
-
@client._write('R', 'U', @name, (@version += 1), JSON.dump(@data))
|
71
|
-
end
|
72
|
-
@data
|
73
|
-
rescue => e
|
74
|
-
print "unable to add ", @data.pop, "\n"
|
75
|
-
print "Error: ", e.message, "\n" if @client.verbose
|
76
|
-
@data
|
77
|
-
end
|
78
|
-
|
79
|
-
def remove(record_name)
|
80
|
-
@data.delete_if { |x| x == record_name }
|
81
|
-
@client._write('R', 'U', @name, (@version += 1), JSON.dump(@data))
|
82
|
-
@data
|
83
|
-
rescue => e
|
84
|
-
print "unable to remove ", record_name, "\n"
|
85
|
-
@data.push record_name
|
86
|
-
print "Error: ", e.message, "\n" if @client.verbose
|
87
|
-
@data
|
88
|
-
end
|
89
|
-
|
90
|
-
def all
|
91
|
-
@data.map { |x| @client.get_record(x) }
|
92
|
-
end
|
93
|
-
|
94
|
-
def keys
|
95
|
-
@data
|
96
|
-
end
|
97
|
-
|
98
|
-
def set(*args)
|
99
|
-
fail 'cannot use set on a list'
|
100
|
-
end
|
101
|
-
|
102
|
-
def inspect
|
103
|
-
"Deepstream::List (#{@version}) #{@name} keys: #{@data}"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
class Deepstream::Client
|
108
|
-
def initialize(address, port = 6021, credentials = {})
|
109
|
-
@address, @port, @unread_msg, @event_callbacks, @records, @max_timeout, @timeout = address, port, nil, {}, {}, 60, 1
|
110
|
-
connect(credentials)
|
111
|
-
end
|
112
|
-
|
113
|
-
attr_accessor :verbose, :max_timeout
|
114
|
-
attr_reader :connected
|
115
|
-
|
116
|
-
def _login(credentials)
|
117
|
-
_write("A", "REQ", credentials.to_json)
|
118
|
-
ack = _read_socket
|
119
|
-
raise unless ack == %w{A A} || (ack == %w{C A} && _read_socket == %w{A A})
|
120
|
-
end
|
121
|
-
|
122
|
-
def _read_socket(timeout: nil)
|
123
|
-
Timeout.timeout(timeout) do
|
124
|
-
@socket.gets(30.chr).tap { |m| break m.chomp(30.chr).split(31.chr) if m }
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def connect(credentials)
|
129
|
-
return self if @connected
|
130
|
-
Thread.start do
|
131
|
-
Thread.current[:name] = "reader#{object_id}"
|
132
|
-
loop do
|
133
|
-
break if @connected # ensures only one thread remains after reconnection
|
134
|
-
begin
|
135
|
-
Timeout.timeout(2) { @socket = TCPSocket.new(@address, @port) }
|
136
|
-
_login(credentials)
|
137
|
-
@connected = true
|
138
|
-
print Time.now.to_s[/.+ .+ /], "Connected\n" if @verbose
|
139
|
-
Thread.start do
|
140
|
-
_sync_records
|
141
|
-
_resubscribe_events
|
142
|
-
end
|
143
|
-
loop do
|
144
|
-
@timeout = 1
|
145
|
-
begin
|
146
|
-
_process_msg(_read_socket(timeout: 10))
|
147
|
-
rescue Timeout::Error
|
148
|
-
_write("heartbeat") # send anything to check if deepstream responds
|
149
|
-
_process_msg(_read_socket(timeout: 10))
|
150
|
-
end
|
151
|
-
end
|
152
|
-
rescue => e
|
153
|
-
@connected = false
|
154
|
-
@socket.close rescue nil
|
155
|
-
print Time.now.to_s[/.+ .+ /], "Can't connect to deepstream server\n" if @verbose
|
156
|
-
print "Error: ", e.message, "\n" if @verbose
|
157
|
-
sleep @timeout
|
158
|
-
@timeout = [@timeout * 1.2, @max_timeout].min
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
sleep 0.5
|
163
|
-
self
|
164
|
-
end
|
165
|
-
|
166
|
-
def disconnect
|
167
|
-
@connected = false
|
168
|
-
@socket.close rescue nil
|
169
|
-
Thread.list.find { |x| x[:name] == "reader#{object_id}" }.kill
|
170
|
-
self
|
171
|
-
end
|
172
|
-
|
173
|
-
def emit(event, value = nil, opts = { timeout: nil })
|
174
|
-
result = nil
|
175
|
-
Timeout::timeout(opts[:timeout]) do
|
176
|
-
sleep 1 until (result = _write('E', 'EVT', event, _typed(value)) rescue false) || opts[:timeout].nil?
|
177
|
-
end
|
178
|
-
result
|
179
|
-
end
|
180
|
-
|
181
|
-
def on(event, &block)
|
182
|
-
_write_and_read('E', 'S', event)
|
183
|
-
@event_callbacks[event] = block
|
184
|
-
rescue => e
|
185
|
-
print "Error: ", e.message, "\n" if @verbose
|
186
|
-
@event_callbacks[event] = block
|
187
|
-
end
|
188
|
-
|
189
|
-
def get(record_name)
|
190
|
-
get_record(record_name)
|
191
|
-
end
|
192
|
-
|
193
|
-
def get_record(record_name, list: nil)
|
194
|
-
name = list ? "#{list}/#{record_name}" : record_name
|
195
|
-
if list
|
196
|
-
@records[list] ||= get_list(list)
|
197
|
-
@records[list].add(name)
|
198
|
-
end
|
199
|
-
@records[name] ||= (
|
200
|
-
_write_and_read('R', 'CR', name)
|
201
|
-
msg = _read
|
202
|
-
Deepstream::Record.new(self, name, _parse_data(msg[4]), msg[3].to_i)
|
203
|
-
)
|
204
|
-
@records[name]
|
205
|
-
rescue => e
|
206
|
-
print "Error: ", e.message, "\n" if @verbose
|
207
|
-
@records[name] = Deepstream::Record.new(self, name, OpenStruct.new, 0)
|
208
|
-
end
|
209
|
-
|
210
|
-
def get_list(list_name)
|
211
|
-
@records[list_name] ||= (
|
212
|
-
_write_and_read('R', 'CR', list_name)
|
213
|
-
msg = _read
|
214
|
-
Deepstream::List.new(self, list_name, _parse_data(msg[4]), msg[3].to_i)
|
215
|
-
)
|
216
|
-
rescue => e
|
217
|
-
print "Error: ", e.message, "\n" if @verbose
|
218
|
-
@records[list_name] = Deepstream::List.new(self, list_name, [], 0)
|
219
|
-
end
|
220
|
-
|
221
|
-
def delete(record_name)
|
222
|
-
if matching = record_name.match(/(?<namespace>\w+)\/(?<record>.+)/)
|
223
|
-
tmp = get_list(matching[:namespace])
|
224
|
-
tmp.remove(record_name)
|
225
|
-
end
|
226
|
-
_write('R', 'D', record_name)
|
227
|
-
rescue => e
|
228
|
-
print "Error: ", e.message, "\n" if @verbose
|
229
|
-
false
|
230
|
-
end
|
231
|
-
|
232
|
-
def _resubscribe_events
|
233
|
-
@event_callbacks.keys.each do |event|
|
234
|
-
_write_and_read('E', 'S', event)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def _sync_records
|
239
|
-
@records.each do |name, record|
|
240
|
-
_write_and_read('R', 'CR', name)
|
241
|
-
msg = _read
|
242
|
-
@records[name]._update(msg[3].to_i, _parse_data(msg[4]))
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def _write_and_read(*args)
|
247
|
-
@unread_msg = nil
|
248
|
-
_write(*args)
|
249
|
-
yield _read if block_given?
|
250
|
-
end
|
251
|
-
|
252
|
-
def _write(*args)
|
253
|
-
@socket.write(args.join(31.chr) + 30.chr)
|
254
|
-
rescue => e
|
255
|
-
raise "not connected" unless @connected
|
256
|
-
raise e
|
257
|
-
end
|
258
|
-
|
259
|
-
def _process_msg(msg)
|
260
|
-
case msg[0..1]
|
261
|
-
when %w{E EVT} then _fire_event_callback(msg)
|
262
|
-
when %w{R P} then @records[msg[2]]._patch(msg[3], msg[4], _parse_data(msg[5]))
|
263
|
-
when %w{R U} then @records[msg[2]]._update(msg[3], _parse_data(msg[4]))
|
264
|
-
when %w{R A} then @records.delete(msg[3]) if msg[2] == 'D'
|
265
|
-
when %w{E A} then nil
|
266
|
-
when %w{X E} then nil
|
267
|
-
when [] then nil
|
268
|
-
else
|
269
|
-
@unread_msg = msg
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
def _read
|
274
|
-
loop { break @unread_msg || (next sleep 0.01) }.tap { @unread_msg = nil }
|
275
|
-
end
|
276
|
-
|
277
|
-
def _fire_event_callback(msg)
|
278
|
-
@event_callbacks[msg[2]].tap { |cb| Thread.start { cb.(_parse_data(msg[3])) } if cb }
|
279
|
-
end
|
280
|
-
|
281
|
-
def _typed(value)
|
282
|
-
case value
|
283
|
-
when Hash then "O#{value.to_json}"
|
284
|
-
when String then "S#{value}"
|
285
|
-
when Numeric then "N#{value}"
|
286
|
-
when TrueClass then 'T'
|
287
|
-
when FalseClass then 'F'
|
288
|
-
when NilClass then 'L'
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def _parse_data(payload)
|
293
|
-
case payload[0]
|
294
|
-
when 'O' then JSON.parse(payload[1..-1], object_class: OpenStruct)
|
295
|
-
when '{' then JSON.parse(payload, object_class: OpenStruct)
|
296
|
-
when 'S' then payload[1..-1]
|
297
|
-
when 'N' then payload[1..-1].to_f
|
298
|
-
when 'T' then true
|
299
|
-
when 'F' then false
|
300
|
-
when 'L' then nil
|
301
|
-
else JSON.parse(payload, object_class: OpenStruct)
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
def inspect
|
306
|
-
"Deepstream::Client #{@address}:#{@port} connected: #{@connected}"
|
307
|
-
end
|
308
|
-
end
|
14
|
+
require 'deepstream/client'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Deepstream
|
2
|
+
class AckTimeoutRegistry
|
3
|
+
def initialize(client)
|
4
|
+
@client = client
|
5
|
+
@timeouts = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def add(name, message)
|
9
|
+
return unless (timeout = @client.options[:ack_timeout])
|
10
|
+
@timeouts[name] = Celluloid.after(timeout) { @client.on_error(message) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def cancel(name)
|
14
|
+
@timeouts.delete(name)&.cancel
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'celluloid/websocket/client'
|
3
|
+
require 'deepstream/constants'
|
4
|
+
require 'deepstream/error_handler'
|
5
|
+
require 'deepstream/event_handler'
|
6
|
+
require 'deepstream/record_handler'
|
7
|
+
require 'deepstream/helpers'
|
8
|
+
require 'deepstream/message'
|
9
|
+
require 'deepstream/exceptions'
|
10
|
+
|
11
|
+
module Deepstream
|
12
|
+
class Client
|
13
|
+
attr_reader :last_hearbeat, :options, :state
|
14
|
+
|
15
|
+
include Celluloid
|
16
|
+
include Celluloid::Internals::Logger
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
execute_block_on_receiver :on, :subscribe, :listen
|
20
|
+
|
21
|
+
def_delegators :@event_handler, :on, :emit, :subscribe, :unsubscribe, :listen, :resubscribe, :unlisten
|
22
|
+
def_delegators :@error_handler, :error, :on_error
|
23
|
+
def_delegators :@record_handler, :get, :set, :delete, :discard, :get_list
|
24
|
+
|
25
|
+
def initialize(url, options = {})
|
26
|
+
@url = Helpers.url(url)
|
27
|
+
@error_handler = ErrorHandler.new(self)
|
28
|
+
@record_handler = RecordHandler.new(self)
|
29
|
+
@event_handler = EventHandler.new(self)
|
30
|
+
@options = Helpers.default_options.merge!(options)
|
31
|
+
@message_buffer = []
|
32
|
+
@last_hearbeat = nil
|
33
|
+
@challenge_denied, @login_requested, @deliberate_close = false
|
34
|
+
@failed_reconnect_attempts = 0
|
35
|
+
@state = CONNECTION_STATE::CLOSED
|
36
|
+
Celluloid.logger.level = @options[:verbose] ? LOG_LEVEL::INFO : LOG_LEVEL::OFF
|
37
|
+
async.connect
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_open
|
41
|
+
@state = CONNECTION_STATE::AWAITING_CONNECTION
|
42
|
+
@failed_reconnect_attempts = 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_message(data)
|
46
|
+
message = Message.new(data)
|
47
|
+
info("Incoming message: #{message.inspect}")
|
48
|
+
case message.topic
|
49
|
+
when TOPIC::AUTH then authentication_message(message)
|
50
|
+
when TOPIC::CONNECTION then connection_message(message)
|
51
|
+
when TOPIC::EVENT then @event_handler.on_message(message)
|
52
|
+
when TOPIC::ERROR then @error_handler.on_error(message)
|
53
|
+
when TOPIC::RECORD then @record_handler.on_message(message)
|
54
|
+
when TOPIC::RPC then raise UnknownTopic('RPC is currently not implemented.')
|
55
|
+
else raise UnknownTopic(message.to_s)
|
56
|
+
end
|
57
|
+
rescue => e
|
58
|
+
@error_handler.on_exception(e)
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_close(code, reason)
|
62
|
+
info("Websocket connection closed: #{code.inspect}, #{reason.inspect}")
|
63
|
+
@state = CONNECTION_STATE::CLOSED
|
64
|
+
reconnect unless @deliberate_close
|
65
|
+
rescue => e
|
66
|
+
@error_handler.on_exception(e)
|
67
|
+
end
|
68
|
+
|
69
|
+
def login(credentials = @options[:credentials])
|
70
|
+
@login_requested = true
|
71
|
+
@options[:credentials] = credentials
|
72
|
+
if @challenge_denied
|
73
|
+
on_error("this client's connection was closed")
|
74
|
+
elsif !connected?
|
75
|
+
async.connect
|
76
|
+
elsif @state == CONNECTION_STATE::AUTHENTICATING
|
77
|
+
@login_requested = false
|
78
|
+
send_message(TOPIC::AUTH, ACTION::REQUEST, @options[:credentials].to_json)
|
79
|
+
end
|
80
|
+
self
|
81
|
+
rescue => e
|
82
|
+
@error_handler.on_exception(e)
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def close
|
87
|
+
return unless connected?
|
88
|
+
@deliberate_close = true
|
89
|
+
@connection.close
|
90
|
+
@connection.terminate
|
91
|
+
@state = CONNECTION_STATE::CLOSED
|
92
|
+
rescue => e
|
93
|
+
@error_handler.on_exception(e)
|
94
|
+
end
|
95
|
+
|
96
|
+
def connected?
|
97
|
+
@state != CONNECTION_STATE::CLOSED
|
98
|
+
end
|
99
|
+
|
100
|
+
def logged_in?
|
101
|
+
@state == CONNECTION_STATE::OPEN
|
102
|
+
end
|
103
|
+
|
104
|
+
def inspect
|
105
|
+
"#{self.class} #{@url} | connection state: #{@state}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def send_message(*args)
|
109
|
+
message = Message.parse(*args)
|
110
|
+
if !logged_in? && message.needs_authentication?
|
111
|
+
info("Placing message #{message.inspect} in buffer, waiting for connection")
|
112
|
+
@message_buffer << message
|
113
|
+
else
|
114
|
+
info("Sending message: #{message.inspect}")
|
115
|
+
@connection.text(message.to_s)
|
116
|
+
end
|
117
|
+
rescue => e
|
118
|
+
@error_handler.on_exception(e)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def connection_message(message)
|
124
|
+
case message.action
|
125
|
+
when ACTION::ACK then on_connection_ack
|
126
|
+
when ACTION::CHALLENGE then on_challenge
|
127
|
+
when ACTION::ERROR then on_error(message)
|
128
|
+
when ACTION::PING then on_ping
|
129
|
+
when ACTION::REDIRECT then on_redirection(message)
|
130
|
+
when ACTION::REJECTION then on_rejection
|
131
|
+
else raise UnknownAction(message)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def authentication_message(message)
|
136
|
+
case message.action
|
137
|
+
when ACTION::ACK then on_login
|
138
|
+
when ACTION::ERROR then on_error(message)
|
139
|
+
else raise UnknownAction(message)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def on_challenge
|
144
|
+
@state = CONNECTION_STATE::CHALLENGING
|
145
|
+
send_message(TOPIC::CONNECTION, ACTION::CHALLENGE_RESPONSE, @url)
|
146
|
+
end
|
147
|
+
|
148
|
+
def on_connection_ack
|
149
|
+
@state = CONNECTION_STATE::AUTHENTICATING
|
150
|
+
login if @options[:autologin] || @login_requested
|
151
|
+
end
|
152
|
+
|
153
|
+
def on_ping
|
154
|
+
@last_heartbeat = Time.now
|
155
|
+
send_message(TOPIC::CONNECTION, ACTION::PONG)
|
156
|
+
end
|
157
|
+
|
158
|
+
def on_login
|
159
|
+
@state = CONNECTION_STATE::OPEN
|
160
|
+
@message_buffer.each { |message| send_message(message) }.clear
|
161
|
+
every(@options[:heartbeat_interval]) { check_heartbeat } if @options[:heartbeat_interval]
|
162
|
+
end
|
163
|
+
|
164
|
+
def on_rejection
|
165
|
+
@challenge_denied = true
|
166
|
+
close
|
167
|
+
end
|
168
|
+
|
169
|
+
def check_heartbeat
|
170
|
+
return unless @last_heartbeat && Time.now - @last_heartbeat > 2 * @options[:heartbeat_interval]
|
171
|
+
@state = CONNECTION_STATE::CLOSED
|
172
|
+
on_error('Two connections heartbeats missed successively')
|
173
|
+
end
|
174
|
+
|
175
|
+
def on_redirection(message)
|
176
|
+
close
|
177
|
+
connect(message.data.last)
|
178
|
+
end
|
179
|
+
|
180
|
+
def connect(url = @url, reraise = false)
|
181
|
+
@connection = Celluloid::WebSocket::Client.new(url, Actor.current)
|
182
|
+
rescue => e
|
183
|
+
reraise ? raise : @error_handler.on_exception(e)
|
184
|
+
end
|
185
|
+
|
186
|
+
def reconnect
|
187
|
+
@state = CONNECTION_STATE::RECONNECTING
|
188
|
+
if @failed_reconnect_attempts < @options[:max_reconnect_attempts]
|
189
|
+
connect(@url, true)
|
190
|
+
resubscribe
|
191
|
+
else
|
192
|
+
@state = CONNECTION_STATE::ERROR
|
193
|
+
end
|
194
|
+
rescue Errno::ECONNREFUSED
|
195
|
+
@failed_reconnect_attempts += 1
|
196
|
+
on_error("Can't connect! Deepstream server unreachable on #{@url}")
|
197
|
+
sleep(reconnect_interval)
|
198
|
+
reconnect
|
199
|
+
end
|
200
|
+
|
201
|
+
def reconnect_interval
|
202
|
+
[@options[:reconnect_interval] * @failed_reconnect_attempts, @options[:max_reconnect_interval]].min
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Deepstream
|
2
|
+
MESSAGE_SEPARATOR = 30.chr
|
3
|
+
MESSAGE_PART_SEPARATOR = 31.chr
|
4
|
+
|
5
|
+
module LOG_LEVEL
|
6
|
+
DEBUG = 0
|
7
|
+
INFO = 1
|
8
|
+
WARN = 2
|
9
|
+
ERROR = 3
|
10
|
+
OFF = 100
|
11
|
+
end
|
12
|
+
|
13
|
+
module CONNECTION_STATE
|
14
|
+
CLOSED = :closed
|
15
|
+
AWAITING_CONNECTION = :awaiting_connection
|
16
|
+
CHALLENGING = :challenging
|
17
|
+
AWAITING_AUTHENTICATION = :awaiting_authentication
|
18
|
+
AUTHENTICATING = :authenticating
|
19
|
+
OPEN = :open
|
20
|
+
ERROR = :error
|
21
|
+
RECONNECTING = :reconnecting
|
22
|
+
end
|
23
|
+
|
24
|
+
module TOPIC
|
25
|
+
CONNECTION = :C
|
26
|
+
AUTH = :A
|
27
|
+
ERROR = :X
|
28
|
+
EVENT = :E
|
29
|
+
RECORD = :R
|
30
|
+
RPC = :P
|
31
|
+
end
|
32
|
+
|
33
|
+
module ACTION
|
34
|
+
ACK = :A
|
35
|
+
READ = :R
|
36
|
+
REDIRECT = :RED
|
37
|
+
CHALLENGE = :CH
|
38
|
+
CHALLENGE_RESPONSE = :CHR
|
39
|
+
CREATE = :C
|
40
|
+
UPDATE = :U
|
41
|
+
PATCH = :P
|
42
|
+
DELETE = :D
|
43
|
+
SUBSCRIBE = :S
|
44
|
+
UNSUBSCRIBE = :US
|
45
|
+
HAS = :H
|
46
|
+
SNAPSHOT = :SN
|
47
|
+
LISTEN = :L
|
48
|
+
UNLISTEN = :UL
|
49
|
+
LISTEN_ACCEPT = :LA
|
50
|
+
LISTEN_REJET = :LR
|
51
|
+
SUBSCRIPTION_HAS_PROVIDER = :SH
|
52
|
+
SUBSCRIPTION_FOR_PATTERN_FOUND = :SP
|
53
|
+
SUBSCRIPTION_FOR_PATTERN_REMOVED = :SR
|
54
|
+
PROVIDER_UPDATE = :PU
|
55
|
+
QUERY = :Q
|
56
|
+
CREATEORREAD = :CR
|
57
|
+
EVENT = :EVT
|
58
|
+
ERROR = :E
|
59
|
+
REQUEST = :REQ
|
60
|
+
RESPONSE = :RES
|
61
|
+
REJECTION = :REJ
|
62
|
+
PING = :PI
|
63
|
+
PONG = :PO
|
64
|
+
end
|
65
|
+
|
66
|
+
module TYPE
|
67
|
+
STRING = :S
|
68
|
+
OBJECT = :O
|
69
|
+
NUMBER = :N
|
70
|
+
NULL = :L
|
71
|
+
TRUE = :T
|
72
|
+
FALSE = :F
|
73
|
+
UNDEFINED = :U
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'deepstream/constants'
|
2
|
+
require 'deepstream/helpers'
|
3
|
+
require 'deepstream/message'
|
4
|
+
|
5
|
+
module Deepstream
|
6
|
+
class ErrorHandler
|
7
|
+
attr_reader :error
|
8
|
+
|
9
|
+
def initialize(client)
|
10
|
+
@client = client
|
11
|
+
@error = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_error(message)
|
15
|
+
@error =
|
16
|
+
if message.is_a?(Message)
|
17
|
+
message.topic == TOPIC::ERROR ? message.data : Helpers.to_type(message.data.last)
|
18
|
+
else
|
19
|
+
message
|
20
|
+
end
|
21
|
+
puts @error
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_exception(exception)
|
25
|
+
raise exception if @client.options[:debug]
|
26
|
+
puts exception.message
|
27
|
+
puts exception.backtrace
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'deepstream/ack_timeout_registry'
|
2
|
+
require 'deepstream/constants'
|
3
|
+
require 'deepstream/helpers'
|
4
|
+
|
5
|
+
module Deepstream
|
6
|
+
class EventHandler
|
7
|
+
def initialize(client)
|
8
|
+
@client = client
|
9
|
+
@callbacks = {}
|
10
|
+
@listeners = {}
|
11
|
+
@ack_timeout_registry = AckTimeoutRegistry.new(@client)
|
12
|
+
end
|
13
|
+
|
14
|
+
def on(event, &block)
|
15
|
+
unless @callbacks[event]
|
16
|
+
@client.send_message(TOPIC::EVENT, ACTION::SUBSCRIBE, event)
|
17
|
+
@ack_timeout_registry.add(event, "No ACK message received in time for #{event}")
|
18
|
+
end
|
19
|
+
@callbacks[event] = block
|
20
|
+
end
|
21
|
+
alias subscribe on
|
22
|
+
|
23
|
+
def listen(pattern, &block)
|
24
|
+
pattern = pattern.is_a?(Regexp) ? pattern.source : pattern
|
25
|
+
@listeners[pattern] = block
|
26
|
+
@client.send_message(TOPIC::EVENT, ACTION::LISTEN, pattern)
|
27
|
+
@ack_timeout_registry.add(pattern, "No ACK message received in time for #{pattern}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def unlisten(pattern)
|
31
|
+
pattern = pattern.is_a?(Regexp) ? pattern.source : pattern
|
32
|
+
@listeners.delete(pattern)
|
33
|
+
@client.send_message(TOPIC::EVENT, ACTION::UNLISTEN, pattern)
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_message(message)
|
37
|
+
case message.action
|
38
|
+
when ACTION::ACK then @ack_timeout_registry.cancel(message.data.last)
|
39
|
+
when ACTION::EVENT then fire_event_callback(message)
|
40
|
+
when ACTION::SUBSCRIPTION_FOR_PATTERN_FOUND then fire_listen_callback(message)
|
41
|
+
when ACTION::SUBSCRIPTION_FOR_PATTERN_REMOVED then fire_listen_callback(message)
|
42
|
+
else @client.on_error(message)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def emit(event, data = nil)
|
47
|
+
@client.send_message(TOPIC::EVENT, ACTION::EVENT, event, Helpers.to_deepstream_type(data))
|
48
|
+
end
|
49
|
+
|
50
|
+
def unsubscribe(event)
|
51
|
+
@callbacks.delete(event)
|
52
|
+
@client.send_message(TOPIC::EVENT, ACTION::UNSUBSCRIBE, event)
|
53
|
+
end
|
54
|
+
|
55
|
+
def resubscribe
|
56
|
+
@callbacks.keys.each { |event| @client.send_message(TOPIC::EVENT, ACTION::SUBSCRIBE, event) }
|
57
|
+
@listeners.keys.each { |pattern| @client.send_message(TOPIC::EVENT, ACTION::LISTEN, pattern) }
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def fire_event_callback(message)
|
63
|
+
event, data = message.data
|
64
|
+
data = Helpers.to_type(data)
|
65
|
+
Celluloid::Future.new { @callbacks[event].call(event, data) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def fire_listen_callback(message)
|
69
|
+
is_subscribed = message.action == ACTION::SUBSCRIPTION_FOR_PATTERN_FOUND
|
70
|
+
pattern, event = message.data
|
71
|
+
return @client.on_error(pattern) unless @listeners[pattern]
|
72
|
+
@listeners[pattern].call(is_subscribed, event)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Deepstream
|
4
|
+
module Helpers
|
5
|
+
SCHEME = 'ws://'
|
6
|
+
DEFAULT_PORT = 6020
|
7
|
+
DEFAULT_PATH = 'deepstream'
|
8
|
+
|
9
|
+
def self.to_deepstream_type(value)
|
10
|
+
case value
|
11
|
+
when Hash then "O#{value.to_json}"
|
12
|
+
when String then "S#{value}"
|
13
|
+
when Numeric then "N#{value}"
|
14
|
+
when TrueClass then 'T'
|
15
|
+
when FalseClass then 'F'
|
16
|
+
when NilClass then 'L'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.to_type(payload)
|
21
|
+
case payload[0]
|
22
|
+
when 'O' then JSON.parse(payload[1..-1])
|
23
|
+
when '{' then JSON.parse(payload)
|
24
|
+
when 'S' then payload[1..-1]
|
25
|
+
when 'N' then payload[1..-1].to_f
|
26
|
+
when 'T' then true
|
27
|
+
when 'F' then false
|
28
|
+
when 'L' then nil
|
29
|
+
else JSON.parse(payload)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.default_options
|
34
|
+
{
|
35
|
+
ack_timeout: nil,
|
36
|
+
autologin: true,
|
37
|
+
credentials: {},
|
38
|
+
heartbeat_interval: nil,
|
39
|
+
max_reconnect_attempts: 5,
|
40
|
+
max_reconnect_interval: 30,
|
41
|
+
reconnect_interval: 1,
|
42
|
+
verbose: false,
|
43
|
+
debug: false
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.url(url)
|
48
|
+
url.tap do |url|
|
49
|
+
url.prepend(SCHEME) unless url.start_with?(SCHEME)
|
50
|
+
url.concat(":#{DEFAULT_PORT}") unless url[/\:\d+/]
|
51
|
+
url.concat("/#{DEFAULT_PATH}") unless url[/\/\w+$/]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'deepstream/record'
|
2
|
+
|
3
|
+
module Deepstream
|
4
|
+
class List < Record
|
5
|
+
def initialize(*args)
|
6
|
+
super
|
7
|
+
@data = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def add(record_name)
|
11
|
+
set(@data.length.to_s, record_name) unless @data.include?(record_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(version, data)
|
15
|
+
@version = version.to_i
|
16
|
+
data = JSON.parse(data)
|
17
|
+
if data.is_a?(Array)
|
18
|
+
@data.concat(data).uniq!
|
19
|
+
set(@data) if @data.size > data.size
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove(record_name)
|
24
|
+
set(@data) if @data.delete(record_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def all
|
28
|
+
@data.map { |record_name| @client.get(record_name) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'deepstream/constants'
|
3
|
+
|
4
|
+
module Deepstream
|
5
|
+
class Message
|
6
|
+
attr_reader :topic, :action, :data
|
7
|
+
|
8
|
+
def self.parse(*args)
|
9
|
+
args.first.is_a?(self) ? args.first : new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
if args.one?
|
14
|
+
args = args.first.delete(MESSAGE_SEPARATOR).split(MESSAGE_PART_SEPARATOR)
|
15
|
+
end
|
16
|
+
@topic, @action = args.take(2).map(&:to_sym)
|
17
|
+
@data = args.drop(2)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
args = [@topic, @action]
|
22
|
+
args << @data unless @data.empty?
|
23
|
+
args.join(MESSAGE_PART_SEPARATOR).concat(MESSAGE_SEPARATOR)
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"#{self.class.name}: #{@topic} #{@action} #{@data}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def needs_authentication?
|
31
|
+
![TOPIC::CONNECTION, TOPIC::AUTH].include?(@topic)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'deepstream/constants'
|
3
|
+
require 'deepstream/helpers'
|
4
|
+
|
5
|
+
module Deepstream
|
6
|
+
class Record
|
7
|
+
attr_reader :name, :data, :version
|
8
|
+
|
9
|
+
def initialize(client, name)
|
10
|
+
@client = client
|
11
|
+
@name = name
|
12
|
+
@data, @version = nil
|
13
|
+
@client.send_message(TOPIC::RECORD, ACTION::CREATEORREAD, @name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"#{self.class} #{@name} #{@version} #{@data}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def unsubscribe
|
21
|
+
@client.send_message(TOPIC::RECORD, ACTION::UNSUBSCRIBE, name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete
|
25
|
+
@client.delete(@name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def set(*args)
|
29
|
+
if args.one?
|
30
|
+
@data = args.first
|
31
|
+
@client.send_message(TOPIC::RECORD, ACTION::UPDATE, @name, (@version += 1), @data.to_json) if @version
|
32
|
+
elsif args.size == 2
|
33
|
+
path, value = args
|
34
|
+
set_path(@data, path, value)
|
35
|
+
@client.send_message(TOPIC::RECORD, ACTION::PATCH, @name, (@version += 1), path, Helpers.to_deepstream_type(value)) if @version
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def read(version, data)
|
40
|
+
update(version, data)
|
41
|
+
end
|
42
|
+
|
43
|
+
def patch(version, path, value)
|
44
|
+
@version = version.to_i
|
45
|
+
set_path(@data, path, Helpers.to_type(value))
|
46
|
+
end
|
47
|
+
|
48
|
+
def update(version, data)
|
49
|
+
@version = version.to_i
|
50
|
+
@data = JSON.parse(data)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def set_path(data, path, value)
|
56
|
+
key, subkey = path.split('.', 2)
|
57
|
+
if data.is_a?(Hash)
|
58
|
+
subkey ? set_path(data.fetch(key), subkey, value) : data[key] = value
|
59
|
+
elsif data.is_a?(Array)
|
60
|
+
subkey ? set_path(data[key.to_i], subkey, value) : data[key.to_i] = value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'deepstream/constants'
|
2
|
+
require 'deepstream/record'
|
3
|
+
require 'deepstream/list'
|
4
|
+
|
5
|
+
module Deepstream
|
6
|
+
class RecordHandler
|
7
|
+
def initialize(client)
|
8
|
+
@client = client
|
9
|
+
@records = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_message(message)
|
13
|
+
case message.action
|
14
|
+
when ACTION::ACK then nil
|
15
|
+
when ACTION::PATCH then patch(message)
|
16
|
+
when ACTION::READ then read(message)
|
17
|
+
when ACTION::UPDATE then update(message)
|
18
|
+
else @client.on_error(message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(name, list: nil)
|
23
|
+
if list
|
24
|
+
name.prepend("#{list}/")
|
25
|
+
@records[list] ||= List.new(@client, list)
|
26
|
+
@records[list].add(name)
|
27
|
+
end
|
28
|
+
@records[name] ||= Record.new(@client, name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_list(name)
|
32
|
+
@records[name] ||= List.new(@client, name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def set(name, *args)
|
36
|
+
@records[name]&.set(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def unsubscribe(name)
|
40
|
+
@records[name]&.unsubscribe
|
41
|
+
end
|
42
|
+
|
43
|
+
def discard(name)
|
44
|
+
unsubscribe(name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete(name)
|
48
|
+
@client.send_message(TOPIC::RECORD, ACTION::DELETE, name) if @records.delete(name)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def read(message)
|
54
|
+
name, *data = message.data
|
55
|
+
@records[name]&.read(*data)
|
56
|
+
end
|
57
|
+
|
58
|
+
def update(message)
|
59
|
+
name, *data = message.data
|
60
|
+
@records[name]&.update(*data)
|
61
|
+
end
|
62
|
+
|
63
|
+
def patch(message)
|
64
|
+
name, *data = message.data
|
65
|
+
@records[name]&.patch(*data)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,99 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deepstream
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Currency-One S.A.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2017-02-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: celluloid-websocket-client
|
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: cucumber
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
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: reel
|
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
|
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: rspec-expectations
|
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: rake
|
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'
|
13
97
|
description: Basic ruby client for the deepstream.io server
|
14
98
|
email:
|
15
99
|
- piotr.szczudlak@currency-one.com
|
@@ -18,11 +102,24 @@ extensions: []
|
|
18
102
|
extra_rdoc_files: []
|
19
103
|
files:
|
20
104
|
- ".gitignore"
|
105
|
+
- ".gitmodules"
|
21
106
|
- Gemfile
|
22
107
|
- LICENSE
|
23
108
|
- README.md
|
109
|
+
- Rakefile
|
24
110
|
- deepstream.gemspec
|
25
111
|
- lib/deepstream.rb
|
112
|
+
- lib/deepstream/ack_timeout_registry.rb
|
113
|
+
- lib/deepstream/client.rb
|
114
|
+
- lib/deepstream/constants.rb
|
115
|
+
- lib/deepstream/error_handler.rb
|
116
|
+
- lib/deepstream/event_handler.rb
|
117
|
+
- lib/deepstream/exceptions.rb
|
118
|
+
- lib/deepstream/helpers.rb
|
119
|
+
- lib/deepstream/list.rb
|
120
|
+
- lib/deepstream/message.rb
|
121
|
+
- lib/deepstream/record.rb
|
122
|
+
- lib/deepstream/record_handler.rb
|
26
123
|
homepage: https://github.com/Currency-One/deepstream-ruby
|
27
124
|
licenses:
|
28
125
|
- Apache-2.0
|
@@ -35,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
35
132
|
requirements:
|
36
133
|
- - ">="
|
37
134
|
- !ruby/object:Gem::Version
|
38
|
-
version:
|
135
|
+
version: 2.3.0
|
39
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
137
|
requirements:
|
41
138
|
- - ">="
|
@@ -43,9 +140,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
140
|
version: '0'
|
44
141
|
requirements: []
|
45
142
|
rubyforge_project:
|
46
|
-
rubygems_version: 2.
|
143
|
+
rubygems_version: 2.6.8
|
47
144
|
signing_key:
|
48
145
|
specification_version: 4
|
49
146
|
summary: deepstream.io ruby client
|
50
147
|
test_files: []
|
51
|
-
has_rdoc:
|