schwab_rb 0.3.2 → 0.3.3
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/.rspec_status +193 -193
- data/.rubocop.yml +2 -0
- data/examples/fetch_account_numbers.rb +1 -1
- data/examples/fetch_user_preferences.rb +1 -1
- data/lib/schwab_rb/auth/auth_context.rb +19 -15
- data/lib/schwab_rb/auth/init_client_easy.rb +36 -32
- data/lib/schwab_rb/auth/init_client_login.rb +165 -161
- data/lib/schwab_rb/auth/init_client_token_file.rb +27 -23
- data/lib/schwab_rb/auth/login_flow_server.rb +39 -36
- data/lib/schwab_rb/auth/token.rb +25 -21
- data/lib/schwab_rb/auth/token_manager.rb +88 -84
- data/lib/schwab_rb/clients/async_client.rb +2 -0
- data/lib/schwab_rb/clients/base_client.rb +2 -0
- data/lib/schwab_rb/configuration.rb +2 -0
- data/lib/schwab_rb/constants.rb +6 -4
- data/lib/schwab_rb/data_objects/account_numbers.rb +1 -0
- data/lib/schwab_rb/data_objects/market_hours.rb +1 -1
- data/lib/schwab_rb/data_objects/market_movers.rb +1 -0
- data/lib/schwab_rb/data_objects/option_expiration_chain.rb +1 -1
- data/lib/schwab_rb/data_objects/order_preview.rb +21 -49
- data/lib/schwab_rb/data_objects/price_history.rb +1 -1
- data/lib/schwab_rb/orders/builder.rb +162 -158
- data/lib/schwab_rb/orders/destination.rb +2 -0
- data/lib/schwab_rb/orders/duration.rb +14 -8
- data/lib/schwab_rb/orders/equity_instructions.rb +2 -0
- data/lib/schwab_rb/orders/errors.rb +2 -0
- data/lib/schwab_rb/orders/instruments.rb +2 -0
- data/lib/schwab_rb/orders/option_instructions.rb +2 -0
- data/lib/schwab_rb/orders/order.rb +2 -0
- data/lib/schwab_rb/orders/price_link_basis.rb +2 -0
- data/lib/schwab_rb/orders/price_link_type.rb +2 -0
- data/lib/schwab_rb/orders/session.rb +16 -10
- data/lib/schwab_rb/orders/special_instruction.rb +2 -0
- data/lib/schwab_rb/orders/stop_price_link_basis.rb +2 -0
- data/lib/schwab_rb/orders/stop_price_link_type.rb +2 -0
- data/lib/schwab_rb/orders/stop_type.rb +2 -0
- data/lib/schwab_rb/orders/tax_lot_method.rb +2 -0
- data/lib/schwab_rb/utils/enum_enforcer.rb +3 -4
- data/lib/schwab_rb/utils/logger.rb +3 -1
- data/lib/schwab_rb/utils/redactor.rb +3 -5
- data/lib/schwab_rb/version.rb +1 -1
- metadata +2 -2
@@ -1,23 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "oauth2"
|
2
4
|
|
3
|
-
module SchwabRb
|
4
|
-
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
module SchwabRb
|
6
|
+
module Auth
|
7
|
+
class AuthContext
|
8
|
+
class << self
|
9
|
+
def build(oauth_client, callback_url, authorization_url, state: nil)
|
10
|
+
auth_params = { redirect_uri: callback_url }
|
11
|
+
auth_params[:state] = state if state
|
12
|
+
authorization_url = oauth_client.auth_code.authorize_url(auth_params)
|
10
13
|
|
11
|
-
|
14
|
+
new(callback_url, authorization_url, state)
|
15
|
+
end
|
12
16
|
end
|
13
|
-
end
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
def initialize(callback_url, authorization_url, state)
|
19
|
+
@callback_url = callback_url
|
20
|
+
@authorization_url = authorization_url
|
21
|
+
@state = state
|
22
|
+
end
|
20
23
|
|
21
|
-
|
24
|
+
attr_reader :callback_url, :authorization_url, :state
|
25
|
+
end
|
22
26
|
end
|
23
27
|
end
|
@@ -1,43 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "oauth2"
|
2
4
|
require_relative "init_client_token_file"
|
3
5
|
require_relative "init_client_login"
|
4
6
|
|
5
|
-
module SchwabRb
|
6
|
-
|
7
|
-
|
8
|
-
app_secret,
|
9
|
-
callback_url,
|
10
|
-
token_path,
|
11
|
-
asyncio: false,
|
12
|
-
enforce_enums: false,
|
13
|
-
callback_timeout: 300.0,
|
14
|
-
interactive: true,
|
15
|
-
requested_browser: nil
|
16
|
-
)
|
17
|
-
|
18
|
-
raise OAuth2::Error.new("No token found") unless File.exist?(token_path)
|
19
|
-
|
20
|
-
client = SchwabRb::Auth.init_client_token_file(
|
21
|
-
api_key,
|
22
|
-
app_secret,
|
23
|
-
token_path,
|
24
|
-
enforce_enums: enforce_enums
|
25
|
-
)
|
26
|
-
client.refresh! if client.session.expired?
|
27
|
-
raise OAuth2::Error.new("Token expired") if client.session.expired?
|
28
|
-
|
29
|
-
client
|
30
|
-
rescue StandardError
|
31
|
-
SchwabRb::Auth.init_client_login(
|
7
|
+
module SchwabRb
|
8
|
+
module Auth
|
9
|
+
def self.init_client_easy(
|
32
10
|
api_key,
|
33
11
|
app_secret,
|
34
12
|
callback_url,
|
35
13
|
token_path,
|
36
|
-
asyncio:
|
37
|
-
enforce_enums:
|
38
|
-
callback_timeout:
|
39
|
-
interactive:
|
40
|
-
requested_browser:
|
14
|
+
asyncio: false,
|
15
|
+
enforce_enums: false,
|
16
|
+
callback_timeout: 300.0,
|
17
|
+
interactive: true,
|
18
|
+
requested_browser: nil
|
41
19
|
)
|
20
|
+
|
21
|
+
raise OAuth2::Error, "No token found" unless File.exist?(token_path)
|
22
|
+
|
23
|
+
client = SchwabRb::Auth.init_client_token_file(
|
24
|
+
api_key,
|
25
|
+
app_secret,
|
26
|
+
token_path,
|
27
|
+
enforce_enums: enforce_enums
|
28
|
+
)
|
29
|
+
client.refresh! if client.session.expired?
|
30
|
+
raise OAuth2::Error, "Token expired" if client.session.expired?
|
31
|
+
|
32
|
+
client
|
33
|
+
rescue StandardError
|
34
|
+
SchwabRb::Auth.init_client_login(
|
35
|
+
api_key,
|
36
|
+
app_secret,
|
37
|
+
callback_url,
|
38
|
+
token_path,
|
39
|
+
asyncio: asyncio,
|
40
|
+
enforce_enums: enforce_enums,
|
41
|
+
callback_timeout: callback_timeout,
|
42
|
+
interactive: interactive,
|
43
|
+
requested_browser: requested_browser
|
44
|
+
)
|
45
|
+
end
|
42
46
|
end
|
43
47
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "openssl"
|
2
4
|
require "uri"
|
3
5
|
require "net/http"
|
@@ -5,200 +7,202 @@ require "json"
|
|
5
7
|
require "oauth2"
|
6
8
|
# require 'logger'
|
7
9
|
|
8
|
-
module SchwabRb
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
module SchwabRb
|
11
|
+
module Auth
|
12
|
+
class RedirectTimeoutError < StandardError
|
13
|
+
def initialize(msg = "Timed out waiting for a callback")
|
14
|
+
super
|
15
|
+
end
|
12
16
|
end
|
13
|
-
end
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
class RedirectServerExitedError < StandardError; end
|
19
|
+
|
20
|
+
# class TokenExchangeError < StandardError
|
21
|
+
# def initialize(msg)
|
22
|
+
# super(msg)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
class InvalidHostname < ArgumentError
|
26
|
+
def initialize(hostname)
|
27
|
+
msg = "Disallowed hostname #{hostname}. init_client_login only allows callback URLs with hostname 127.0.0.1."
|
28
|
+
super(msg)
|
29
|
+
end
|
26
30
|
end
|
27
|
-
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
)
|
40
|
-
|
41
|
-
callback_timeout = if !callback_timeout
|
42
|
-
0
|
43
|
-
elsif callback_timeout < 0
|
44
|
-
raise ArgumentError, "callback_timeout must be non-negative"
|
45
|
-
else
|
46
|
-
callback_timeout
|
47
|
-
end
|
48
|
-
|
49
|
-
parsed = URI.parse(callback_url)
|
50
|
-
raise InvalidHostname.new(parsed.host) unless parsed.host == "127.0.0.1"
|
51
|
-
|
52
|
-
callback_port = parsed.port || 4567
|
53
|
-
callback_path = parsed.path.empty? ? "/" : parsed.path
|
54
|
-
|
55
|
-
cert_file, key_file = create_ssl_certificate
|
56
|
-
|
57
|
-
SchwabRb::Auth::LoginFlowServer.run_in_thread(
|
58
|
-
callback_port: callback_port,
|
59
|
-
callback_path: callback_path,
|
60
|
-
cert_file: cert_file,
|
61
|
-
key_file: key_file
|
32
|
+
def self.init_client_login(
|
33
|
+
api_key,
|
34
|
+
app_secret,
|
35
|
+
callback_url,
|
36
|
+
token_path,
|
37
|
+
asyncio: false,
|
38
|
+
enforce_enums: false,
|
39
|
+
callback_timeout: 300.0,
|
40
|
+
interactive: true,
|
41
|
+
requested_browser: nil
|
62
42
|
)
|
63
43
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
http.use_ssl = true
|
72
|
-
http.ca_file = cert_file.path
|
44
|
+
callback_timeout = if !callback_timeout
|
45
|
+
0
|
46
|
+
elsif callback_timeout.negative?
|
47
|
+
raise ArgumentError, "callback_timeout must be non-negative"
|
48
|
+
else
|
49
|
+
callback_timeout
|
50
|
+
end
|
73
51
|
|
74
|
-
|
52
|
+
parsed = URI.parse(callback_url)
|
53
|
+
raise InvalidHostname, parsed.host unless parsed.host == "127.0.0.1"
|
75
54
|
|
76
|
-
|
55
|
+
callback_port = parsed.port || 4567
|
56
|
+
callback_path = parsed.path.empty? ? "/" : parsed.path
|
77
57
|
|
78
|
-
|
79
|
-
rescue Errno::ECONNREFUSED
|
80
|
-
sleep 0.1
|
81
|
-
end
|
58
|
+
cert_file, key_file = create_ssl_certificate
|
82
59
|
|
83
|
-
|
84
|
-
|
60
|
+
SchwabRb::Auth::LoginFlowServer.run_in_thread(
|
61
|
+
callback_port: callback_port,
|
62
|
+
callback_path: callback_path,
|
63
|
+
cert_file: cert_file,
|
64
|
+
key_file: key_file
|
65
|
+
)
|
85
66
|
|
86
|
-
|
67
|
+
begin
|
68
|
+
# NOTE: wait for server to start
|
69
|
+
start_time = Time.now
|
70
|
+
loop do
|
71
|
+
begin
|
72
|
+
uri = URI("https://127.0.0.1:#{callback_port}/status")
|
73
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
74
|
+
http.use_ssl = true
|
75
|
+
http.ca_file = cert_file.path
|
87
76
|
|
88
|
-
|
89
|
-
***********************************************************************
|
90
|
-
Open this URL in your browser to log in:
|
91
|
-
#{auth_context.authorization_url}
|
92
|
-
***********************************************************************
|
93
|
-
MESSAGE
|
77
|
+
http.set_debug_output($stdout)
|
94
78
|
|
95
|
-
|
96
|
-
puts "Press ENTER to open the browser..."
|
97
|
-
gets
|
98
|
-
end
|
79
|
+
resp = http.get(uri.path)
|
99
80
|
|
100
|
-
|
81
|
+
break if resp.is_a?(Net::HTTPSuccess)
|
82
|
+
rescue Errno::ECONNREFUSED
|
83
|
+
sleep 0.1
|
84
|
+
end
|
101
85
|
|
102
|
-
|
103
|
-
|
86
|
+
raise RedirectServerExitedError if Time.now - start_time > 5
|
87
|
+
end
|
104
88
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
89
|
+
auth_context = build_auth_context(api_key, callback_url)
|
90
|
+
|
91
|
+
puts <<~MESSAGE
|
92
|
+
***********************************************************************
|
93
|
+
Open this URL in your browser to log in:
|
94
|
+
#{auth_context.authorization_url}
|
95
|
+
***********************************************************************
|
96
|
+
MESSAGE
|
97
|
+
|
98
|
+
if interactive
|
99
|
+
puts "Press ENTER to open the browser..."
|
100
|
+
gets
|
109
101
|
end
|
110
|
-
sleep 0.1
|
111
|
-
end
|
112
102
|
|
113
|
-
|
103
|
+
`open "#{auth_context.authorization_url}}"`
|
114
104
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
105
|
+
timeout_time = Time.now + callback_timeout
|
106
|
+
received_url = nil
|
107
|
+
|
108
|
+
while Time.now < timeout_time
|
109
|
+
unless LoginFlowServer.queue.empty?
|
110
|
+
received_url = LoginFlowServer.queue.pop
|
111
|
+
break
|
112
|
+
end
|
113
|
+
sleep 0.1
|
114
|
+
end
|
115
|
+
|
116
|
+
raise RedirectTimeoutError unless received_url
|
117
|
+
|
118
|
+
client_from_received_url(
|
119
|
+
api_key,
|
120
|
+
app_secret,
|
121
|
+
auth_context,
|
122
|
+
received_url,
|
123
|
+
token_path
|
124
|
+
)
|
125
|
+
ensure
|
126
|
+
LoginFlowServer.stop
|
127
|
+
end
|
124
128
|
end
|
125
|
-
end
|
126
129
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
+
def self.create_ssl_certificate
|
131
|
+
key = OpenSSL::PKey::RSA.new(2048)
|
132
|
+
cert = OpenSSL::X509::Certificate.new
|
130
133
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
134
|
+
cert.subject = OpenSSL::X509::Name.parse("/CN=127.0.0.1")
|
135
|
+
cert.issuer = cert.subject
|
136
|
+
cert.public_key = key.public_key
|
137
|
+
cert.not_before = Time.now
|
138
|
+
cert.not_after = Time.now + (60 * 60 * 24) # 1 day
|
139
|
+
cert.serial = 0x0
|
140
|
+
cert.version = 2
|
141
|
+
cert.sign(key, OpenSSL::Digest.new("SHA256"))
|
139
142
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
+
cert_file = Tempfile.new("cert.pem")
|
144
|
+
cert_file.write(cert.to_pem)
|
145
|
+
cert_file.close
|
143
146
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
+
key_file = Tempfile.new("key.pem")
|
148
|
+
key_file.write(key.to_pem)
|
149
|
+
key_file.close
|
147
150
|
|
148
|
-
|
149
|
-
|
151
|
+
[cert_file, key_file]
|
152
|
+
end
|
150
153
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
154
|
+
def self.build_auth_context(api_key, callback_url, state: nil)
|
155
|
+
oauth = OAuth2::Client.new(
|
156
|
+
api_key,
|
157
|
+
nil,
|
158
|
+
site: SchwabRb::Constants::SCHWAB_BASE_URL,
|
159
|
+
authorize_url: "/v1/oauth/authorize",
|
160
|
+
connection_opts: { ssl: { verify: false } }
|
161
|
+
)
|
159
162
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
+
auth_params = { redirect_uri: callback_url }
|
164
|
+
auth_params[:state] = state if state
|
165
|
+
authorization_url = oauth.auth_code.authorize_url(auth_params)
|
163
166
|
|
164
|
-
|
165
|
-
|
167
|
+
AuthContext.new(callback_url, authorization_url, state)
|
168
|
+
end
|
166
169
|
|
167
|
-
|
168
|
-
|
169
|
-
)
|
170
|
-
oauth = OAuth2::Client.new(
|
171
|
-
api_key,
|
172
|
-
app_secret,
|
173
|
-
site: SchwabRb::Constants::SCHWAB_BASE_URL,
|
174
|
-
token_url: "/v1/oauth/token"
|
170
|
+
def self.client_from_received_url(
|
171
|
+
api_key, app_secret, auth_context, received_url, token_path, enforce_enums: true
|
175
172
|
)
|
176
|
-
|
177
|
-
|
178
|
-
|
173
|
+
oauth = OAuth2::Client.new(
|
174
|
+
api_key,
|
175
|
+
app_secret,
|
176
|
+
site: SchwabRb::Constants::SCHWAB_BASE_URL,
|
177
|
+
token_url: "/v1/oauth/token"
|
178
|
+
)
|
179
|
+
uri = URI.parse(received_url)
|
180
|
+
params = URI.decode_www_form(uri.query).to_h
|
181
|
+
authorization_code = params["code"]
|
179
182
|
|
180
|
-
|
183
|
+
token = oauth.auth_code.get_token(authorization_code, redirect_uri: auth_context.callback_url)
|
181
184
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
185
|
+
metadata_manager = SchwabRb::Auth::TokenManager.from_oauth2_token(
|
186
|
+
token,
|
187
|
+
Time.now.to_i,
|
188
|
+
token_path: token_path
|
189
|
+
)
|
190
|
+
metadata_manager.to_file
|
188
191
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
192
|
+
session = OAuth2::AccessToken.new(
|
193
|
+
oauth,
|
194
|
+
token.token,
|
195
|
+
refresh_token: token.refresh_token,
|
196
|
+
expires_at: token.expires_at
|
197
|
+
)
|
195
198
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
199
|
+
SchwabRb::Client.new(
|
200
|
+
api_key,
|
201
|
+
app_secret,
|
202
|
+
session,
|
203
|
+
token_manager: metadata_manager,
|
204
|
+
enforce_enums: enforce_enums
|
205
|
+
)
|
206
|
+
end
|
203
207
|
end
|
204
208
|
end
|
@@ -1,30 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "oauth2"
|
2
4
|
|
3
|
-
module SchwabRb
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
module SchwabRb
|
6
|
+
module Auth
|
7
|
+
def self.init_client_token_file(api_key, app_secret, token_path, enforce_enums: true)
|
8
|
+
oauth = OAuth2::Client.new(
|
9
|
+
api_key,
|
10
|
+
app_secret,
|
11
|
+
site: SchwabRb::Constants::SCHWAB_BASE_URL,
|
12
|
+
token_url: "/v1/oauth/token"
|
13
|
+
)
|
11
14
|
|
12
|
-
|
13
|
-
|
15
|
+
metadata_manager = SchwabRb::Auth::TokenManager.from_file(token_path)
|
16
|
+
token = metadata_manager.token
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
session = OAuth2::AccessToken.new(
|
19
|
+
oauth,
|
20
|
+
token.token,
|
21
|
+
refresh_token: token.refresh_token,
|
22
|
+
expires_at: token.expires_at
|
23
|
+
)
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
SchwabRb::Client.new(
|
26
|
+
api_key,
|
27
|
+
app_secret,
|
28
|
+
session,
|
29
|
+
token_manager: metadata_manager,
|
30
|
+
enforce_enums: enforce_enums
|
31
|
+
)
|
32
|
+
end
|
29
33
|
end
|
30
34
|
end
|
@@ -1,55 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "sinatra"
|
2
4
|
require "puma"
|
3
|
-
require "thread"
|
4
5
|
|
5
|
-
module SchwabRb
|
6
|
-
|
7
|
-
class
|
8
|
-
|
9
|
-
|
6
|
+
module SchwabRb
|
7
|
+
module Auth
|
8
|
+
class LoginFlowServer < Sinatra::Base
|
9
|
+
class << self
|
10
|
+
attr_accessor :queue
|
11
|
+
end
|
10
12
|
|
11
|
-
|
13
|
+
self.queue = Queue.new
|
12
14
|
|
13
|
-
|
15
|
+
disable :logging
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
get "/status" do
|
18
|
+
"running"
|
19
|
+
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
def self.create_routes(root_path)
|
22
|
+
get root_path do
|
23
|
+
self.class.queue.push(request.url)
|
24
|
+
"Callback received! You may now close this window/tab."
|
25
|
+
end
|
23
26
|
end
|
24
|
-
end
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
+
def self.run_in_thread(callback_port: 4567, callback_path: "/", cert_file: nil, key_file: nil)
|
29
|
+
create_routes(callback_path)
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
thread = Thread.new do
|
32
|
+
set :server, "puma"
|
33
|
+
set :port, callback_port
|
34
|
+
set :bind, "127.0.0.1"
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
ctx = Puma::MiniSSL::Context.new.tap do |ctx|
|
37
|
+
ctx.key = key_file.path
|
38
|
+
ctx.cert = cert_file.path
|
39
|
+
ctx.verify_mode = Puma::MiniSSL::VERIFY_NONE
|
40
|
+
end
|
39
41
|
|
40
|
-
|
42
|
+
puts ctx.inspect
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
Puma::Server.new(self).tap do |server|
|
45
|
+
server.add_ssl_listener("127.0.0.1", callback_port, ctx)
|
46
|
+
server.run
|
47
|
+
end
|
45
48
|
end
|
49
|
+
sleep 0.5
|
50
|
+
thread
|
46
51
|
end
|
47
|
-
sleep 0.5
|
48
|
-
thread
|
49
|
-
end
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
+
def self.stop
|
54
|
+
Thread.list.each { |t| t.exit if t != Thread.main }
|
55
|
+
end
|
53
56
|
end
|
54
57
|
end
|
55
58
|
end
|