bitflyer 1.0.0 → 1.4.0

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: e017b1fb5befa1653ab22b3333a94e9d59e2168ea7f0d5dd58acdcb143dd3eea
4
- data.tar.gz: d06a630e375806601b5cdc0d1d23f0c5d5574bc847d23e5156092e20f101baab
3
+ metadata.gz: 5b29fa40669e3131fb53fa6f878fd53b0a0a9fb8e4e2f0e29923cd85288b95c6
4
+ data.tar.gz: 10dfdb3120b030ce9aeca642bfc700be01ec8824270e998dae3f53d845b48fa5
5
5
  SHA512:
6
- metadata.gz: 58f8ca8dfe138dd4d0d6a22883bca134ef94f6abef0474f6e3a30dc9c4ae1a123cdd88a67a32daa6b940e814872535f8140472683ac6b8ccf9dcb98f2f3d573a
7
- data.tar.gz: 675d662fe2f2667c3d733a82a04af407891132d5afd31fadd43a10c95f32d36a65fde231fb45297e5122aba1341c1e4677e2511faf4d44a664aa39e33f7b8369
6
+ metadata.gz: c4cf3537edc91a5de46622a5fb0e7082f17364317f9fea569bf6a32d277ef0de0fd3b35e8b0c13a0e09713fef3c6a8f5f7ebd1c2ddf56edecf6cf0006dc2ca8f
7
+ data.tar.gz: d04b272d9f7f268c1b683db172a47c27fea800db2afb9d4513b4aaef746d1fd5fa5dfe317f7c1590db6832c0939548b3c1de8b22bfd67558efda854b31690414
@@ -0,0 +1,51 @@
1
+ {
2
+ "files": [
3
+ "README.md"
4
+ ],
5
+ "imageSize": 100,
6
+ "commit": false,
7
+ "contributors": [
8
+ {
9
+ "login": "yemartin",
10
+ "name": "Yves-Eric Martin",
11
+ "avatar_url": "https://avatars.githubusercontent.com/u/139002?v=4",
12
+ "profile": "https://github.com/yemartin",
13
+ "contributions": [
14
+ "code"
15
+ ]
16
+ },
17
+ {
18
+ "login": "unhappychoice",
19
+ "name": "Yuji Ueki",
20
+ "avatar_url": "https://avatars.githubusercontent.com/u/5608948?v=4",
21
+ "profile": "http://blog.unhappychoice.com",
22
+ "contributions": [
23
+ "code"
24
+ ]
25
+ },
26
+ {
27
+ "login": "fkshom",
28
+ "name": "Shoma FUKUDA",
29
+ "avatar_url": "https://avatars.githubusercontent.com/u/1889118?v=4",
30
+ "profile": "https://github.com/fkshom",
31
+ "contributions": [
32
+ "code"
33
+ ]
34
+ },
35
+ {
36
+ "login": "shidetake",
37
+ "name": "shidetake",
38
+ "avatar_url": "https://avatars.githubusercontent.com/u/1559558?v=4",
39
+ "profile": "http://shidetake.com",
40
+ "contributions": [
41
+ "code"
42
+ ]
43
+ }
44
+ ],
45
+ "contributorsPerLine": 7,
46
+ "projectName": "bitflyer",
47
+ "projectOwner": "unhappychoice",
48
+ "repoType": "github",
49
+ "repoHost": "https://github.com",
50
+ "skipCi": true
51
+ }
data/.circleci/config.yml CHANGED
@@ -1,5 +1,10 @@
1
1
  version: 2.1
2
2
 
3
+ update_bundler: &update_bundler
4
+ run:
5
+ name: update bundler
6
+ command: gem update bundler
7
+
3
8
  bundle_install: &bundle_install
4
9
  run:
5
10
  name: bundle install
@@ -12,10 +17,11 @@ restore_bundle_cache: &restore_bundle_cache
12
17
  jobs:
13
18
  build:
14
19
  docker:
15
- - image: circleci/ruby
20
+ - image: circleci/ruby:2.7.2
16
21
  steps:
17
22
  - checkout
18
23
  - *restore_bundle_cache
24
+ - *update_bundler
19
25
  - *bundle_install
20
26
  - save_cache:
21
27
  key: cache-bundler-{{ checksum "Gemfile.lock" }}
@@ -23,18 +29,20 @@ jobs:
23
29
  - vendor/bundle
24
30
  rubocop:
25
31
  docker:
26
- - image: circleci/ruby
32
+ - image: circleci/ruby:2.7.2
27
33
  steps:
28
34
  - checkout
29
35
  - *restore_bundle_cache
36
+ - *update_bundler
30
37
  - *bundle_install
31
38
  - run: bundle exec rubocop
32
39
  rspec:
33
40
  docker:
34
- - image: circleci/ruby
41
+ - image: circleci/ruby:2.7.2
35
42
  steps:
36
43
  - checkout
37
44
  - *restore_bundle_cache
45
+ - *update_bundler
38
46
  - *bundle_install
39
47
  - run: bundle exec rspec
40
48
  workflows:
@@ -0,0 +1,23 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "12:00"
8
+ timezone: Asia/Tokyo
9
+ open-pull-requests-limit: 20
10
+ reviewers:
11
+ - unhappychoice
12
+ allow:
13
+ - dependency-type: direct
14
+ - dependency-type: indirect
15
+ ignore:
16
+ - dependency-name: rubocop
17
+ versions:
18
+ - 1.12.0
19
+ - 1.12.1
20
+ - dependency-name: regexp_parser
21
+ versions:
22
+ - 2.1.0
23
+ - 2.1.1
data/.rubocop.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
+ NewCops: enable
4
5
  Include:
5
6
  - 'lib/**/*.rb'
6
7
  Style/Documentation:
data/.rubocop_todo.yml CHANGED
@@ -1,26 +1,23 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2019-07-08 01:59:32 +0900 using RuboCop version 0.72.0.
3
+ # on 2020-08-08 12:17:16 UTC using RuboCop version 0.89.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 3
9
+ # Offense count: 2
10
+ # Configuration parameters: IgnoredMethods.
10
11
  Metrics/AbcSize:
11
- Max: 29
12
+ Max: 32
12
13
 
13
14
  # Offense count: 2
14
- # Configuration parameters: CountComments.
15
+ # Configuration parameters: CountComments, CountAsOne.
15
16
  Metrics/ClassLength:
16
- Max: 150
17
-
18
- # Offense count: 1
19
- Metrics/CyclomaticComplexity:
20
- Max: 7
17
+ Max: 146
21
18
 
22
19
  # Offense count: 2
23
- # Configuration parameters: CountComments, ExcludedMethods.
20
+ # Configuration parameters: CountComments, CountAsOne, ExcludedMethods.
24
21
  Metrics/MethodLength:
25
22
  Max: 20
26
23
 
@@ -28,3 +25,11 @@ Metrics/MethodLength:
28
25
  # Configuration parameters: CountKeywordArgs.
29
26
  Metrics/ParameterLists:
30
27
  Max: 7
28
+
29
+ # Offense count: 1
30
+ # Cop supports --auto-correct.
31
+ # Configuration parameters: EnforcedStyle.
32
+ # SupportedStyles: always, always_true, never
33
+ Style/FrozenStringLiteralComment:
34
+ Exclude:
35
+ - 'bin/console'
data/Gemfile.lock CHANGED
@@ -1,56 +1,73 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bitflyer (1.0.0)
5
- faraday (~> 0.14.0)
6
- faraday_middleware (~> 0.12.0)
4
+ bitflyer (1.4.0)
5
+ faraday (>= 0.14, < 1.5)
6
+ faraday_middleware (>= 0.12, < 1.1)
7
7
  websocket-client-simple (~> 0.3.0)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- ast (2.4.0)
13
- coderay (1.1.2)
14
- diff-lcs (1.3)
12
+ ast (2.4.2)
13
+ coderay (1.1.3)
14
+ diff-lcs (1.4.4)
15
15
  event_emitter (0.2.6)
16
- faraday (0.14.0)
16
+ faraday (1.4.2)
17
+ faraday-em_http (~> 1.0)
18
+ faraday-em_synchrony (~> 1.0)
19
+ faraday-excon (~> 1.1)
20
+ faraday-net_http (~> 1.0)
21
+ faraday-net_http_persistent (~> 1.1)
17
22
  multipart-post (>= 1.2, < 3)
18
- faraday_middleware (0.12.2)
19
- faraday (>= 0.7.4, < 1.0)
20
- jaro_winkler (1.5.3)
21
- method_source (0.9.2)
23
+ ruby2_keywords (>= 0.0.4)
24
+ faraday-em_http (1.0.0)
25
+ faraday-em_synchrony (1.0.0)
26
+ faraday-excon (1.1.0)
27
+ faraday-net_http (1.0.1)
28
+ faraday-net_http_persistent (1.1.0)
29
+ faraday_middleware (1.0.0)
30
+ faraday (~> 1.0)
31
+ method_source (1.0.0)
22
32
  multipart-post (2.1.1)
23
- parallel (1.17.0)
24
- parser (2.6.3.0)
25
- ast (~> 2.4.0)
26
- pry (0.12.2)
27
- coderay (~> 1.1.0)
28
- method_source (~> 0.9.0)
33
+ parallel (1.20.1)
34
+ parser (3.0.1.1)
35
+ ast (~> 2.4.1)
36
+ pry (0.14.1)
37
+ coderay (~> 1.1)
38
+ method_source (~> 1.0)
29
39
  rainbow (3.0.0)
30
- rake (12.3.0)
31
- rspec (3.7.0)
32
- rspec-core (~> 3.7.0)
33
- rspec-expectations (~> 3.7.0)
34
- rspec-mocks (~> 3.7.0)
35
- rspec-core (3.7.1)
36
- rspec-support (~> 3.7.0)
37
- rspec-expectations (3.7.0)
40
+ rake (13.0.3)
41
+ regexp_parser (2.1.1)
42
+ rexml (3.2.5)
43
+ rspec (3.10.0)
44
+ rspec-core (~> 3.10.0)
45
+ rspec-expectations (~> 3.10.0)
46
+ rspec-mocks (~> 3.10.0)
47
+ rspec-core (3.10.1)
48
+ rspec-support (~> 3.10.0)
49
+ rspec-expectations (3.10.1)
38
50
  diff-lcs (>= 1.2.0, < 2.0)
39
- rspec-support (~> 3.7.0)
40
- rspec-mocks (3.7.0)
51
+ rspec-support (~> 3.10.0)
52
+ rspec-mocks (3.10.2)
41
53
  diff-lcs (>= 1.2.0, < 2.0)
42
- rspec-support (~> 3.7.0)
43
- rspec-support (3.7.1)
44
- rubocop (0.72.0)
45
- jaro_winkler (~> 1.5.1)
54
+ rspec-support (~> 3.10.0)
55
+ rspec-support (3.10.2)
56
+ rubocop (1.15.0)
46
57
  parallel (~> 1.10)
47
- parser (>= 2.6)
58
+ parser (>= 3.0.0.0)
48
59
  rainbow (>= 2.2.2, < 4.0)
60
+ regexp_parser (>= 1.8, < 3.0)
61
+ rexml
62
+ rubocop-ast (>= 1.5.0, < 2.0)
49
63
  ruby-progressbar (~> 1.7)
50
- unicode-display_width (>= 1.4.0, < 1.7)
51
- ruby-progressbar (1.10.1)
52
- unicode-display_width (1.6.0)
53
- websocket (1.2.8)
64
+ unicode-display_width (>= 1.4.0, < 3.0)
65
+ rubocop-ast (1.5.0)
66
+ parser (>= 3.0.1.1)
67
+ ruby-progressbar (1.11.0)
68
+ ruby2_keywords (0.0.4)
69
+ unicode-display_width (2.0.0)
70
+ websocket (1.2.9)
54
71
  websocket-client-simple (0.3.0)
55
72
  event_emitter
56
73
  websocket
@@ -60,11 +77,11 @@ PLATFORMS
60
77
 
61
78
  DEPENDENCIES
62
79
  bitflyer!
63
- bundler (~> 1.12)
80
+ bundler (~> 2.0)
64
81
  pry
65
82
  rake
66
83
  rspec
67
84
  rubocop
68
85
 
69
86
  BUNDLED WITH
70
- 1.17.3
87
+ 2.2.4
data/README.md CHANGED
@@ -1,8 +1,13 @@
1
1
  # bitflyer
2
+ <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
3
+ [![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-)
4
+ <!-- ALL-CONTRIBUTORS-BADGE:END -->
2
5
  [![Gem Version](https://badge.fury.io/rb/bitflyer.svg)](https://badge.fury.io/rb/bitflyer)
3
6
  [![Circle CI](https://circleci.com/gh/unhappychoice/bitflyer.svg?style=shield)](https://circleci.com/gh/unhappychoice/bitflyer)
4
7
  [![Code Climate](https://codeclimate.com/github/unhappychoice/bitflyer/badges/gpa.svg)](https://codeclimate.com/github/unhappychoice/bitflyer)
8
+ [![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/unhappychoice/bitflyer.svg)](https://libraries.io/github/unhappychoice/bitflyer)
5
9
  ![](http://ruby-gem-downloads-badge.herokuapp.com/bitflyer?type=total)
10
+ ![GitHub](https://img.shields.io/github/license/unhappychoice/bitflyer.svg)
6
11
 
7
12
  bitflyer is a wrapper interface of [Bitflyer lightning API](https://lightning.bitflyer.jp/docs)
8
13
 
@@ -32,20 +37,52 @@ p private_client.positions # will print your positions
32
37
 
33
38
  ### Realtime API
34
39
 
40
+ #### Public events
41
+
35
42
  Accessor format is like `{event_name}_{product_code}`.
36
43
  You can set lambda to get realtime events.
37
44
 
38
45
  `{event_name}` and `{product_code}` is defined at [client.rb](./lib/bitflyer/realtime/client.rb).
39
46
 
40
- #### Example
47
+ #### Private events
48
+
49
+ To subscribe to the private `child_order_events` and `parent_order_events`, pass your API key and secret when creating the `realtime_client`.
50
+
51
+ #### Connection status monitoring
52
+
53
+ The `ready` callback is called when the `realtime_client` is ready to receive events (after the socket connection is established, the optional authentication has succeeded, and the channels have been subscribed). If connection is lost, the `disconnected` callback is called, and reconnection is attempted automatically. When connection is restored, `ready` is called again.
54
+
55
+ #### Examples
41
56
 
57
+ For public events only:
42
58
  ```ruby
43
59
  client = Bitflyer.realtime_client
44
- client.ticker_btc_jpy = ->(json){ p json } # will print json object
60
+ client.ticker_btc_jpy = ->(json){ p json } # will print json object
45
61
  client.executions_btc_jpy = ->(json){ p json }
46
- # ...
62
+ # ...
63
+ ```
64
+
65
+ For both public and private events:
66
+ ```ruby
67
+ client = Bitflyer.realtime_client('YOUR_API_KEY', 'YOUR_API_SECRET')
68
+ # Private events:
69
+ client.child_order_events = ->(json){ p json }
70
+ client.parent_order_events = ->(json){ p json }
71
+ # Public events:
72
+ client.ticker_btc_jpy = ->(json){ p json }
73
+ client.executions_btc_jpy = ->(json){ p json }
74
+ # ...
75
+ ```
76
+
77
+ Connection monitoring:
78
+ ```ruby
79
+ client = Bitflyer.realtime_client
80
+ client.ready = -> { p "Client is ready to receive events" }
81
+ client.disconnected = ->(error) { p "Client got disconnected" }
47
82
  ```
48
83
 
84
+
85
+
49
86
  ## Contributing
50
87
 
51
88
  Bug reports and pull requests are welcome on GitHub at https://github.com/unhappychoice/bitflyer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
@@ -55,3 +92,26 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/unhapp
55
92
 
56
93
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
57
94
 
95
+
96
+ ## Contributors ✨
97
+
98
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
99
+
100
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
101
+ <!-- prettier-ignore-start -->
102
+ <!-- markdownlint-disable -->
103
+ <table>
104
+ <tr>
105
+ <td align="center"><a href="https://github.com/yemartin"><img src="https://avatars.githubusercontent.com/u/139002?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yves-Eric Martin</b></sub></a><br /><a href="https://github.com/unhappychoice/bitflyer/commits?author=yemartin" title="Code">💻</a></td>
106
+ <td align="center"><a href="http://blog.unhappychoice.com"><img src="https://avatars.githubusercontent.com/u/5608948?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yuji Ueki</b></sub></a><br /><a href="https://github.com/unhappychoice/bitflyer/commits?author=unhappychoice" title="Code">💻</a></td>
107
+ <td align="center"><a href="https://github.com/fkshom"><img src="https://avatars.githubusercontent.com/u/1889118?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shoma FUKUDA</b></sub></a><br /><a href="https://github.com/unhappychoice/bitflyer/commits?author=fkshom" title="Code">💻</a></td>
108
+ <td align="center"><a href="http://shidetake.com"><img src="https://avatars.githubusercontent.com/u/1559558?v=4?s=100" width="100px;" alt=""/><br /><sub><b>shidetake</b></sub></a><br /><a href="https://github.com/unhappychoice/bitflyer/commits?author=shidetake" title="Code">💻</a></td>
109
+ </tr>
110
+ </table>
111
+
112
+ <!-- markdownlint-restore -->
113
+ <!-- prettier-ignore-end -->
114
+
115
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
116
+
117
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
data/bitflyer.gemspec CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_dependency 'faraday', '~> 0.14.0'
21
- spec.add_dependency 'faraday_middleware', '~> 0.12.0'
20
+ spec.add_dependency 'faraday', '>= 0.14', '< 1.5'
21
+ spec.add_dependency 'faraday_middleware', '>= 0.12', '< 1.1'
22
22
  spec.add_dependency 'websocket-client-simple', '~> 0.3.0'
23
- spec.add_development_dependency 'bundler', '~> 1.12'
23
+ spec.add_development_dependency 'bundler', '~> 2.0'
24
24
  spec.add_development_dependency 'pry'
25
25
  spec.add_development_dependency 'rake'
26
26
  spec.add_development_dependency 'rspec'
data/lib/bitflyer.rb CHANGED
@@ -5,8 +5,8 @@ require 'bitflyer/http'
5
5
  require 'bitflyer/realtime'
6
6
 
7
7
  module Bitflyer
8
- def realtime_client
9
- Bitflyer::Realtime::Client.new
8
+ def realtime_client(key = nil, secret = nil)
9
+ Bitflyer::Realtime::Client.new(key, secret)
10
10
  end
11
11
 
12
12
  def http_public_client
data/lib/bitflyer/http.rb CHANGED
@@ -36,7 +36,7 @@ module Bitflyer
36
36
 
37
37
  timestamp = Time.now.to_i.to_s
38
38
  method = env[:method].to_s.upcase
39
- path = env[:url].path + (env[:url].query ? '?' + env[:url].query : '')
39
+ path = env[:url].path + (env[:url].query ? "?#{env[:url].query}" : '')
40
40
  body = env[:body] || ''
41
41
  signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @secret, timestamp + method + path + body)
42
42
  env[:request_headers]['ACCESS-KEY'] = @key if @key
@@ -3,7 +3,7 @@
3
3
  module Bitflyer
4
4
  module HTTP
5
5
  module Private
6
- class Client
6
+ class Client # rubocop:disable Metrics/ClassLength
7
7
  def initialize(key, secret)
8
8
  @connection = Connection.new(key, secret)
9
9
  end
@@ -164,6 +164,16 @@ module Bitflyer
164
164
  @connection.get('/v1/me/getexecutions', query).body
165
165
  end
166
166
 
167
+ def balance_history(currency_code: nil, count: nil, before: nil, after: nil)
168
+ query = {
169
+ currency_code: currency_code,
170
+ count: count,
171
+ before: before,
172
+ after: after
173
+ }.delete_if { |_, v| v.nil? }
174
+ @connection.get('/v1/me/getbalancehistory', query).body
175
+ end
176
+
167
177
  def positions(product_code: 'FX_BTC_JPY')
168
178
  @connection.get('/v1/me/getpositions', product_code: product_code).body
169
179
  end
@@ -24,8 +24,14 @@ module Bitflyer
24
24
  @connection.get('/v1/ticker', product_code: product_code).body
25
25
  end
26
26
 
27
- def executions(product_code: 'BTC_JPY')
28
- @connection.get('/v1/executions', product_code: product_code).body
27
+ def executions(product_code: 'BTC_JPY', count: nil, before: nil, after: nil)
28
+ query = {
29
+ product_code: product_code,
30
+ count: count,
31
+ before: before,
32
+ after: after
33
+ }.delete_if { |_, v| v.nil? }
34
+ @connection.get('/v1/executions', query).body
29
35
  end
30
36
 
31
37
  def chats(from_date: (Time.now - 5 * 24 * 60 * 60))
@@ -4,13 +4,18 @@ require_relative './websocket'
4
4
 
5
5
  module Bitflyer
6
6
  module Realtime
7
- EVENT_NAMES = %w[lightning_board_snapshot lightning_board lightning_ticker lightning_executions].freeze
8
- MARKETS = %w[BTC_JPY FX_BTC_JPY ETH_BTC BCH_BTC BTCJPY_MAT3M BTCJPY_MAT1WK BTCJPY_MAT2WK].freeze
9
- CHANNEL_NAMES = EVENT_NAMES.product(MARKETS).map { |e, m| e + '_' + m }
7
+ PUBLIC_EVENT_NAMES = %w[lightning_board_snapshot lightning_board lightning_ticker lightning_executions].freeze
8
+ MARKETS = %w[BTC_JPY XRP_JPY ETH_JPY XLM_JPY MONA_JPY ETH_BTC BCH_BTC FX_BTC_JPY BTCJPY_MAT1WK BTCJPY_MAT2WK
9
+ BTCJPY_MAT3M].freeze
10
+ PUBLIC_CHANNEL_NAMES = PUBLIC_EVENT_NAMES.product(MARKETS).map { |e, m| "#{e}_#{m}" }.freeze
11
+ PRIVATE_CHANNEL_NAMES = %w[child_order_events parent_order_events].freeze
12
+ CHANNEL_NAMES = (PUBLIC_CHANNEL_NAMES + PRIVATE_CHANNEL_NAMES).freeze
10
13
 
11
14
  SOCKET_HOST = 'https://io.lightstream.bitflyer.com'
12
15
 
13
16
  class Client
17
+ extend Forwardable
18
+ def_delegators :@websocket_client, :ready=, :disconnected=
14
19
  attr_accessor :websocket_client, :ping_interval, :ping_timeout, :last_ping_at, :last_pong_at
15
20
 
16
21
  Realtime::CHANNEL_NAMES.each do |channel_name|
@@ -19,8 +24,8 @@ module Bitflyer
19
24
  end
20
25
  end
21
26
 
22
- def initialize
23
- @websocket_client = Bitflyer::Realtime::WebSocketClient.new(host: SOCKET_HOST)
27
+ def initialize(key = nil, secret = nil)
28
+ @websocket_client = Bitflyer::Realtime::WebSocketClient.new(host: SOCKET_HOST, key: key, secret: secret)
24
29
  end
25
30
  end
26
31
  end
@@ -2,54 +2,55 @@
2
2
 
3
3
  require 'websocket-client-simple'
4
4
  require 'json'
5
+ require 'openssl'
5
6
 
6
7
  module Bitflyer
7
8
  module Realtime
8
9
  class WebSocketClient
9
- attr_accessor :websocket_client, :channel_name, :channel_callbacks, :ping_interval, :ping_timeout,
10
- :last_ping_at, :last_pong_at, :error
10
+ attr_accessor :websocket_client, :channel_names, :channel_callbacks, :ping_interval, :ping_timeout,
11
+ :last_ping_at, :last_pong_at, :ready, :disconnected
11
12
 
12
- def initialize(host:, debug: false)
13
+ def initialize(host:, key:, secret:, debug: false)
13
14
  @host = host
15
+ @key = key
16
+ @secret = secret
14
17
  @debug = debug
15
- @error = nil
16
18
  @channel_names = []
17
19
  @channel_callbacks = {}
18
20
  connect
21
+ start_monitoring
19
22
  end
20
23
 
21
24
  def subscribe(channel_name:, &block)
22
25
  debug_log "Subscribe #{channel_name}"
23
26
  @channel_names = (@channel_names + [channel_name]).uniq
24
27
  @channel_callbacks[channel_name] = block
25
- websocket_client.send "42#{['subscribe', channel_name].to_json}"
28
+ @websocket_client.send "42#{['subscribe', channel_name].to_json}"
26
29
  end
27
30
 
28
31
  def connect
29
32
  @websocket_client = WebSocket::Client::Simple.connect "#{@host}/socket.io/?transport=websocket"
30
33
  this = self
34
+ @websocket_client.on(:message) { |payload| this.handle_message(payload: payload) }
35
+ @websocket_client.on(:error) { |error| this.handle_error(error: error) }
36
+ @websocket_client.on(:close) { |error| this.handle_close(error: error) }
37
+ rescue SocketError => e
38
+ puts e
39
+ puts e.backtrace.join("\n")
40
+ end
31
41
 
42
+ def start_monitoring
32
43
  Thread.new do
33
44
  loop do
34
45
  sleep 1
35
46
  if @websocket_client&.open?
36
47
  send_ping
37
48
  wait_pong
49
+ else
50
+ reconnect
38
51
  end
39
52
  end
40
53
  end
41
-
42
- Thread.new do
43
- loop do
44
- sleep 1
45
- next unless @error
46
-
47
- reconnect
48
- end
49
- end
50
-
51
- @websocket_client.on(:message) { |payload| this.handle_message(payload: payload) }
52
- @websocket_client.on(:error) { |error| this.handle_error(error: error) }
53
54
  end
54
55
 
55
56
  def send_ping
@@ -63,34 +64,30 @@ module Bitflyer
63
64
 
64
65
  def wait_pong
65
66
  return unless @last_pong_at && @ping_timeout
66
- return unless Time.now.to_i - @last_pong_at > @ping_timeout / 1000
67
+ return unless Time.now.to_i - @last_pong_at > (@ping_interval + @ping_timeout) / 1000
67
68
 
68
69
  debug_log 'Timed out waiting pong'
69
70
  @websocket_client.close
70
71
  end
71
72
 
72
73
  def reconnect
73
- return unless @error
74
+ return if @websocket_client&.open?
74
75
 
75
76
  debug_log 'Reconnecting...'
76
77
 
77
- @error = nil
78
78
  @websocket_client.close if @websocket_client.open?
79
79
  connect
80
- @channel_names.each do |channel_name|
81
- debug_log "42#{{ subscribe: channel_name }.to_json}"
82
- websocket_client.send "42#{['subscribe', channel_name].to_json}"
83
- end
84
80
  end
85
81
 
86
82
  def handle_error(error:)
87
83
  debug_log error
88
84
  return unless error.is_a? Errno::ECONNRESET
89
85
 
86
+ @disconnected&.call(error)
90
87
  reconnect
91
88
  end
92
89
 
93
- def handle_message(payload:)
90
+ def handle_message(payload:) # rubocop:disable Metrics/CyclomaticComplexity
94
91
  debug_log payload.data
95
92
  return unless payload.data =~ /^\d+/
96
93
 
@@ -101,20 +98,58 @@ module Bitflyer
101
98
  when 3 then receive_pong
102
99
  when 41 then disconnect
103
100
  when 42 then emit_message(json: body)
101
+ when 430 then authenticated(json: body)
104
102
  end
105
103
  rescue StandardError => e
106
104
  puts e
107
105
  puts e.backtrace.join("\n")
108
106
  end
109
107
 
108
+ def handle_close(error:)
109
+ debug_log error
110
+ @disconnected&.call(error)
111
+ end
112
+
110
113
  def setup_by_response(json:)
111
114
  body = JSON.parse json
112
115
  @ping_interval = body['pingInterval'].to_i || 25_000
113
116
  @ping_timeout = body['pingTimeout'].to_i || 60_000
114
117
  @last_ping_at = Time.now.to_i
115
118
  @last_pong_at = Time.now.to_i
116
- channel_callbacks.each do |channel_name, _|
117
- websocket_client.send "42#{['subscribe', channel_name].to_json}"
119
+ if @key && @secret
120
+ authenticate
121
+ else
122
+ subscribe_channels
123
+ @ready&.call
124
+ end
125
+ end
126
+
127
+ def authenticate
128
+ debug_log 'Authenticate'
129
+ timestamp = Time.now.to_i
130
+ nonce = Random.new.bytes(16).unpack1('H*')
131
+ signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @secret, timestamp.to_s + nonce)
132
+ auth_params = {
133
+ api_key: @key,
134
+ timestamp: timestamp,
135
+ nonce: nonce,
136
+ signature: signature
137
+ }
138
+ @websocket_client.send "420#{['auth', auth_params].to_json}"
139
+ end
140
+
141
+ def authenticated(json:)
142
+ raise "Authentication failed: #{json}" if json != '[null]'
143
+
144
+ debug_log 'Authenticated'
145
+ subscribe_channels
146
+ @ready&.call
147
+ end
148
+
149
+ def subscribe_channels
150
+ @channel_callbacks.each do |channel_name, _|
151
+ debug_log "42#{{ subscribe: channel_name }.to_json}"
152
+ @websocket_client.send "42#{['subscribe', channel_name].to_json}"
118
153
  end
119
154
  end
120
155
 
@@ -125,7 +160,7 @@ module Bitflyer
125
160
 
126
161
  def disconnect
127
162
  debug_log 'Disconnecting from server...'
128
- @error = true
163
+ @websocket_client.close
129
164
  end
130
165
 
131
166
  def emit_message(json:)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitflyer
4
- VERSION = '1.0.0'
4
+ VERSION = '1.4.0'
5
5
  end
metadata CHANGED
@@ -1,43 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitflyer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuji Ueki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-11 00:00:00.000000000 Z
11
+ date: 2021-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.14'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: 0.14.0
22
+ version: '1.5'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '0.14'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: 0.14.0
32
+ version: '1.5'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: faraday_middleware
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - "~>"
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0.12'
40
+ - - "<"
32
41
  - !ruby/object:Gem::Version
33
- version: 0.12.0
42
+ version: '1.1'
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
- - - "~>"
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0.12'
50
+ - - "<"
39
51
  - !ruby/object:Gem::Version
40
- version: 0.12.0
52
+ version: '1.1'
41
53
  - !ruby/object:Gem::Dependency
42
54
  name: websocket-client-simple
43
55
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +70,14 @@ dependencies:
58
70
  requirements:
59
71
  - - "~>"
60
72
  - !ruby/object:Gem::Version
61
- version: '1.12'
73
+ version: '2.0'
62
74
  type: :development
63
75
  prerelease: false
64
76
  version_requirements: !ruby/object:Gem::Requirement
65
77
  requirements:
66
78
  - - "~>"
67
79
  - !ruby/object:Gem::Version
68
- version: '1.12'
80
+ version: '2.0'
69
81
  - !ruby/object:Gem::Dependency
70
82
  name: pry
71
83
  requirement: !ruby/object:Gem::Requirement
@@ -129,7 +141,9 @@ executables: []
129
141
  extensions: []
130
142
  extra_rdoc_files: []
131
143
  files:
144
+ - ".all-contributorsrc"
132
145
  - ".circleci/config.yml"
146
+ - ".github/dependabot.yml"
133
147
  - ".rubocop.yml"
134
148
  - ".rubocop_todo.yml"
135
149
  - CODE_OF_CONDUCT.md
@@ -166,8 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
180
  - !ruby/object:Gem::Version
167
181
  version: '0'
168
182
  requirements: []
169
- rubyforge_project:
170
- rubygems_version: 2.7.6
183
+ rubygems_version: 3.0.3
171
184
  signing_key:
172
185
  specification_version: 4
173
186
  summary: Bitflyer API wrapper