schwab_rb 1.0.4 → 1.0.6
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/CHANGELOG.md +19 -0
- data/README.md +6 -0
- data/lib/schwab_rb/stream/async_client.rb +6 -0
- data/lib/schwab_rb/stream/base.rb +43 -19
- data/lib/schwab_rb/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 931dc11bd7c59a66e180069c36ab7f6dd44cc068db8aa3c5b58ece0aaf830a6e
|
|
4
|
+
data.tar.gz: e9abc3aa0cce142ffe3ac9af5cca9ee9fa1e5d1fac7209e9f39c929c1f6f8f1c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53af08e726ae79f7b672df90cab4486b88b1ffaedc19ebc7b666bde8362f4fe49667f78f8ffd390be215b0c975895f21b7b4a7ae34addbb41d94bbe4c3030807
|
|
7
|
+
data.tar.gz: 1ce70df60193ac32c02b50eb978c96a640ff75bf2715504824341daf4e51ec8a6c2e466a2598509a66195cfbba21a7724bba6f6b6a901946c5b5352df595f934
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.0.6] - 2026-11-13
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Stream services now retain one callback; registering another block for the same service replaces the previous callback.
|
|
7
|
+
- `Stream::AsyncClient` supports dynamic `add` and `unsub` operations.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Prevent duplicate callback invocation on repeated subscription additions and preserve callbacks when symbols are unsubscribed.
|
|
11
|
+
- Allow callback registration before symbols are supplied without sending an empty subscription.
|
|
12
|
+
|
|
13
|
+
## [1.0.5] - 2026-10-19
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Sanitized stream subscription logging (`SUBS`, `ADD`, `UNSUBS`) at INFO level with service, command, request ID, and keys.
|
|
17
|
+
- Stream subscription acknowledgement response logging at INFO level with service, command, response code, message, and request ID.
|
|
18
|
+
|
|
19
|
+
### Security
|
|
20
|
+
- Redacted `Authorization` access tokens in WebSocket outbound debug logging.
|
|
21
|
+
|
|
3
22
|
## [1.0.4] - 2026-09-21
|
|
4
23
|
|
|
5
24
|
### Added
|
data/README.md
CHANGED
|
@@ -205,6 +205,12 @@ stream.unsub(:level_one_equities, symbols: ["SPY", "QQQ"])
|
|
|
205
205
|
stream.stop
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
Each service has one callback. Passing a block to `on` or `add` replaces its
|
|
209
|
+
callback; calls without a block preserve it, including after all symbols are
|
|
210
|
+
unsubscribed. You can register a callback with `on` before supplying symbols;
|
|
211
|
+
fields specified there are used when symbols are later added without fields.
|
|
212
|
+
Both `Stream::Client` and `Stream::AsyncClient` support `on`, `add`, and `unsub`.
|
|
213
|
+
|
|
208
214
|
## Data Objects
|
|
209
215
|
|
|
210
216
|
The gem includes structured data objects for better handling of API responses. Most API methods support a `return_data_objects` parameter (defaults to `true`) which returns parsed Ruby objects instead of raw JSON responses:
|
|
@@ -13,6 +13,12 @@ module SchwabRb
|
|
|
13
13
|
@stream.on(service_symbol, symbols: symbols, fields: fields, &block)
|
|
14
14
|
self
|
|
15
15
|
end
|
|
16
|
+
alias add on
|
|
17
|
+
|
|
18
|
+
def unsub(service_symbol, symbols: nil)
|
|
19
|
+
@stream.unsub(service_symbol, symbols: symbols)
|
|
20
|
+
self
|
|
21
|
+
end
|
|
16
22
|
|
|
17
23
|
def start
|
|
18
24
|
@stream.start
|
|
@@ -19,6 +19,7 @@ module SchwabRb
|
|
|
19
19
|
def initialize(client)
|
|
20
20
|
@client = client
|
|
21
21
|
@handlers = {}
|
|
22
|
+
@service_fields = {}
|
|
22
23
|
@subscriptions = {}
|
|
23
24
|
@connection = nil
|
|
24
25
|
@message_builder = nil
|
|
@@ -34,16 +35,18 @@ module SchwabRb
|
|
|
34
35
|
service_name = Services.lookup(service_symbol)
|
|
35
36
|
resolved_fields = fields ? Fields.resolve(service_name, fields) : nil
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
@
|
|
39
|
-
|
|
38
|
+
@handlers[service_name] = block if block
|
|
39
|
+
@service_fields[service_name] = resolved_fields unless fields.nil?
|
|
40
|
+
symbol_list = Array(symbols).map(&:to_s)
|
|
41
|
+
return self if symbol_list.empty?
|
|
40
42
|
|
|
43
|
+
subscription = { symbols: symbol_list, fields: resolved_fields || @service_fields[service_name] }
|
|
41
44
|
@subscriptions[service_name] = merge_subscription(
|
|
42
45
|
@subscriptions[service_name],
|
|
43
|
-
|
|
46
|
+
subscription
|
|
44
47
|
)
|
|
45
48
|
|
|
46
|
-
send_subscription(service_name,
|
|
49
|
+
send_subscription(service_name, subscription) if @connected
|
|
47
50
|
|
|
48
51
|
self
|
|
49
52
|
end
|
|
@@ -59,19 +62,12 @@ module SchwabRb
|
|
|
59
62
|
@subscriptions.delete(service_name) if @subscriptions[service_name][:symbols].empty?
|
|
60
63
|
end
|
|
61
64
|
|
|
62
|
-
if @handlers[service_name]
|
|
63
|
-
@handlers[service_name].each do |h|
|
|
64
|
-
h[:symbols] -= symbols_to_remove
|
|
65
|
-
end
|
|
66
|
-
@handlers[service_name].reject! { |h| h[:symbols].empty? }
|
|
67
|
-
@handlers.delete(service_name) if @handlers[service_name].empty?
|
|
68
|
-
end
|
|
69
|
-
|
|
70
65
|
if @connected
|
|
71
66
|
request = @message_builder.build_request(
|
|
72
67
|
service_name, Commands::UNSUBS,
|
|
73
68
|
keys: symbols_to_remove
|
|
74
69
|
)
|
|
70
|
+
log_subscription_request(request)
|
|
75
71
|
send_message(@message_builder.wrap_requests(request))
|
|
76
72
|
end
|
|
77
73
|
|
|
@@ -176,6 +172,7 @@ module SchwabRb
|
|
|
176
172
|
keys: sub[:symbols],
|
|
177
173
|
fields: sub[:fields]
|
|
178
174
|
)
|
|
175
|
+
log_subscription_request(request)
|
|
179
176
|
send_message(@message_builder.wrap_requests(request))
|
|
180
177
|
end
|
|
181
178
|
end
|
|
@@ -186,6 +183,7 @@ module SchwabRb
|
|
|
186
183
|
keys: handler[:symbols],
|
|
187
184
|
fields: handler[:fields]
|
|
188
185
|
)
|
|
186
|
+
log_subscription_request(request)
|
|
189
187
|
send_message(@message_builder.wrap_requests(request))
|
|
190
188
|
end
|
|
191
189
|
|
|
@@ -203,20 +201,43 @@ module SchwabRb
|
|
|
203
201
|
if parsed.key?("data")
|
|
204
202
|
parsed["data"].each do |data_entry|
|
|
205
203
|
service = data_entry["service"]
|
|
206
|
-
|
|
207
|
-
next unless handlers
|
|
208
|
-
|
|
209
|
-
handlers.each { |h| h[:callback]&.call(data_entry) }
|
|
204
|
+
@handlers[service]&.call(data_entry)
|
|
210
205
|
end
|
|
211
206
|
end
|
|
212
207
|
|
|
213
208
|
return unless parsed.key?("response")
|
|
214
209
|
|
|
215
210
|
parsed["response"].each do |resp|
|
|
216
|
-
|
|
211
|
+
log_stream_response(resp)
|
|
217
212
|
end
|
|
218
213
|
end
|
|
219
214
|
|
|
215
|
+
def log_subscription_request(request)
|
|
216
|
+
service = request["service"]
|
|
217
|
+
command = request["command"]
|
|
218
|
+
request_id = request["requestid"]
|
|
219
|
+
keys = request.dig("parameters", "keys")
|
|
220
|
+
SchwabRb::Logger.logger.info(
|
|
221
|
+
"Stream subscription request: service=#{service} command=#{command} " \
|
|
222
|
+
"request_id=#{request_id} keys=#{keys}"
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def log_stream_response(resp)
|
|
227
|
+
service = resp["service"]
|
|
228
|
+
command = resp["command"]
|
|
229
|
+
request_id = resp["requestid"]
|
|
230
|
+
content = resp["content"] || {}
|
|
231
|
+
code = content["code"]
|
|
232
|
+
msg = content["msg"]
|
|
233
|
+
|
|
234
|
+
SchwabRb::Logger.logger.info(
|
|
235
|
+
"Stream response: service=#{service} command=#{command} " \
|
|
236
|
+
"code=#{code} msg=#{msg.inspect} request_id=#{request_id}"
|
|
237
|
+
)
|
|
238
|
+
SchwabRb::Logger.logger.debug("Raw stream response: #{resp}")
|
|
239
|
+
end
|
|
240
|
+
|
|
220
241
|
def handle_disconnect(error)
|
|
221
242
|
@connected = false
|
|
222
243
|
begin
|
|
@@ -255,7 +276,10 @@ module SchwabRb
|
|
|
255
276
|
@write_mutex.synchronize do
|
|
256
277
|
return unless @connection
|
|
257
278
|
|
|
258
|
-
SchwabRb::Logger.logger.debug
|
|
279
|
+
if SchwabRb::Logger.logger.debug?
|
|
280
|
+
sanitized = json_string.gsub(/"Authorization"\s*:\s*"[^"]*"/, '"Authorization":"[REDACTED]"')
|
|
281
|
+
SchwabRb::Logger.logger.debug("Stream sending: #{sanitized}")
|
|
282
|
+
end
|
|
259
283
|
@connection.write(json_string)
|
|
260
284
|
@connection.flush
|
|
261
285
|
end
|
data/lib/schwab_rb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schwab_rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joseph Platta
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-11-14 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|