ewelink 2.1.0 → 2.3.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/VERSION +1 -1
- data/ewelink.gemspec +1 -1
- data/lib/ewelink.rb +2 -1
- data/lib/ewelink/api.rb +173 -139
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d17e40d48a819572e83369500edbe6295a991626c3333ce4ff0765876f25ed51
|
4
|
+
data.tar.gz: 9caca646a1355592a276e5e4595ab486e407ecaecb105a9e7134028ecab4cb42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e8a2717428e2b57c7a30a7d4e9a1658032b1155a2f9bd2ba9b8b0cb2309748d248f24d66babfbe2212af9d8096e6be18ebdc3c60d799654571526edff1204d5
|
7
|
+
data.tar.gz: 9b9e517a3c8629d6a69b8195cab906d12f8f4d4231ab22311083deb75e5db97b02ec9c13a8509710017bb17cfcf1750c46919f52b393b73a0d453c423f120153
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.3.0
|
data/ewelink.gemspec
CHANGED
@@ -16,8 +16,8 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.required_ruby_version = '>= 2.0.0'
|
17
17
|
|
18
18
|
s.add_dependency 'activesupport', '>= 6.0.0', '< 7.0.0'
|
19
|
+
s.add_dependency 'faye-websocket', '>= 0.11.0', '< 0.12.0'
|
19
20
|
s.add_dependency 'httparty', '>= 0.18.0', '< 0.19.0'
|
20
|
-
s.add_dependency 'websocket-client-simple', '>= 0.3.0', '< 0.4.0'
|
21
21
|
|
22
22
|
s.add_development_dependency 'byebug', '>= 11.0.0', '< 12.0.0'
|
23
23
|
s.add_development_dependency 'rake', '>= 12.0.0', '< 13.0.0'
|
data/lib/ewelink.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'active_support'
|
2
2
|
require 'active_support/core_ext'
|
3
3
|
require 'byebug' if ENV['DEBUGGER']
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'faye/websocket'
|
4
6
|
require 'httparty'
|
5
7
|
require 'io/console'
|
6
8
|
require 'json'
|
@@ -9,7 +11,6 @@ require 'openssl'
|
|
9
11
|
require 'optparse'
|
10
12
|
require 'set'
|
11
13
|
require 'timeout'
|
12
|
-
require 'websocket-client-simple'
|
13
14
|
|
14
15
|
module Ewelink
|
15
16
|
|
data/lib/ewelink/api.rb
CHANGED
@@ -5,13 +5,15 @@ module Ewelink
|
|
5
5
|
APP_ID = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq'
|
6
6
|
APP_SECRET = '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM'
|
7
7
|
DEFAULT_REGION = 'us'
|
8
|
+
REQUEST_TIMEOUT = 10.seconds
|
8
9
|
RF_BRIDGE_DEVICE_UIID = 28
|
9
10
|
SWITCH_DEVICES_UIIDS = [1, 5, 6, 24]
|
10
|
-
TIMEOUT = 10.seconds
|
11
11
|
URL = 'https://#{region}-api.coolkit.cc:8080'
|
12
12
|
UUID_NAMESPACE = 'e25750fb-3710-41af-b831-23224f4dd609';
|
13
13
|
VERSION = 8
|
14
|
+
WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT = 30.seconds
|
14
15
|
WEB_SOCKET_PING_TOLERANCE_FACTOR = 1.5
|
16
|
+
SWITCH_STATUS_CHANGE_CHECK_TIMEOUT = 2.seconds
|
15
17
|
WEB_SOCKET_WAIT_INTERVAL = 0.2.seconds
|
16
18
|
|
17
19
|
attr_reader :email, :password, :phone_number
|
@@ -21,15 +23,18 @@ module Ewelink
|
|
21
23
|
@mutexs = {}
|
22
24
|
@password = password.presence || raise(Error.new(":password must be specified"))
|
23
25
|
@phone_number = phone_number.presence.try(:strip)
|
24
|
-
@switches_statuses = {}
|
25
26
|
@web_socket_authenticated_api_keys = Set.new
|
26
|
-
|
27
|
+
@web_socket_switches_statuses = {}
|
28
|
+
|
29
|
+
raise(Error.new(':email or :phone_number must be specified')) if email.blank? && phone_number.blank?
|
30
|
+
|
31
|
+
start_web_socket_authentication_check_thread
|
27
32
|
end
|
28
33
|
|
29
34
|
def press_rf_bridge_button!(uuid)
|
30
35
|
synchronize(:press_rf_bridge_button) do
|
31
36
|
button = find_rf_bridge_button!(uuid)
|
32
|
-
web_socket_wait_for(-> { web_socket_authenticated? }) do
|
37
|
+
web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
|
33
38
|
params = {
|
34
39
|
'action' => 'update',
|
35
40
|
'apikey' => button[:api_key],
|
@@ -51,15 +56,40 @@ module Ewelink
|
|
51
56
|
|
52
57
|
def reload
|
53
58
|
Ewelink.logger.debug(self.class.name) { 'Reloading API (authentication token, devices, region, connections,...)' }
|
54
|
-
|
55
|
-
@
|
59
|
+
|
60
|
+
@web_socket_authenticated_api_keys.clear
|
61
|
+
@web_socket_switches_statuses.clear
|
62
|
+
|
63
|
+
[@web_socket_ping_thread, @web_socket_thread].each do |thread|
|
64
|
+
next unless thread
|
65
|
+
if Thread.current == thread
|
66
|
+
thread[:stop] = true
|
67
|
+
else
|
68
|
+
thread.kill
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if @web_socket.present?
|
73
|
+
begin
|
74
|
+
@web_socket.close if @web_socket.open?
|
75
|
+
rescue
|
76
|
+
# Ignoring close errors
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
56
80
|
[
|
57
81
|
:@api_keys,
|
58
82
|
:@authentication_token,
|
59
83
|
:@devices,
|
84
|
+
:@last_web_socket_pong_at,
|
60
85
|
:@region,
|
61
86
|
:@rf_bridge_buttons,
|
62
87
|
:@switches,
|
88
|
+
:@web_socket_ping_interval,
|
89
|
+
:@web_socket_ping_thread,
|
90
|
+
:@web_socket_thread,
|
91
|
+
:@web_socket_url,
|
92
|
+
:@web_socket,
|
63
93
|
].each do |variable|
|
64
94
|
remove_instance_variable(variable) if instance_variable_defined?(variable)
|
65
95
|
end
|
@@ -102,23 +132,22 @@ module Ewelink
|
|
102
132
|
|
103
133
|
def switch_on?(uuid)
|
104
134
|
switch = find_switch!(uuid)
|
105
|
-
if @
|
106
|
-
|
107
|
-
'action' => 'query',
|
108
|
-
'apikey' => switch[:api_key],
|
109
|
-
'deviceid' => switch[:device_id],
|
110
|
-
'sequence' => web_socket_sequence,
|
111
|
-
'ts' => 0,
|
112
|
-
'userAgent' => 'app',
|
113
|
-
}
|
114
|
-
web_socket_wait_for(-> { web_socket_authenticated? }) do
|
135
|
+
if @web_socket_switches_statuses[switch[:uuid]].nil?
|
136
|
+
web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
|
115
137
|
Ewelink.logger.debug(self.class.name) { "Checking switch #{switch[:uuid].inspect} status" }
|
138
|
+
params = {
|
139
|
+
'action' => 'query',
|
140
|
+
'apikey' => switch[:api_key],
|
141
|
+
'deviceid' => switch[:device_id],
|
142
|
+
'sequence' => web_socket_sequence,
|
143
|
+
'ts' => 0,
|
144
|
+
'userAgent' => 'app',
|
145
|
+
}
|
116
146
|
send_to_web_socket(JSON.generate(params))
|
117
147
|
end
|
118
148
|
end
|
119
|
-
web_socket_wait_for(-> { !@
|
120
|
-
|
121
|
-
@switches_statuses[switch[:uuid]] == 'on'
|
149
|
+
web_socket_wait_for(-> { !@web_socket_switches_statuses[switch[:uuid]].nil? }, initialize_web_socket: true) do
|
150
|
+
@web_socket_switches_statuses[switch[:uuid]] == 'on'
|
122
151
|
end
|
123
152
|
end
|
124
153
|
|
@@ -149,8 +178,8 @@ module Ewelink
|
|
149
178
|
on = false
|
150
179
|
end
|
151
180
|
switch = find_switch!(uuid)
|
152
|
-
@
|
153
|
-
web_socket_wait_for(-> { web_socket_authenticated? }) do
|
181
|
+
@web_socket_switches_statuses[switch[:uuid]] = nil
|
182
|
+
web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
|
154
183
|
params = {
|
155
184
|
'action' => 'update',
|
156
185
|
'apikey' => switch[:api_key],
|
@@ -165,6 +194,7 @@ module Ewelink
|
|
165
194
|
Ewelink.logger.debug(self.class.name) { "Turning switch #{switch[:uuid].inspect} #{on ? 'on' : 'off'}" }
|
166
195
|
send_to_web_socket(JSON.generate(params))
|
167
196
|
end
|
197
|
+
sleep(SWITCH_STATUS_CHANGE_CHECK_TIMEOUT)
|
168
198
|
switch_on?(switch[:uuid]) # Waiting for switch status update
|
169
199
|
true
|
170
200
|
end
|
@@ -177,6 +207,24 @@ module Ewelink
|
|
177
207
|
end
|
178
208
|
end
|
179
209
|
|
210
|
+
def authenticate_web_socket_api_keys
|
211
|
+
api_keys.each do |api_key|
|
212
|
+
params = {
|
213
|
+
'action' => 'userOnline',
|
214
|
+
'apikey' => api_key,
|
215
|
+
'appid' => APP_ID,
|
216
|
+
'at' => authentication_token,
|
217
|
+
'nonce' => nonce,
|
218
|
+
'sequence' => web_socket_sequence,
|
219
|
+
'ts' => Time.now.to_i,
|
220
|
+
'userAgent' => 'app',
|
221
|
+
'version' => VERSION,
|
222
|
+
}
|
223
|
+
Ewelink.logger.debug(self.class.name) { "Authenticating WebSocket API key: #{api_key.truncate(16).inspect}" }
|
224
|
+
send_to_web_socket(JSON.generate(params))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
180
228
|
def authentication_headers
|
181
229
|
{ 'Authorization' => "Bearer #{authentication_token}" }
|
182
230
|
end
|
@@ -221,36 +269,6 @@ module Ewelink
|
|
221
269
|
end
|
222
270
|
end
|
223
271
|
|
224
|
-
def dispose_web_socket
|
225
|
-
@web_socket_authenticated_api_keys = Set.new
|
226
|
-
|
227
|
-
if @web_socket_ping_thread
|
228
|
-
if Thread.current == @web_socket_ping_thread
|
229
|
-
Thread.current[:stop] = true
|
230
|
-
else
|
231
|
-
@web_socket_ping_thread.kill
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
if @web_socket.present?
|
236
|
-
begin
|
237
|
-
@web_socket.close if @web_socket.open?
|
238
|
-
rescue
|
239
|
-
# Ignoring close errors
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
[
|
244
|
-
:@last_web_socket_pong_at,
|
245
|
-
:@web_socket_ping_interval,
|
246
|
-
:@web_socket_ping_thread,
|
247
|
-
:@web_socket_url,
|
248
|
-
:@web_socket,
|
249
|
-
].each do |variable|
|
250
|
-
remove_instance_variable(variable) if instance_variable_defined?(variable)
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
272
|
def find_rf_bridge_button!(uuid)
|
255
273
|
rf_bridge_buttons.find { |button| button[:uuid] == uuid } || raise(Error.new("No such RF bridge button with UUID: #{uuid.inspect}"))
|
256
274
|
end
|
@@ -272,7 +290,7 @@ module Ewelink
|
|
272
290
|
method = method.to_s.upcase
|
273
291
|
headers = (options[:headers] || {}).reverse_merge('Content-Type' => 'application/json')
|
274
292
|
Ewelink.logger.debug(self.class.name) { "#{method} #{url}" }
|
275
|
-
response = HTTParty.send(method.downcase, url, options.merge(headers: headers).reverse_merge(timeout:
|
293
|
+
response = HTTParty.send(method.downcase, url, options.merge(headers: headers).reverse_merge(timeout: REQUEST_TIMEOUT))
|
276
294
|
raise(Error.new("#{method} #{url}: #{response.code}")) unless response.success?
|
277
295
|
if response['error'] == 301 && response['region'].present?
|
278
296
|
@region = response['region']
|
@@ -286,15 +304,43 @@ module Ewelink
|
|
286
304
|
raise Error.new(e)
|
287
305
|
end
|
288
306
|
|
289
|
-
def send_to_web_socket(
|
290
|
-
|
291
|
-
|
292
|
-
|
307
|
+
def send_to_web_socket(message)
|
308
|
+
web_socket.send(message)
|
309
|
+
rescue => e
|
310
|
+
reload
|
311
|
+
raise Error.new(e)
|
312
|
+
end
|
313
|
+
|
314
|
+
def start_web_socket_authentication_check_thread
|
315
|
+
raise Error.new('WebSocket authentication check must only be started once') if @web_socket_authentication_check_thread.present?
|
316
|
+
|
317
|
+
@web_socket_authentication_check_thread = Thread.new do
|
318
|
+
loop do
|
319
|
+
Ewelink.logger.debug(self.class.name) { 'Checking if WebSocket is authenticated' }
|
320
|
+
begin
|
321
|
+
web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
|
322
|
+
Ewelink.logger.debug(self.class.name) { 'WebSocket is authenticated' }
|
323
|
+
end
|
324
|
+
rescue => e
|
325
|
+
Ewelink.logger.error(self.class.name) { e }
|
326
|
+
end
|
327
|
+
sleep(WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def start_web_socket_ping_thread(interval)
|
333
|
+
@last_web_socket_pong_at = Time.now
|
334
|
+
@web_socket_ping_interval = interval
|
335
|
+
Ewelink.logger.debug(self.class.name) { "Creating thread for WebSocket ping every #{@web_socket_ping_interval} seconds" }
|
336
|
+
@web_socket_ping_thread = Thread.new do
|
337
|
+
loop do
|
338
|
+
break if Thread.current[:stop]
|
339
|
+
sleep(@web_socket_ping_interval)
|
340
|
+
Ewelink.logger.debug(self.class.name) { 'Sending WebSocket ping' }
|
341
|
+
send_to_web_socket('ping')
|
342
|
+
end
|
293
343
|
end
|
294
|
-
web_socket.send(data)
|
295
|
-
rescue
|
296
|
-
dispose_web_socket
|
297
|
-
raise
|
298
344
|
end
|
299
345
|
|
300
346
|
def synchronize(name, &block)
|
@@ -302,96 +348,82 @@ module Ewelink
|
|
302
348
|
end
|
303
349
|
|
304
350
|
def web_socket
|
351
|
+
if web_socket_outdated_ping?
|
352
|
+
Ewelink.logger.warn(self.class.name) { 'WebSocket ping is outdated' }
|
353
|
+
reload
|
354
|
+
end
|
355
|
+
|
305
356
|
synchronize(:web_socket) do
|
306
|
-
@web_socket
|
307
|
-
api = self
|
357
|
+
next @web_socket if @web_socket
|
308
358
|
|
309
|
-
|
310
|
-
|
359
|
+
# Initializes caches before opening WebSocket: important in order to
|
360
|
+
# NOT cumulate requests Timeouts from #web_socket_wait_for.
|
361
|
+
api_keys
|
362
|
+
web_socket_url
|
311
363
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
364
|
+
Ewelink.logger.debug(self.class.name) { "Opening WebSocket to #{web_socket_url.inspect}" }
|
365
|
+
|
366
|
+
@web_socket_thread = Thread.new do
|
367
|
+
EventMachine.run do
|
368
|
+
@web_socket = Faye::WebSocket::Client.new(web_socket_url)
|
369
|
+
|
370
|
+
@web_socket.on(:close) do |event|
|
371
|
+
Ewelink.logger.debug(self.class.name) { 'WebSocket closed' }
|
372
|
+
reload
|
317
373
|
end
|
318
374
|
|
319
|
-
web_socket.on(:
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
end
|
375
|
+
@web_socket.on(:open) do |event|
|
376
|
+
Ewelink.logger.debug(self.class.name) { 'WebSocket opened' }
|
377
|
+
@last_web_socket_pong_at = Time.now
|
378
|
+
authenticate_web_socket_api_keys
|
324
379
|
end
|
325
380
|
|
326
|
-
web_socket.on(:message) do |
|
327
|
-
|
328
|
-
if message.data == 'pong'
|
329
|
-
Ewelink.logger.debug(self.class.name) { "Received WebSocket #{message.data.inspect} message" }
|
330
|
-
@last_web_socket_pong_at = Time.now
|
331
|
-
next
|
332
|
-
end
|
381
|
+
@web_socket.on(:message) do |event|
|
382
|
+
message = event.data
|
333
383
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
end
|
384
|
+
if message == 'pong'
|
385
|
+
Ewelink.logger.debug(self.class.name) { "Received WebSocket #{message.inspect} message" }
|
386
|
+
@last_web_socket_pong_at = Time.now
|
387
|
+
next
|
388
|
+
end
|
340
389
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
390
|
+
begin
|
391
|
+
json = JSON.parse(message)
|
392
|
+
rescue => e
|
393
|
+
Ewelink.logger.error(self.class.name) { 'WebSocket JSON parse error' }
|
394
|
+
reload
|
395
|
+
next
|
396
|
+
end
|
345
397
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
@web_socket_ping_thread = Thread.new do
|
352
|
-
loop do
|
353
|
-
break if Thread.current[:stop]
|
354
|
-
sleep(@web_socket_ping_interval)
|
355
|
-
Ewelink.logger.debug(self.class.name) { 'Sending WebSocket ping' }
|
356
|
-
send_to_web_socket('ping')
|
357
|
-
end
|
358
|
-
end
|
359
|
-
end
|
398
|
+
if json.key?('error') && json['error'] != 0
|
399
|
+
Ewelink.logger.error(self.class.name) { "WebSocket message error: #{message.inspect}" }
|
400
|
+
reload
|
401
|
+
next
|
402
|
+
end
|
360
403
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
end
|
404
|
+
if !@web_socket_ping_thread && json.key?('config') && json['config']['hb'] == 1 && json['config']['hbInterval'].present?
|
405
|
+
start_web_socket_ping_thread(json['config']['hbInterval'] + 7)
|
406
|
+
end
|
365
407
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
end
|
408
|
+
if json['apikey'].present? && !@web_socket_authenticated_api_keys.include?(json['apikey'])
|
409
|
+
@web_socket_authenticated_api_keys << json['apikey']
|
410
|
+
Ewelink.logger.debug(self.class.name) { "WebSocket successfully authenticated API key: #{json['apikey'].truncate(16).inspect}" }
|
370
411
|
end
|
371
|
-
end
|
372
412
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
'action' => 'userOnline',
|
379
|
-
'apikey' => api_key,
|
380
|
-
'appid' => APP_ID,
|
381
|
-
'at' => authentication_token,
|
382
|
-
'nonce' => nonce,
|
383
|
-
'sequence' => web_socket_sequence,
|
384
|
-
'ts' => Time.now.to_i,
|
385
|
-
'userAgent' => 'app',
|
386
|
-
'version' => VERSION,
|
387
|
-
}
|
388
|
-
Ewelink.logger.debug(self.class.name) { "Authenticating WebSocket API key: #{api_key.truncate(16).inspect}" }
|
389
|
-
send_to_web_socket(JSON.generate(params))
|
413
|
+
if json['deviceid'].present? && json['params'].is_a?(Hash) && json['params']['switch'].present?
|
414
|
+
switch = switches.find { |switch| switch[:device_id] == json['deviceid'] }
|
415
|
+
if switch.present?
|
416
|
+
@web_socket_switches_statuses[switch[:uuid]] = json['params']['switch']
|
417
|
+
Ewelink.logger.debug(self.class.name) { "Switch #{switch[:uuid].inspect} is #{@web_socket_switches_statuses[switch[:uuid]]}" }
|
390
418
|
end
|
391
419
|
end
|
392
420
|
end
|
393
421
|
end
|
394
422
|
end
|
423
|
+
|
424
|
+
web_socket_wait_for(-> { @web_socket.present? }) do
|
425
|
+
@web_socket
|
426
|
+
end
|
395
427
|
end
|
396
428
|
end
|
397
429
|
|
@@ -426,16 +458,18 @@ module Ewelink
|
|
426
458
|
end
|
427
459
|
end
|
428
460
|
|
429
|
-
def web_socket_wait_for(condition, &block)
|
430
|
-
web_socket
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
return true
|
461
|
+
def web_socket_wait_for(condition, initialize_web_socket: false, &block)
|
462
|
+
web_socket if initialize_web_socket
|
463
|
+
begin
|
464
|
+
Timeout.timeout(REQUEST_TIMEOUT) do
|
465
|
+
while !condition.call
|
466
|
+
sleep(WEB_SOCKET_WAIT_INTERVAL)
|
436
467
|
end
|
437
|
-
|
468
|
+
block_given? ? yield : true
|
438
469
|
end
|
470
|
+
rescue => e
|
471
|
+
reload
|
472
|
+
raise Error.new(e)
|
439
473
|
end
|
440
474
|
end
|
441
475
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ewelink
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexis Toulotte
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-09-
|
11
|
+
date: 2020-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -31,45 +31,45 @@ dependencies:
|
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 7.0.0
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: faye-websocket
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 0.
|
39
|
+
version: 0.11.0
|
40
40
|
- - "<"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: 0.
|
42
|
+
version: 0.12.0
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: 0.
|
49
|
+
version: 0.11.0
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: 0.
|
52
|
+
version: 0.12.0
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
|
-
name:
|
54
|
+
name: httparty
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
57
|
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: 0.
|
59
|
+
version: 0.18.0
|
60
60
|
- - "<"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.
|
62
|
+
version: 0.19.0
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0.
|
69
|
+
version: 0.18.0
|
70
70
|
- - "<"
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version: 0.
|
72
|
+
version: 0.19.0
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: byebug
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|