schwab_rb 0.9.2 → 1.0.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/.claude/settings.local.json +16 -1
- data/CHANGELOG.md +20 -4
- data/examples/place_oco_order.rb +1 -4
- data/examples/stream_level_one.rb +65 -0
- data/examples/stream_order_book.rb +57 -0
- data/lib/schwab_rb/auth/init_client_easy.rb +19 -18
- data/lib/schwab_rb/auth/{init_client_token_file.rb → init_client_from_database.rb} +5 -6
- data/lib/schwab_rb/auth/init_client_login.rb +7 -8
- data/lib/schwab_rb/auth/token_manager.rb +25 -15
- data/lib/schwab_rb/cli/app.rb +11 -23
- data/lib/schwab_rb/clients/base_client.rb +51 -75
- data/lib/schwab_rb/configuration.rb +3 -3
- data/lib/schwab_rb/constants.rb +0 -1
- data/lib/schwab_rb/path_support.rb +2 -1
- data/lib/schwab_rb/storage/database.rb +148 -0
- data/lib/schwab_rb/stream/async_client.rb +30 -0
- data/lib/schwab_rb/stream/base.rb +240 -0
- data/lib/schwab_rb/stream/client.rb +44 -0
- data/lib/schwab_rb/stream/fields.rb +339 -0
- data/lib/schwab_rb/stream/message_builder.rb +66 -0
- data/lib/schwab_rb/stream/services.rb +52 -0
- data/lib/schwab_rb/version.rb +1 -1
- data/lib/schwab_rb.rb +8 -2
- metadata +40 -4
- data/lib/schwab_rb/account_hash_manager.rb +0 -119
|
@@ -49,15 +49,14 @@ module SchwabRb
|
|
|
49
49
|
@token_manager.token_age
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
def get_account(account_hash
|
|
52
|
+
def get_account(account_hash: nil, fields: nil, return_data_objects: true)
|
|
53
53
|
# Account balances, positions, and orders for a given account.
|
|
54
54
|
#
|
|
55
|
-
# @param account_hash [String] The account hash
|
|
56
|
-
# @param account_name [String] The account name from account_names.json (takes priority)
|
|
55
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
57
56
|
# @param fields [Array] Balances displayed by default, additional fields can be
|
|
58
57
|
# added here by adding values from Account.fields.
|
|
59
58
|
# @param return_data_objects [Boolean] Whether to return data objects or Hash
|
|
60
|
-
resolved_hash = resolve_account_hash(
|
|
59
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
61
60
|
|
|
62
61
|
with_account_hash_retry(resolved_hash) do
|
|
63
62
|
refresh_token_if_needed
|
|
@@ -117,13 +116,6 @@ module SchwabRb
|
|
|
117
116
|
|
|
118
117
|
account_numbers_data = JSON.parse(response.body, symbolize_names: true)
|
|
119
118
|
|
|
120
|
-
begin
|
|
121
|
-
hash_manager = SchwabRb::AccountHashManager.new
|
|
122
|
-
hash_manager.update_hashes_from_api_response(account_numbers_data)
|
|
123
|
-
rescue SchwabRb::AccountHashManager::AccountNamesFileNotFoundError
|
|
124
|
-
# Silently skip if account names file doesn't exist - not all users will use this feature
|
|
125
|
-
end
|
|
126
|
-
|
|
127
119
|
if return_data_objects
|
|
128
120
|
SchwabRb::DataObjects::AccountNumbers.build(account_numbers_data)
|
|
129
121
|
else
|
|
@@ -131,23 +123,13 @@ module SchwabRb
|
|
|
131
123
|
end
|
|
132
124
|
end
|
|
133
125
|
|
|
134
|
-
def
|
|
135
|
-
# Returns a list of available account names from account_names.json
|
|
136
|
-
# Returns empty array if account_names.json doesn't exist
|
|
137
|
-
#
|
|
138
|
-
# @return [Array<String>] List of account names
|
|
139
|
-
hash_manager = SchwabRb::AccountHashManager.new
|
|
140
|
-
hash_manager.available_account_names
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def get_order(order_id, account_hash = nil, account_name: nil, return_data_objects: true)
|
|
126
|
+
def get_order(order_id, account_hash: nil, return_data_objects: true)
|
|
144
127
|
# Get a specific order for a specific account by its order ID.
|
|
145
128
|
#
|
|
146
129
|
# @param order_id [String] The order ID.
|
|
147
|
-
# @param account_hash [String] The account hash
|
|
148
|
-
# @param account_name [String] The account name from account_names.json (takes priority)
|
|
130
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
149
131
|
# @param return_data_objects [Boolean] Whether to return data objects or Hash
|
|
150
|
-
resolved_hash = resolve_account_hash(
|
|
132
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
151
133
|
|
|
152
134
|
with_account_hash_retry(resolved_hash) do
|
|
153
135
|
refresh_token_if_needed
|
|
@@ -164,13 +146,12 @@ module SchwabRb
|
|
|
164
146
|
end
|
|
165
147
|
end
|
|
166
148
|
|
|
167
|
-
def cancel_order(order_id, account_hash
|
|
149
|
+
def cancel_order(order_id, account_hash: nil)
|
|
168
150
|
# Cancel a specific order for a specific account.
|
|
169
151
|
#
|
|
170
152
|
# @param order_id [String] The order ID.
|
|
171
|
-
# @param account_hash [String] The account hash
|
|
172
|
-
|
|
173
|
-
resolved_hash = resolve_account_hash(account_name: account_name, account_hash: account_hash)
|
|
153
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
154
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
174
155
|
|
|
175
156
|
with_account_hash_retry(resolved_hash) do
|
|
176
157
|
refresh_token_if_needed
|
|
@@ -181,8 +162,7 @@ module SchwabRb
|
|
|
181
162
|
end
|
|
182
163
|
|
|
183
164
|
def get_account_orders(
|
|
184
|
-
account_hash
|
|
185
|
-
account_name: nil,
|
|
165
|
+
account_hash: nil,
|
|
186
166
|
max_results: nil,
|
|
187
167
|
from_entered_datetime: nil,
|
|
188
168
|
to_entered_datetime: nil,
|
|
@@ -191,14 +171,13 @@ module SchwabRb
|
|
|
191
171
|
)
|
|
192
172
|
# Orders for a specific account. Optionally specify a single status on which to filter.
|
|
193
173
|
#
|
|
194
|
-
# @param account_hash [String] The account hash
|
|
195
|
-
# @param account_name [String] The account name from account_names.json (takes priority)
|
|
174
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
196
175
|
# @param max_results [Integer] The maximum number of orders to retrieve.
|
|
197
176
|
# @param from_entered_datetime [DateTime] Start of the query date range (default: 60 days ago).
|
|
198
177
|
# @param to_entered_datetime [DateTime] End of the query date range (default: now).
|
|
199
178
|
# @param status [String] Restrict query to orders with this status.
|
|
200
179
|
# @param return_data_objects [Boolean] Whether to return data objects or Hash
|
|
201
|
-
resolved_hash = resolve_account_hash(
|
|
180
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
202
181
|
|
|
203
182
|
with_account_hash_retry(resolved_hash) do
|
|
204
183
|
refresh_token_if_needed
|
|
@@ -262,17 +241,16 @@ module SchwabRb
|
|
|
262
241
|
end
|
|
263
242
|
end
|
|
264
243
|
|
|
265
|
-
def place_order(order_spec, account_hash
|
|
244
|
+
def place_order(order_spec, account_hash: nil)
|
|
266
245
|
# Place an order for a specific account. If order creation is successful,
|
|
267
246
|
# the response will contain the ID of the generated order.
|
|
268
247
|
#
|
|
269
|
-
# @param account_hash [String] The account hash (optional if account_name provided)
|
|
270
248
|
# @param order_spec [Hash, SchwabRb::Orders::Builder] The order specification
|
|
271
|
-
# @param
|
|
249
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
272
250
|
#
|
|
273
251
|
# Note: Unlike most methods in this library, successful responses typically
|
|
274
252
|
# do not contain JSON data, and attempting to extract it may raise an exception.
|
|
275
|
-
resolved_hash = resolve_account_hash(
|
|
253
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
276
254
|
|
|
277
255
|
with_account_hash_retry(resolved_hash) do
|
|
278
256
|
refresh_token_if_needed
|
|
@@ -284,16 +262,15 @@ module SchwabRb
|
|
|
284
262
|
end
|
|
285
263
|
end
|
|
286
264
|
|
|
287
|
-
def replace_order(order_id, order_spec, account_hash
|
|
265
|
+
def replace_order(order_id, order_spec, account_hash: nil)
|
|
288
266
|
# Replace an existing order for an account.
|
|
289
267
|
# The existing order will be replaced by the new order.
|
|
290
268
|
# Once replaced, the old order will be canceled and a new order will be created.
|
|
291
269
|
#
|
|
292
|
-
# @param account_hash [String] The account hash (optional if account_name provided)
|
|
293
270
|
# @param order_id [String] The order ID to replace
|
|
294
271
|
# @param order_spec [Hash, SchwabRb::Orders::Builder] The new order specification
|
|
295
|
-
# @param
|
|
296
|
-
resolved_hash = resolve_account_hash(
|
|
272
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
273
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
297
274
|
|
|
298
275
|
with_account_hash_retry(resolved_hash) do
|
|
299
276
|
refresh_token_if_needed
|
|
@@ -305,15 +282,14 @@ module SchwabRb
|
|
|
305
282
|
end
|
|
306
283
|
end
|
|
307
284
|
|
|
308
|
-
def preview_order(order_spec, account_hash
|
|
285
|
+
def preview_order(order_spec, account_hash: nil, return_data_objects: true)
|
|
309
286
|
# Preview an order, i.e., test whether an order would be accepted by the
|
|
310
287
|
# API and see the structure it would result in.
|
|
311
288
|
#
|
|
312
|
-
# @param account_hash [String] The account hash (optional if account_name provided)
|
|
313
289
|
# @param order_spec [Hash, SchwabRb::Orders::Builder] The order specification to preview
|
|
314
|
-
# @param
|
|
290
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
315
291
|
# @param return_data_objects [Boolean] Whether to return data objects or Hash
|
|
316
|
-
resolved_hash = resolve_account_hash(
|
|
292
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
317
293
|
|
|
318
294
|
with_account_hash_retry(resolved_hash) do
|
|
319
295
|
refresh_token_if_needed
|
|
@@ -333,8 +309,7 @@ module SchwabRb
|
|
|
333
309
|
end
|
|
334
310
|
|
|
335
311
|
def get_transactions(
|
|
336
|
-
account_hash
|
|
337
|
-
account_name: nil,
|
|
312
|
+
account_hash: nil,
|
|
338
313
|
start_date: nil,
|
|
339
314
|
end_date: nil,
|
|
340
315
|
transaction_types: nil,
|
|
@@ -343,14 +318,13 @@ module SchwabRb
|
|
|
343
318
|
)
|
|
344
319
|
# Transactions for a specific account.
|
|
345
320
|
#
|
|
346
|
-
# @param account_hash [String] The account hash
|
|
347
|
-
# @param account_name [String] The account name from account_names.json (takes priority)
|
|
321
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
348
322
|
# @param start_date [Date, DateTime] Start date for transactions (default: 60 days ago).
|
|
349
323
|
# @param end_date [Date, DateTime] End date for transactions (default: now).
|
|
350
324
|
# @param transaction_types [Array] List of transaction types to filter by.
|
|
351
325
|
# @param symbol [String] Filter transactions by the specified symbol.
|
|
352
326
|
# @param return_data_objects [Boolean] Whether to return data objects or Hash
|
|
353
|
-
resolved_hash = resolve_account_hash(
|
|
327
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
354
328
|
|
|
355
329
|
with_account_hash_retry(resolved_hash) do
|
|
356
330
|
refresh_token_if_needed
|
|
@@ -394,14 +368,13 @@ module SchwabRb
|
|
|
394
368
|
end
|
|
395
369
|
end
|
|
396
370
|
|
|
397
|
-
def get_transaction(activity_id, account_hash
|
|
371
|
+
def get_transaction(activity_id, account_hash: nil, return_data_objects: true)
|
|
398
372
|
# Transaction for a specific account.
|
|
399
373
|
#
|
|
400
|
-
# @param account_hash [String] The account hash (optional if account_name provided)
|
|
401
374
|
# @param activity_id [String] ID of the transaction to retrieve
|
|
402
|
-
# @param
|
|
375
|
+
# @param account_hash [String] The account hash. If nil, resolved from SCHWAB_ACCOUNT_NUMBER.
|
|
403
376
|
# @param return_data_objects [Boolean] Whether to return data objects or Hash
|
|
404
|
-
resolved_hash = resolve_account_hash(
|
|
377
|
+
resolved_hash = resolve_account_hash(account_hash: account_hash)
|
|
405
378
|
|
|
406
379
|
with_account_hash_retry(resolved_hash) do
|
|
407
380
|
refresh_token_if_needed
|
|
@@ -930,31 +903,34 @@ module SchwabRb
|
|
|
930
903
|
|
|
931
904
|
private
|
|
932
905
|
|
|
933
|
-
# Resolves account
|
|
934
|
-
#
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
# If account_name is provided, look it up
|
|
938
|
-
if account_name
|
|
939
|
-
hash_manager = SchwabRb::AccountHashManager.new
|
|
940
|
-
resolved_hash = hash_manager.get_hash_by_name(account_name)
|
|
941
|
-
|
|
942
|
-
unless resolved_hash
|
|
943
|
-
raise ArgumentError,
|
|
944
|
-
"Account name '#{account_name}' not found in account hashes. " \
|
|
945
|
-
"Make sure get_account_numbers has been called to populate hashes."
|
|
946
|
-
end
|
|
906
|
+
# Resolves account hash. Uses the provided hash directly, or auto-resolves
|
|
907
|
+
# from SCHWAB_ACCOUNT_NUMBER via the database (fetching from API if not cached).
|
|
908
|
+
def resolve_account_hash(account_hash: nil)
|
|
909
|
+
return account_hash if account_hash
|
|
947
910
|
|
|
948
|
-
|
|
911
|
+
account_number = SchwabRb.configuration.account_number
|
|
912
|
+
unless account_number
|
|
913
|
+
raise ArgumentError,
|
|
914
|
+
"No account_hash provided and SCHWAB_ACCOUNT_NUMBER is not configured"
|
|
949
915
|
end
|
|
950
916
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
917
|
+
db = SchwabRb::Storage::Database.new
|
|
918
|
+
cached = db.load_account_hash(account_number)
|
|
919
|
+
return cached if cached
|
|
920
|
+
|
|
921
|
+
fetch_and_cache_account_hash(account_number, db)
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
def fetch_and_cache_account_hash(account_number, db)
|
|
925
|
+
refresh_token_if_needed
|
|
926
|
+
response = get("/trader/v1/accounts/accountNumbers", {})
|
|
927
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
928
|
+
entry = data.find { |a| a[:accountNumber] == account_number }
|
|
929
|
+
raise ArgumentError, "Account #{account_number} not found in Schwab API response" unless entry
|
|
955
930
|
|
|
956
|
-
|
|
957
|
-
|
|
931
|
+
hash = entry[:hashValue]
|
|
932
|
+
db.save_account(account_number, hash)
|
|
933
|
+
hash
|
|
958
934
|
end
|
|
959
935
|
|
|
960
936
|
# Wraps API calls that use account_hash with retry logic for stale hashes
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module SchwabRb
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_accessor :logger, :log_file, :log_level, :silence_output,
|
|
6
|
-
:schwab_home, :
|
|
6
|
+
:schwab_home, :account_number, :database_path
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
9
|
@logger = nil
|
|
@@ -13,8 +13,8 @@ module SchwabRb
|
|
|
13
13
|
|
|
14
14
|
default_home = File.expand_path("~/.schwab_rb")
|
|
15
15
|
@schwab_home = ENV.fetch("SCHWAB_HOME", default_home)
|
|
16
|
-
@
|
|
17
|
-
@
|
|
16
|
+
@account_number = ENV.fetch("SCHWAB_ACCOUNT_NUMBER", nil)
|
|
17
|
+
@database_path = ENV.fetch("SCHWAB_DATABASE_PATH", File.join(@schwab_home, "schwab.db"))
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def has_external_logger?
|
data/lib/schwab_rb/constants.rb
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
|
|
5
5
|
module SchwabRb
|
|
6
|
-
# Common path expansion and directory helpers for token and data files.
|
|
7
6
|
module PathSupport
|
|
8
7
|
module_function
|
|
9
8
|
|
|
10
9
|
def expand_path(path)
|
|
10
|
+
raise ArgumentError, "path is nil or empty" if path.nil? || path.to_s.strip.empty?
|
|
11
|
+
|
|
11
12
|
File.expand_path(path.to_s)
|
|
12
13
|
end
|
|
13
14
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sqlite3"
|
|
4
|
+
|
|
5
|
+
module SchwabRb
|
|
6
|
+
module Storage
|
|
7
|
+
class Database
|
|
8
|
+
attr_reader :path
|
|
9
|
+
|
|
10
|
+
def initialize(path = nil)
|
|
11
|
+
@path = path || SchwabRb.configuration.database_path
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@db = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def save_token(api_key, token_hash)
|
|
17
|
+
synchronize do
|
|
18
|
+
db.execute("DELETE FROM tokens WHERE api_key = ?", [api_key])
|
|
19
|
+
db.execute(<<~SQL, bind_token_params(api_key, token_hash))
|
|
20
|
+
INSERT INTO tokens (api_key, access_token, refresh_token, expires_at, expires_in,
|
|
21
|
+
token_type, scope, id_token, timestamp)
|
|
22
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
23
|
+
SQL
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load_token(api_key)
|
|
28
|
+
row = synchronize do
|
|
29
|
+
db.get_first_row("SELECT * FROM tokens WHERE api_key = ?", [api_key])
|
|
30
|
+
end
|
|
31
|
+
return nil unless row
|
|
32
|
+
|
|
33
|
+
expires_at = row["expires_at"]
|
|
34
|
+
if expires_at && Time.now.to_i > expires_at
|
|
35
|
+
synchronize { db.execute("DELETE FROM tokens WHERE api_key = ?", [api_key]) }
|
|
36
|
+
return nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
"timestamp" => row["timestamp"],
|
|
41
|
+
"token" => {
|
|
42
|
+
"access_token" => row["access_token"],
|
|
43
|
+
"refresh_token" => row["refresh_token"],
|
|
44
|
+
"expires_at" => expires_at,
|
|
45
|
+
"expires_in" => row["expires_in"],
|
|
46
|
+
"token_type" => row["token_type"],
|
|
47
|
+
"scope" => row["scope"],
|
|
48
|
+
"id_token" => row["id_token"]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def save_account(account_number, account_hash)
|
|
54
|
+
synchronize do
|
|
55
|
+
db.execute(<<~SQL, [account_number, account_hash])
|
|
56
|
+
INSERT INTO accounts (account_number, account_hash)
|
|
57
|
+
VALUES (?, ?)
|
|
58
|
+
ON CONFLICT(account_number) DO UPDATE SET
|
|
59
|
+
account_hash = excluded.account_hash,
|
|
60
|
+
updated_at = CURRENT_TIMESTAMP
|
|
61
|
+
SQL
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def load_account_hash(account_number)
|
|
66
|
+
row = synchronize do
|
|
67
|
+
db.get_first_row("SELECT account_hash FROM accounts WHERE account_number = ?", [account_number])
|
|
68
|
+
end
|
|
69
|
+
row&.fetch("account_hash", nil)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def close
|
|
73
|
+
synchronize do
|
|
74
|
+
@db&.close
|
|
75
|
+
@db = nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def db
|
|
82
|
+
@db ||= open_database
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def open_database
|
|
86
|
+
is_memory = @path == ":memory:"
|
|
87
|
+
SchwabRb::PathSupport.ensure_parent_directory(@path) unless is_memory
|
|
88
|
+
|
|
89
|
+
database = SQLite3::Database.new(@path)
|
|
90
|
+
database.results_as_hash = true
|
|
91
|
+
database.execute("PRAGMA journal_mode=WAL") unless is_memory
|
|
92
|
+
database.execute("PRAGMA foreign_keys=ON")
|
|
93
|
+
create_tables(database)
|
|
94
|
+
database
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def create_tables(database)
|
|
98
|
+
database.execute(<<~SQL)
|
|
99
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
100
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
account_number TEXT NOT NULL UNIQUE,
|
|
102
|
+
account_hash TEXT NOT NULL,
|
|
103
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
104
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
105
|
+
)
|
|
106
|
+
SQL
|
|
107
|
+
|
|
108
|
+
database.execute(<<~SQL)
|
|
109
|
+
CREATE TABLE IF NOT EXISTS tokens (
|
|
110
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
111
|
+
api_key TEXT NOT NULL UNIQUE,
|
|
112
|
+
access_token TEXT,
|
|
113
|
+
refresh_token TEXT,
|
|
114
|
+
expires_at INTEGER,
|
|
115
|
+
expires_in INTEGER,
|
|
116
|
+
token_type TEXT DEFAULT 'Bearer',
|
|
117
|
+
scope TEXT,
|
|
118
|
+
id_token TEXT,
|
|
119
|
+
timestamp INTEGER,
|
|
120
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
121
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
122
|
+
)
|
|
123
|
+
SQL
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def synchronize(&block)
|
|
128
|
+
@mutex.synchronize(&block)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def bind_token_params(api_key, token_hash)
|
|
132
|
+
token = token_hash["token"] || token_hash[:token] || {}
|
|
133
|
+
[
|
|
134
|
+
api_key,
|
|
135
|
+
token["access_token"] || token[:access_token],
|
|
136
|
+
token["refresh_token"] || token[:refresh_token],
|
|
137
|
+
token["expires_at"] || token[:expires_at],
|
|
138
|
+
token["expires_in"] || token[:expires_in],
|
|
139
|
+
token["token_type"] || token[:token_type] || "Bearer",
|
|
140
|
+
token["scope"] || token[:scope],
|
|
141
|
+
token["id_token"] || token[:id_token],
|
|
142
|
+
token_hash["timestamp"] || token_hash[:timestamp]
|
|
143
|
+
]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SchwabRb
|
|
6
|
+
module Stream
|
|
7
|
+
class AsyncClient
|
|
8
|
+
def initialize(client)
|
|
9
|
+
@stream = Base.new(client)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def on(service_symbol, symbols: nil, fields: nil, &block)
|
|
13
|
+
@stream.on(service_symbol, symbols: symbols, fields: fields, &block)
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def start
|
|
18
|
+
@stream.start
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def stop
|
|
22
|
+
@stream.stop
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def connected?
|
|
26
|
+
@stream.connected?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|