substrate_client.rb 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af04674d5e75dbafa82eade829b5798fe87e015a51a8edb63d7845cdd38c27b9
4
- data.tar.gz: 8afa612ea099169396f4ef3155c5158ba188b09ff602b5d515fcf6deadfe5739
3
+ metadata.gz: bb233970cdc1b51057d436d0ffc2ae411b99ce3c2e5c51ba3dfb5dcdae0fc960
4
+ data.tar.gz: 35750c213d2fbf7ae223cdd7b9f7a8bc40c7c7b8fab6a09603a0604db6533562
5
5
  SHA512:
6
- metadata.gz: f5c5379451ece56ac1a868092446f66929c83eafb36dace7007b2f479616c3bb2c7a9aff2f77fd1ef25b0bdf5ebb0a431b17308d892ecc4ad3cad54a03700a2e
7
- data.tar.gz: e179bd7fb5a421f1de150463d09a04ad369a15eb1f1027c5c21d9fdc837dda5ebed1671cac093e2a584b9c356ad3112881b990d0763394d533ddb7d3e22c7f00
6
+ metadata.gz: 5731212fc4ad5791bb607f132e660b89f7ff652937029e986040aeee85335939d5a5edf9dd9a21a91f346c2b88711657586747a01727f35eae8318b9b944a62f
7
+ data.tar.gz: 69514b6b9b3eb085f7fefc672dff0b3a85cdfb64babfad28f6ab788f98ee950e2a2e9fcf2e89ab448b7e8c7137da20ba6ec0e68d375ece1b9c41e5f77d3d6964
@@ -1,16 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- substrate_client.rb (0.1.7)
4
+ substrate_client.rb (0.1.8)
5
5
  activesupport (~> 5.2.4)
6
- eventmachine (~> 1.2.7)
7
- faye-websocket (~> 0.10.9)
8
- scale.rb (~> 0.2.5)
6
+ kontena-websocket-client (~> 0.1.1)
7
+ scale.rb (~> 0.2.11)
8
+ substrate_common.rb (~> 0.1.9)
9
9
 
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- activesupport (5.2.4.3)
13
+ activesupport (5.2.4.4)
14
14
  concurrent-ruby (~> 1.0, >= 1.0.2)
15
15
  i18n (>= 0.7, < 2)
16
16
  minitest (~> 5.1)
@@ -18,17 +18,15 @@ GEM
18
18
  base58 (0.2.3)
19
19
  blake2b (0.10.0)
20
20
  coderay (1.1.2)
21
- concurrent-ruby (1.1.6)
21
+ concurrent-ruby (1.1.7)
22
22
  diff-lcs (1.3)
23
- eventmachine (1.2.7)
24
- faye-websocket (0.10.9)
25
- eventmachine (>= 0.12.0)
26
- websocket-driver (>= 0.5.1)
27
- i18n (1.8.2)
23
+ i18n (1.8.5)
28
24
  concurrent-ruby (~> 1.0)
29
- json (2.3.0)
25
+ json (2.3.1)
26
+ kontena-websocket-client (0.1.1)
27
+ websocket-driver (~> 0.6.5)
30
28
  method_source (0.9.2)
31
- minitest (5.14.1)
29
+ minitest (5.14.2)
32
30
  pry (0.12.2)
33
31
  coderay (~> 1.1.0)
34
32
  method_source (~> 0.9.0)
@@ -46,12 +44,12 @@ GEM
46
44
  diff-lcs (>= 1.2.0, < 2.0)
47
45
  rspec-support (~> 3.9.0)
48
46
  rspec-support (3.9.2)
49
- scale.rb (0.2.5)
47
+ scale.rb (0.2.11)
50
48
  activesupport (>= 4.0.0)
51
49
  json (~> 2.3.0)
52
- substrate_common.rb (~> 0.1.8)
50
+ substrate_common.rb (~> 0.1.9)
53
51
  thor (~> 0.19.0)
54
- substrate_common.rb (0.1.8)
52
+ substrate_common.rb (0.1.9)
55
53
  base58
56
54
  blake2b
57
55
  xxhash
@@ -59,9 +57,9 @@ GEM
59
57
  thread_safe (0.3.6)
60
58
  tzinfo (1.2.7)
61
59
  thread_safe (~> 0.1)
62
- websocket-driver (0.7.1)
60
+ websocket-driver (0.6.5)
63
61
  websocket-extensions (>= 0.1.0)
64
- websocket-extensions (0.1.4)
62
+ websocket-extensions (0.1.5)
65
63
  xxhash (0.4.0)
66
64
 
67
65
  PLATFORMS
data/README.md CHANGED
@@ -22,205 +22,116 @@ Or install it yourself as:
22
22
 
23
23
  ### Supported rpc methods
24
24
 
25
- #### rpc method list
26
-
27
25
  ```ruby
28
26
  require "substrate_client"
29
27
 
30
28
  client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
31
- client.method_list do |methods|
32
- p methods
33
- end
29
+ client.methods
30
+ ```
31
+ returns like:
32
+ ```shell
33
+ [
34
+ "account_nextIndex",
35
+ "author_hasKey",
36
+ ...
37
+ "chain_getBlock",
38
+ "chain_getBlockHash",
39
+ ...
40
+ ]
34
41
  ```
35
- The rpc methods can be dynamically called by its name, so the methods returned by this method can all be used.
36
-
37
-
38
-
39
- #### rpc methods
40
-
41
- But, in order to show the parameters more clearly, some important or frequently used methods are hard-coded:
42
-
43
- - chain_get_finalised_head(&callback)
44
-
45
- Get hash of the last finalized block in the canon chain
46
-
47
- ```ruby
48
- client.chain_get_finalised_head do |head|
49
- p head
50
- end
51
- ```
52
-
53
-
54
-
55
- - chain_get_head(&callback)
56
-
57
- Retrieves the header
58
-
59
-
60
-
61
- - chain_get_header(block_hash = nil, &callback)
62
-
63
- Retrieves the header for a specific block
64
-
65
-
66
-
67
- - chain_get_block(block_hash = nil, &callback)
68
-
69
- Get header and body of a relay chain block
70
-
71
-
72
-
73
- - chain_get_block_hash(block_id, &callback)
74
-
75
- Get the block hash for a specific block
76
-
77
-
78
-
79
- - chain_get_runtime_version(block_hash = nil, &callback)
80
-
81
- Get the runtime version for a specific block
82
-
83
-
84
-
85
- - state_get_metadata(block_hash = nil, &callback)
86
-
87
- Returns the runtime metadata by block
88
-
89
-
90
-
91
- - state_get_storage(storage_key, block_hash = nil, &callback)
92
-
93
- Retrieves the storage for a key
94
-
95
-
96
-
97
- - system_name(&callback)
98
-
99
-
100
-
101
- - system_version(&callback)
102
-
103
-
104
-
105
- - chain_subscribe_all_heads(&callback)
106
-
107
- Retrieves the newest header via subscription. This will return data continuously until you unsubscribe the subscription.
108
-
109
- ```ruby
110
- subscription = client.chain_subscribe_all_heads do |data|
111
- p data
112
- end
113
- ```
114
-
115
- - chain_unsubscribe_all_heads(subscription)
116
-
117
- Unsubscribe newest header subscription.
118
-
119
- ```ruby
120
- client.chain_unsubscribe_all_heads(subscription)
121
- ```
122
-
123
-
124
-
125
- - chain_subscribe_new_heads(&callback)
126
-
127
- Retrieves the best header via subscription. This will return data continuously until you unsubscribe the subscription.
128
-
129
- - chain_unsubscribe_new_heads(subscription)
130
-
131
- Unsubscribe the best header subscription.
132
-
133
-
134
-
135
-
136
- - chain_subscribe_finalized_heads(&callback)
137
-
138
- Retrieves the best finalized header via subscription. This will return data continuously until you unsubscribe the subscription.
139
-
140
- - chain_unsubscribe_finalized_heads(subscription)
141
-
142
- Unsubscribe the best finalized header subscription.
143
-
144
-
145
-
146
- - state_subscribe_runtime_version(&callback)
147
-
148
- Retrieves the runtime version via subscription.
149
-
150
- - state_unsubscribe_runtime_version(subscription)
151
-
152
- Unsubscribe the runtime version subscription.
153
-
154
-
155
-
156
- - state_subscribe_storage(keys, &callback)
157
-
158
- Subscribes to storage changes for the provided keys until unsubscribe.
159
42
 
160
- ```ruby
161
- subscription = client.state_subscribe_storage ["0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"] do |data|
162
- p data
163
- end
164
- ```
43
+ The rpc methods can be dynamically called by its name, so you can call it like:
165
44
 
166
- - state_unsubscribe_storage(subscription)
45
+ ```ruby
46
+ client.chain_getBlockHash(1024)
47
+ ```
167
48
 
168
- Unsubscribe storage changes.
169
-
170
-
171
- ### Cutom methods based on rpc methods
49
+ ### Origin rpc methods
50
+
51
+ - `account_nextIndex`
52
+ * `author_hasKey`
53
+ * `author_hasSessionKeys`
54
+ * `author_insertKey`
55
+ * `author_pendingExtrinsics`
56
+ * `author_removeExtrinsic`
57
+ * `author_rotateKeys`
58
+ * `author_submitExtrinsic`
59
+ * `babe_epochAuthorship`
60
+ * `chain_getBlock(block_hash = nil)`
61
+ * `chain_getBlockHash(block_id)`
62
+ * `chain_getFinalisedHead`
63
+ * `chain_getFinalizedHead`
64
+ * `chain_getHead`
65
+ * `chain_getHeader(block_hash = nil)`
66
+ * `chain_getRuntimeVersion(block_hash = nil)`
67
+ * `childstate_getKeys`
68
+ * `childstate_getStorage`
69
+ * `childstate_getStorageHash`
70
+ * `childstate_getStorageSize`
71
+ * `grandpa_roundState`
72
+ * `offchain_localStorageGet`
73
+ * `offchain_localStorageSet`
74
+ * `payment_queryInfo`
75
+ * `state_call`
76
+ * `state_callAt`
77
+ * `state_getKeys`
78
+ * `state_getKeysPaged`
79
+ * `state_getKeysPagedAt`
80
+ * `state_getMetadata(block_hash = nil)`
81
+ * `state_getPairs`
82
+ * `state_getReadProof`
83
+ * `state_getRuntimeVersion`
84
+ * `state_getStorage(storage_key, block_hash = nil)`
85
+ * `state_getStorageAt`
86
+ * `state_getStorageHash`
87
+ * `state_getStorageHashAt`
88
+ * `state_getStorageSize`
89
+ * `state_getStorageSizeAt`
90
+ * `state_queryStorage`
91
+ * `state_queryStorageAt`
92
+ * `system_accountNextIndex`
93
+ * `system_addReservedPeer`
94
+ * `system_chain`
95
+ * `system_chainType`
96
+ * `system_dryRun`
97
+ * `system_dryRunAt`
98
+ * `system_health`
99
+ * `system_localListenAddresses`
100
+ * `system_localPeerId`
101
+ * `system_name`
102
+ * `system_networkState`
103
+ * `system_nodeRoles`
104
+ * `system_peers`
105
+ * `system_properties`
106
+ * `system_removeReservedPeer`
107
+ - `system_version`
108
+
109
+ ### Wrap methods
172
110
 
173
111
  These methods will encode the parameters and decode the returned data
174
112
 
175
- - get_block_number(block_hash, &callback)
176
-
177
- - get_metadata(block_hash, &callback)
113
+ - `get_block_number(block_hash)`
178
114
 
179
- - get_block(block_hash=nil, &callback)
115
+ - `get_metadata(block_hash)`
180
116
 
181
- - get_block_events(block_hash, &callback)
117
+ - `get_block(block_hash=nil)`
182
118
 
183
- - subscribe_block_events(&callback, &callback)
119
+ - `get_block_events(block_hash)`
184
120
 
185
- - get_storage(module_name, storage_name, params = nil, block_hash = nil, &callback)
121
+ - `get_storage(module_name, storage_name, params = nil, block_hash = nil)`
186
122
 
187
123
  ```ruby
188
- client.get_storage("Balances", "TotalIssuance", nil, nil) do |storage|
189
- p storage
190
- end
191
-
192
- client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], nil) do |storage|
193
- p storage
194
- end
195
-
196
- client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"], nil) do |storage|
197
- p storage
198
- end
124
+ client.get_storage("Balances", "TotalIssuance", nil, nil)
125
+ client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], nil)
126
+ client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"], nil)
199
127
  ```
200
-
201
- - compose_call(module_name, call_name, params, block_hash=nil, &callback)
128
+
129
+ - `compose_call(module_name, call_name, params, block_hash=nil)`
202
130
 
203
131
  ```ruby
204
- compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }, nil do |hex|
205
- p hex
206
- end
132
+ client.compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }, nil
207
133
  ```
208
134
 
209
-
210
-
211
- ## Synchronized client
212
-
213
- There is also a synchronized version of the client, which is disconnected every time the data is retrieved. It is very convenient in some situations. But this version cannot use the subscription interface.
214
-
215
- ```ruby
216
- client = SubstrateClientSync.new "wss://kusama-rpc.polkadot.io/"
217
- p client.method_list
218
- p client.chain_get_head
219
- p client.chain_get_finalised_head
220
- p client.chain_get_header(block_hash = nil)
221
- ...
222
- ```
223
-
224
135
  ## Docker
225
136
 
226
137
  1. update to latest image
@@ -253,22 +164,13 @@ p client.chain_get_header(block_hash = nil)
253
164
  /usr/src/app # ./bin/console
254
165
  [1] pry(main)> client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
255
166
  => #<SubstrateClient:0x000055a78f124f58 ...
256
- [2] pry(main)> client.method_list do |methods| p methods end
167
+ [2] pry(main)> client.methods
257
168
  => ...
258
- [3] pry(main)> subscription = client.chain_subscribe_new_heads do |data| p data end
259
- ...
260
- ```
261
-
262
- 5. Or, SubstrateClientSync:
263
-
264
- ```shell
265
- /usr/src/app # ./bin/console
266
- [1] pry(main)> client = SubstrateClientSync.new("wss://kusama-rpc.polkadot.io/", spec_name: "kusama")
267
- => #<SubstrateClientSync:0x000055a78edfd6e0 @request_id=1, @spec_name="kusama", @url="wss://kusama-rpc.polkadot.io/">
268
- [2] pry(main)> client.chain_get_head
169
+ [3] pry(main)> client.chain_getHead
269
170
  => "0xb3c3a220d4639b7c62f179f534b3a66336a115ebc18f13db053f0c57437c45fc"
270
171
  ```
271
172
 
173
+
272
174
  ## Development
273
175
 
274
176
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -3,10 +3,10 @@
3
3
  require "substrate_client"
4
4
  require "json"
5
5
 
6
- url = ARGV[0] || "wss://kusama-rpc.polkadot.io/"
7
- client = SubstrateClientSync.new(url)
6
+ url = ARGV[0] || "wss://kusama-rpc.polkadot.io"
7
+ client = SubstrateClient.new(url)
8
8
 
9
- block_hash = ARGV[1] || client.chain_get_finalised_head
9
+ block_hash = ARGV[1] || client.chain_getFinalisedHead
10
10
 
11
11
  metadata = client.get_metadata(block_hash)
12
12
  puts JSON.pretty_generate(metadata.value.to_human)
@@ -1,7 +1,7 @@
1
1
 
2
2
  class SubstrateClient::Helper
3
3
  class << self
4
- def generate_storage_hash_from_metadata(metadata, module_name, storage_name, params = nil)
4
+ def generate_storage_key_from_metadata(metadata, module_name, storage_name, params = nil)
5
5
  # find the storage item from metadata
6
6
  metadata_modules = metadata.value.value[:metadata][:modules]
7
7
  metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
@@ -18,7 +18,8 @@ class SubstrateClient::Helper
18
18
  return_type = map[:value]
19
19
  # TODO: decode to account id if param is address
20
20
  # params[0] = decode(params[0]) if map[:key] == "AccountId"
21
- params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
21
+ type = Scale::Types.get(map[:key])
22
+ params[0] = type.new(params[0]).encode
22
23
  elsif map = storage_item[:type][:DoubleMap]
23
24
  raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
24
25
 
@@ -31,7 +32,7 @@ class SubstrateClient::Helper
31
32
  raise NotImplementedError
32
33
  end
33
34
 
34
- storage_hash = generate_storage_hash(
35
+ storage_key = generate_storage_key(
35
36
  module_name,
36
37
  storage_name,
37
38
  params,
@@ -39,12 +40,12 @@ class SubstrateClient::Helper
39
40
  hasher2,
40
41
  metadata.value.value[:metadata][:version]
41
42
  )
42
- [storage_hash, return_type]
43
+ [storage_key, return_type]
43
44
  end
44
45
 
45
- def generate_storage_hash(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
46
+ def generate_storage_key(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
46
47
  if metadata_version and metadata_version >= 9
47
- storage_hash = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
48
+ storage_key = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
48
49
 
49
50
  params&.each_with_index do |param, index|
50
51
  if index == 0
@@ -57,22 +58,22 @@ class SubstrateClient::Helper
57
58
 
58
59
  param_key = param.hex_to_bytes
59
60
  param_hasher = "Twox128" if param_hasher.nil?
60
- storage_hash += Crypto.send param_hasher.underscore, param_key
61
+ storage_key += Crypto.send param_hasher.underscore, param_key
61
62
  end
62
63
 
63
- "0x#{storage_hash}"
64
+ "0x#{storage_key}"
64
65
  else
65
66
  # TODO: add test
66
- storage_hash = module_name + " " + storage_name
67
+ storage_key = module_name + " " + storage_name
67
68
 
68
69
  unless params.nil?
69
70
  params = [params] if params.class != ::Array
70
71
  params_key = params.join("")
71
72
  hasher = "Twox128" if hasher.nil?
72
- storage_hash += params_key.hex_to_bytes.bytes_to_utf8
73
+ storage_key += params_key.hex_to_bytes.bytes_to_utf8
73
74
  end
74
75
 
75
- "0x#{Crypto.send( hasher.underscore, storage_hash )}"
76
+ "0x#{Crypto.send( hasher.underscore, storage_key )}"
76
77
  end
77
78
  end
78
79
 
@@ -114,16 +115,5 @@ class SubstrateClient::Helper
114
115
  block
115
116
  end
116
117
 
117
- # chain_unsubscribe_runtime_version
118
- # =>
119
- # chain_unsubscribeRuntimeVersion
120
- def real_method_name(method_name)
121
- segments = method_name.to_s.split("_")
122
- if segments.length == 1
123
- segments[0]
124
- else
125
- segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
126
- end
127
- end
128
118
  end
129
119
  end
@@ -5,40 +5,42 @@ require "scale.rb"
5
5
  require "json"
6
6
  require "active_support"
7
7
  require "active_support/core_ext/string"
8
- require "websocket"
9
8
  require "helper"
10
- require "timeout_queue"
11
- require "substrate_client_sync"
9
+ require 'kontena-websocket-client'
10
+
11
+ def ws_request(url, payload)
12
+ result = nil
13
+ Kontena::Websocket::Client.connect(url, {}) do |client|
14
+ client.send(payload.to_json)
15
+
16
+ client.read do |message|
17
+ result = JSON.parse message
18
+ client.close(1000)
19
+ end
20
+ end
21
+
22
+ return result
23
+ rescue Kontena::Websocket::CloseError => e
24
+ raise SubstrateClient::WebsocketError, e.reason
25
+ rescue Kontena::Websocket::Error => e
26
+ raise SubstrateClient::WebsocketError, e.reason
27
+ end
12
28
 
13
29
  class SubstrateClient
30
+ class WebsocketError < StandardError; end
14
31
  class RpcError < StandardError; end
15
32
  class RpcTimeout < StandardError; end
16
- class << self
17
- attr_accessor :logger
18
- end
19
- SubstrateClient.logger = Logger.new(STDOUT)
20
- SubstrateClient.logger.level = Logger::INFO
21
33
 
22
34
  attr_accessor :spec_name, :spec_version, :metadata
23
- attr_accessor :ws
24
35
 
25
- def initialize(url, spec_name: nil, onopen: nil)
36
+ def initialize(url, spec_name: nil)
26
37
  @url = url
27
38
  @request_id = 1
28
39
  @spec_name = spec_name
29
- @onopen = onopen
30
- Scale::TypeRegistry.instance.load(spec_name)
31
-
32
- init_ws
33
-
34
- at_exit { self.close }
35
- end
36
-
37
- def close
38
- @ws.close
40
+ Scale::TypeRegistry.instance.load(spec_name: spec_name)
39
41
  end
40
42
 
41
- def request(method, params, callback: nil, subscription_callback: nil)
43
+ def request(method, params)
42
44
  payload = {
43
45
  "jsonrpc" => "2.0",
44
46
  "method" => method,
@@ -46,274 +48,98 @@ class SubstrateClient
46
48
  "id" => @request_id
47
49
  }
48
50
 
49
- while @callbacks.nil?
50
- sleep(1)
51
+ data = ws_request(@url, payload)
52
+ if data["error"]
53
+ raise RpcError, data["error"]
54
+ else
55
+ data["result"]
51
56
  end
52
-
53
- @callbacks[@request_id] = proc do |data|
54
- if not subscription_callback.nil? && data["result"]
55
- @subscription_callbacks[data["result"]] = subscription_callback
56
- end
57
-
58
- callback.call data if callback
59
- end
60
- @ws.send(payload.to_json)
61
- @request_id += 1
62
57
  end
63
58
 
64
- def init_runtime(block_hash=nil, &callback)
59
+ def init_runtime(block_hash=nil)
65
60
  # set current runtime spec version
66
- self.state_get_runtime_version(block_hash) do |runtime_version|
67
- @spec_version = runtime_version["specVersion"]
68
- Scale::TypeRegistry.instance.spec_version = @spec_version
69
-
70
- # set current metadata
71
- self.get_metadata(block_hash) do |metadata|
72
- @metadata = metadata
73
- Scale::TypeRegistry.instance.metadata = @metadata.value
74
- callback.call
75
- end
76
- end
77
- end
61
+ runtime_version = self.state_getRuntimeVersion(block_hash)
62
+ @spec_version = runtime_version["specVersion"]
63
+ Scale::TypeRegistry.instance.spec_version = @spec_version
78
64
 
79
- def do_init_runtime(block_hash, &callback)
65
+ # set current metadata
66
+ @metadata = self.get_metadata(block_hash)
67
+ Scale::TypeRegistry.instance.metadata = @metadata.value
68
+ true
80
69
  end
81
70
 
82
- def invoke(method, params, callback)
83
- request method, params, callback: proc { |data|
84
- if data["error"]
85
- Thread.main.raise RpcError, data["error"]
86
- else
87
- callback.call data["result"] unless callback.nil?
88
- end
89
- }
90
- end
91
-
92
- def rpc_method(method_name)
93
- Helper.real_method_name(method_name.to_s)
71
+ def invoke(method, *params)
72
+ # params.reject! { |param| param.nil? }
73
+ request(method, params)
94
74
  end
95
75
 
96
76
  # ################################################
97
77
  # origin rpc methods
98
78
  # ################################################
99
- def method_missing(method, args, &callback)
100
- rpc_method = Helper.real_method_name(method)
101
- invoke rpc_method, args, callback
102
- end
103
-
104
- def state_get_runtime_version(block_hash=nil, &callback)
105
- invoke rpc_method(__method__), [ block_hash ], callback
106
- end
107
-
108
- def rpc_methods(&callback)
109
- invoke rpc_method(__method__), [], callback
110
- end
111
-
112
- def chain_get_head(&callback)
113
- invoke rpc_method(__method__), [], callback
114
- end
115
-
116
- def chain_get_finalised_head(&callback)
117
- invoke rpc_method(__method__), [], callback
118
- end
119
-
120
- def chain_get_header(block_hash = nil, &callback)
121
- invoke rpc_method(__method__), [ block_hash ], callback
122
- end
123
-
124
- def chain_get_block(block_hash = nil, &callback)
125
- invoke rpc_method(__method__), [ block_hash ], callback
126
- end
127
-
128
- def chain_get_block_hash(block_id, &callback)
129
- invoke rpc_method(__method__), [ block_id ], callback
130
- end
131
-
132
- def chain_get_runtime_version(block_hash = nil, &callback)
133
- invoke rpc_method(__method__), [ block_hash ], callback
134
- end
135
-
136
- def state_get_metadata(block_hash = nil, &callback)
137
- invoke rpc_method(__method__), [ block_hash ], callback
138
- end
139
-
140
- def state_get_storage(storage_key, block_hash = nil, &callback)
141
- invoke rpc_method(__method__), [ storage_key, block_hash ], callback
142
- end
143
-
144
- def system_name(&callback)
145
- invoke rpc_method(__method__), [], callback
146
- end
147
-
148
- def system_version(&callback)
149
- invoke rpc_method(__method__), [], callback
150
- end
151
-
152
- def chain_subscribe_all_heads(&callback)
153
- request rpc_method(__method__), [], subscription_callback: callback
154
- end
155
-
156
- def chain_unsubscribe_all_heads(subscription)
157
- invoke rpc_method(__method__), [ subscription ], nil
158
- end
159
-
160
- def chain_subscribe_new_heads(&callback)
161
- request rpc_method(__method__), [], subscription_callback: callback
162
- end
163
-
164
- def chain_unsubscribe_new_heads(subscription)
165
- invoke rpc_method(__method__), [ subscription ], nil
166
- end
167
-
168
- def chain_subscribe_finalized_heads(&callback)
169
- request rpc_method(__method__), [], subscription_callback: callback
170
- end
171
-
172
- def chain_unsubscribe_finalized_heads(subscription)
173
- invoke rpc_method(__method__), [ subscription ], nil
174
- end
175
-
176
- def state_subscribe_runtime_version(&callback)
177
- request rpc_method(__method__), [], subscription_callback: callback
178
- end
179
-
180
- def state_unsubscribe_runtime_version(subscription)
181
- invoke rpc_method(__method__), [ subscription ], nil
182
- end
183
-
184
- def state_subscribe_storage(keys, &callback)
185
- request rpc_method(__method__), [keys], subscription_callback: callback
186
- end
187
-
188
- def state_unsubscribe_storage(subscription)
189
- invoke rpc_method(__method__), [ subscription ], nil
79
+ def method_missing(method, *args)
80
+ invoke method, *args
190
81
  end
191
82
 
192
83
  # ################################################
193
84
  # custom methods based on origin rpc methods
194
85
  # ################################################
195
- def method_list(&callback)
196
- self.rpc_methods do |result|
197
- callback.call result["methods"].map(&:underscore)
198
- end
199
- end
200
-
201
- def get_block_number(block_hash, &callback)
202
- self.chain_get_header(block_hash) do |header|
203
- callback.call header["number"].to_i(16)
204
- end
86
+ def methods
87
+ invoke("rpc_methods")["methods"]
205
88
  end
206
89
 
207
- def get_metadata(block_hash=nil, &callback)
208
- self.state_get_metadata(block_hash) do |hex|
209
- callback.call Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
210
- end
90
+ def get_block_number(block_hash)
91
+ header = self.chain_getHeader(block_hash)
92
+ header["number"].to_i(16)
211
93
  end
212
94
 
213
- def get_block(block_hash=nil, &callback)
214
- self.init_runtime block_hash do
215
- self.chain_get_block(block_hash) do |block|
216
- block = Helper.decode_block block
217
- callback.call block
218
- end
219
- end
95
+ def get_metadata(block_hash=nil)
96
+ hex = self.state_getMetadata(block_hash)
97
+ Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
220
98
  end
221
99
 
222
- def get_block_events(block_hash=nil, &callback)
223
- self.init_runtime(block_hash) do
224
- storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
225
- self.state_get_storage storage_key, block_hash do |events_data|
226
- scale_bytes = Scale::Bytes.new(events_data)
227
- events = Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
228
- callback.call events
229
- end
230
- end
100
+ def get_block(block_hash=nil)
101
+ self.init_runtime(block_hash)
102
+ block = self.chain_getBlock(block_hash)
103
+ SubstrateClient::Helper.decode_block(block)
231
104
  end
232
105
 
233
- def subscribe_block_events(&callback)
234
- self.chain_subscribe_finalized_heads do |data|
106
+ def get_block_events(block_hash=nil)
107
+ self.init_runtime(block_hash)
235
108
 
236
- block_number = data["params"]["result"]["number"].to_i(16) - 1
237
- block_hash = data["params"]["result"]["parentHash"]
109
+ storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
110
+ events_data = state_getStorage storage_key, block_hash
238
111
 
239
- EM.defer(
240
-
241
- proc {
242
- self.get_block_events block_hash do |events|
243
- begin
244
- result = { block_number: block_number, events: events }
245
- callback.call result
246
- rescue => ex
247
- SubstrateClient.logger.error ex.message
248
- SubstrateClient.logger.error ex.backtrace.join("\n")
249
- end
250
- end
251
- },
112
+ scale_bytes = Scale::Bytes.new(events_data)
113
+ Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
114
+ end
252
115
 
253
- proc { |result|
254
- },
116
+ # Plain: client.get_storage("Sudo", "Key")
117
+ # Plain: client.get_storage("Balances", "TotalIssuance")
118
+ # Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
119
+ # DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
120
+ def get_storage(module_name, storage_name, params = nil, block_hash = nil)
121
+ self.init_runtime(block_hash)
255
122
 
256
- proc { |e|
257
- SubstrateClient.logger.error e
258
- }
123
+ storage_key, return_type = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
259
124
 
260
- )
261
- end
125
+ result = self.state_getStorage(storage_key, block_hash)
126
+ return unless result
127
+ Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
262
128
  end
263
129
 
264
- def get_storage(module_name, storage_name, params = nil, block_hash = nil, &callback)
265
- self.init_runtime(block_hash) do
266
- storage_hash, return_type = Helper.generate_storage_hash_from_metadata(@metadata, module_name, storage_name, params)
267
- self.state_get_storage(storage_hash, block_hash) do |result|
268
- if result
269
- storage = Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
270
- callback.call storage
271
- else
272
- callback.call nil
273
- end
274
- end
275
- end
130
+ def generate_storage_key(module_name, storage_name, params = nil, block_hash = nil)
131
+ self.init_runtime(block_hash)
132
+ SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
276
133
  end
277
134
 
278
- def compose_call(module_name, call_name, params, block_hash=nil, &callback)
279
- self.init_runtime(block_hash) do
280
- hex = Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
281
- callback.call hex
282
- end
135
+ # compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
136
+ def compose_call(module_name, call_name, params, block_hash=nil)
137
+ self.init_runtime(block_hash)
138
+ SubstrateClient::Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
283
139
  end
284
140
 
285
- private
286
- def init_ws
287
- @ws = Websocket.new(@url,
288
-
289
- onopen: proc do |event|
290
- @callbacks = {}
291
- @subscription_callbacks = {}
292
- @onopen.call event if not @onopen.nil?
293
- end,
294
-
295
- onmessage: proc do |event|
296
- if event.data.include?("jsonrpc")
297
- begin
298
- data = JSON.parse event.data
299
-
300
- if data["params"]
301
- if @subscription_callbacks[data["params"]["subscription"]]
302
- @subscription_callbacks[data["params"]["subscription"]].call data
303
- end
304
- else
305
- @callbacks[data["id"]].call data
306
- @callbacks.delete(data["id"])
307
- end
308
-
309
- rescue => ex
310
- SubstrateClient.logger.error ex.message
311
- SubstrateClient.logger.error ex.backtrace.join("\n")
312
- end
313
- end
314
- end
315
-
316
- )
141
+ def generate_storage_hash_from_data(storage_hex_data)
142
+ "0x" + Crypto.blake2_256(Scale::Bytes.new(storage_hex_data).bytes)
317
143
  end
318
144
 
319
145
  end
@@ -1,3 +1,3 @@
1
1
  class SubstrateClient
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
@@ -36,10 +36,10 @@ Gem::Specification.new do |spec|
36
36
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
37
  spec.require_paths = ["lib"]
38
38
 
39
- spec.add_dependency "faye-websocket", "~> 0.10.9"
40
- spec.add_dependency "eventmachine", "~> 1.2.7"
41
39
  spec.add_dependency "activesupport", "~> 5.2.4"
42
- spec.add_dependency "scale.rb", "~> 0.2.5"
40
+ spec.add_dependency "scale.rb", "~> 0.2.11"
41
+ spec.add_dependency "kontena-websocket-client", "~> 0.1.1"
42
+ spec.add_dependency "substrate_common.rb", "~> 0.1.9"
43
43
 
44
44
  spec.add_development_dependency "bundler", "~> 1.17"
45
45
  spec.add_development_dependency "rake", ">= 12.3.3"
metadata CHANGED
@@ -1,71 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: substrate_client.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wu Minzhe
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-11 00:00:00.000000000 Z
11
+ date: 2020-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: faye-websocket
14
+ name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.10.9
19
+ version: 5.2.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.10.9
26
+ version: 5.2.4
27
27
  - !ruby/object:Gem::Dependency
28
- name: eventmachine
28
+ name: scale.rb
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.2.7
33
+ version: 0.2.11
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.2.7
40
+ version: 0.2.11
41
41
  - !ruby/object:Gem::Dependency
42
- name: activesupport
42
+ name: kontena-websocket-client
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 5.2.4
47
+ version: 0.1.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 5.2.4
54
+ version: 0.1.1
55
55
  - !ruby/object:Gem::Dependency
56
- name: scale.rb
56
+ name: substrate_common.rb
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.2.5
61
+ version: 0.1.9
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.2.5
68
+ version: 0.1.9
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -146,9 +146,6 @@ files:
146
146
  - lib/helper.rb
147
147
  - lib/substrate_client.rb
148
148
  - lib/substrate_client/version.rb
149
- - lib/substrate_client_sync.rb
150
- - lib/timeout_queue.rb
151
- - lib/websocket.rb
152
149
  - substrate_client.gemspec
153
150
  homepage: https://github.com/itering/substrate_client.rb
154
151
  licenses:
@@ -1,190 +0,0 @@
1
- def ws_request(url, payload)
2
- queue = TimeoutQueue.new
3
-
4
- thread = Thread.new do
5
- EM.run do
6
- ws = Faye::WebSocket::Client.new(url)
7
-
8
- ws.on :open do |event|
9
- ws.send(payload.to_json)
10
- end
11
-
12
- ws.on :message do |event|
13
- if event.data.include?("jsonrpc")
14
- queue << JSON.parse(event.data)
15
- ws.close(3001, "data received")
16
- Thread.kill thread
17
- end
18
- end
19
-
20
- ws.on :close do |event|
21
- ws = nil
22
- end
23
- end
24
- end
25
-
26
- queue.pop true, 10
27
- rescue ThreadError => ex
28
- raise SubstrateClientSync::RpcTimeout
29
- end
30
-
31
- class SubstrateClientSync
32
- class RpcError < StandardError; end
33
- class RpcTimeout < StandardError; end
34
-
35
- attr_accessor :spec_name, :spec_version, :metadata
36
-
37
- def initialize(url, spec_name: nil)
38
- @url = url
39
- @request_id = 1
40
- @spec_name = spec_name
41
- Scale::TypeRegistry.instance.load(spec_name)
42
- end
43
-
44
- def request(method, params)
45
- payload = {
46
- "jsonrpc" => "2.0",
47
- "method" => method,
48
- "params" => params,
49
- "id" => @request_id
50
- }
51
-
52
- data = ws_request(@url, payload)
53
- if data["error"]
54
- raise RpcError, data["error"]
55
- else
56
- data["result"]
57
- end
58
- end
59
-
60
- def init_runtime(block_hash=nil)
61
- # set current runtime spec version
62
- runtime_version = self.state_get_runtime_version(block_hash)
63
- @spec_version = runtime_version["specVersion"]
64
- Scale::TypeRegistry.instance.spec_version = @spec_version
65
-
66
- # set current metadata
67
- @metadata = self.get_metadata(block_hash)
68
- Scale::TypeRegistry.instance.metadata = @metadata.value
69
- true
70
- end
71
-
72
- def invoke(method, *params)
73
- # params.reject! { |param| param.nil? }
74
- request(method, params)
75
- end
76
-
77
- def rpc_method(method_name)
78
- SubstrateClient::Helper.real_method_name(method_name.to_s)
79
- end
80
-
81
- # ################################################
82
- # origin rpc methods
83
- # ################################################
84
- def method_missing(method, *args)
85
- rpc_method = SubstrateClient::Helper.real_method_name(method)
86
- invoke rpc_method, *args
87
- end
88
-
89
- def state_get_runtime_version(block_hash=nil)
90
- invoke rpc_method(__method__), block_hash
91
- end
92
-
93
- def rpc_methods
94
- invoke rpc_method(__method__)
95
- end
96
-
97
- def chain_get_head
98
- invoke rpc_method(__method__)
99
- end
100
-
101
- def chain_get_finalised_head
102
- invoke rpc_method(__method__)
103
- end
104
-
105
- def chain_get_header(block_hash = nil)
106
- invoke rpc_method(__method__), block_hash
107
- end
108
-
109
- def chain_get_block(block_hash = nil)
110
- invoke rpc_method(__method__), block_hash
111
- end
112
-
113
- def chain_get_block_hash(block_id)
114
- invoke rpc_method(__method__), block_id
115
- end
116
-
117
- def chain_get_runtime_version(block_hash = nil)
118
- invoke rpc_method(__method__), block_hash
119
- end
120
-
121
- def state_get_metadata(block_hash = nil)
122
- invoke rpc_method(__method__), block_hash
123
- end
124
-
125
- def state_get_storage(storage_key, block_hash = nil)
126
- invoke rpc_method(__method__), storage_key, block_hash
127
- end
128
-
129
- def system_name
130
- invoke rpc_method(__method__)
131
- end
132
-
133
- def system_version
134
- invoke rpc_method(__method__)
135
- end
136
-
137
- # ################################################
138
- # custom methods based on origin rpc methods
139
- # ################################################
140
- def method_list
141
- self.rpc_methods["methods"].map(&:underscore)
142
- end
143
-
144
- def get_block_number(block_hash)
145
- header = self.chain_get_header(block_hash)
146
- header["number"].to_i(16)
147
- end
148
-
149
- def get_metadata(block_hash=nil)
150
- hex = self.state_get_metadata(block_hash)
151
- Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
152
- end
153
-
154
- def get_block(block_hash=nil)
155
- self.init_runtime(block_hash)
156
- block = self.chain_get_block(block_hash)
157
- SubstrateClient::Helper.decode_block(block)
158
- end
159
-
160
- def get_block_events(block_hash=nil)
161
- self.init_runtime(block_hash)
162
-
163
- storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
164
- events_data = state_get_storage storage_key, block_hash
165
-
166
- scale_bytes = Scale::Bytes.new(events_data)
167
- Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
168
- end
169
-
170
- # Plain: client.get_storage("Sudo", "Key")
171
- # Plain: client.get_storage("Balances", "TotalIssuance")
172
- # Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
173
- # DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
174
- def get_storage(module_name, storage_name, params = nil, block_hash = nil)
175
- self.init_runtime(block_hash)
176
-
177
- storage_hash, return_type = SubstrateClient::Helper.generate_storage_hash_from_metadata(@metadata, module_name, storage_name, params)
178
-
179
- result = self.state_get_storage(storage_hash, block_hash)
180
- return unless result
181
- Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
182
- end
183
-
184
- # compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
185
- def compose_call(module_name, call_name, params, block_hash=nil)
186
- self.init_runtime(block_hash)
187
- SubstrateClient::Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
188
- end
189
-
190
- end
@@ -1,34 +0,0 @@
1
- # https://vaneyckt.io/posts/ruby_concurrency_building_a_timeout_queue/
2
- class TimeoutQueue
3
- def initialize
4
- @elems = []
5
- @mutex = Mutex.new
6
- @cond_var = ConditionVariable.new
7
- end
8
-
9
- def <<(elem)
10
- @mutex.synchronize do
11
- @elems << elem
12
- @cond_var.signal
13
- end
14
- end
15
-
16
- def pop(blocking = true, timeout = nil)
17
- @mutex.synchronize do
18
- if blocking
19
- if timeout.nil?
20
- while @elems.empty?
21
- @cond_var.wait(@mutex)
22
- end
23
- else
24
- timeout_time = Time.now.to_f + timeout
25
- while @elems.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0
26
- @cond_var.wait(@mutex, remaining_time)
27
- end
28
- end
29
- end
30
- raise ThreadError, 'queue empty' if @elems.empty?
31
- @elems.shift
32
- end
33
- end
34
- end
@@ -1,79 +0,0 @@
1
- require "faye/websocket"
2
- require "eventmachine"
3
-
4
- class SubstrateClient::Websocket
5
- HEARTBEAT_INTERVAL = 3
6
- RECONNECT_INTERVAL = 3
7
-
8
- def initialize(url, onopen: nil, onmessage: nil)
9
- @url = url
10
- @onopen = onopen || proc { p [:open] }
11
- @onmessage = onmessage || proc { |event| p [:message, event.data] }
12
-
13
- @thread = Thread.new do
14
- EM.run do
15
- start_connection
16
- end
17
- SubstrateClient.logger.info "Event loop stopped"
18
- end
19
- @heartbeat_thread = start_heartbeat
20
- end
21
-
22
- def start_connection
23
- SubstrateClient.logger.info "Start to connect"
24
- @close = false
25
- @missed_heartbeats = 0
26
- @ping_id = 0
27
- @ws = Faye::WebSocket::Client.new(@url)
28
- @ws.on :open do |event|
29
- @do_heartbeat = true
30
- @onopen.call event
31
- end
32
-
33
- @ws.on :message do |event|
34
- @onmessage.call event
35
- end
36
-
37
- @ws.on :close do |event|
38
- # p [:close, event.code, event.reason]
39
- if @close == false
40
- @do_heartbeat = false
41
- sleep RECONNECT_INTERVAL
42
- start_connection
43
- end
44
- end
45
-
46
- end
47
-
48
- def start_heartbeat
49
- Thread.new do
50
- loop do
51
- send_heartbeat if @do_heartbeat
52
- sleep HEARTBEAT_INTERVAL
53
- end
54
- end
55
- end
56
-
57
- def send_heartbeat
58
- if @missed_heartbeats < 2
59
- # puts "ping_#{@ping_id}"
60
- @ws.ping @ping_id.to_s do
61
- # puts "pong"
62
- @missed_heartbeats -= 1
63
- end
64
- @missed_heartbeats += 1
65
- @ping_id += 1
66
- end
67
- end
68
-
69
- def send(message)
70
- @ws.send message
71
- end
72
-
73
- def close
74
- @close = true
75
- Thread.kill @heartbeat_thread
76
- Thread.kill @thread
77
- @ws = nil
78
- end
79
- end