briskapi 0.3.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (34) hide show
  1. {briskapi-0.3.0 → briskapi-0.3.1}/PKG-INFO +1 -1
  2. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/__init__.py +1 -1
  3. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/decoder/decoder.cjs +79 -13
  4. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/decoder/engineio.cjs +26 -2
  5. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/decoder/sbi.cjs +45 -8
  6. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi.egg-info/PKG-INFO +1 -1
  7. {briskapi-0.3.0 → briskapi-0.3.1}/pyproject.toml +1 -1
  8. {briskapi-0.3.0 → briskapi-0.3.1}/LICENSE +0 -0
  9. {briskapi-0.3.0 → briskapi-0.3.1}/README.md +0 -0
  10. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/LICENSE-pybrisk.txt +0 -0
  11. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/__main__.py +0 -0
  12. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/_archive.py +0 -0
  13. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/_live.py +0 -0
  14. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/_market.py +0 -0
  15. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/_recording.py +0 -0
  16. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/archive.json +0 -0
  17. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/cli.py +0 -0
  18. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/decoder/assets.json +0 -0
  19. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/decoder/web.cjs +0 -0
  20. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/references/historical_mock.json +0 -0
  21. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/sbi.py +0 -0
  22. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/schema.py +0 -0
  23. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi/timing.py +0 -0
  24. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi.egg-info/SOURCES.txt +0 -0
  25. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi.egg-info/dependency_links.txt +0 -0
  26. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi.egg-info/entry_points.txt +0 -0
  27. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi.egg-info/requires.txt +0 -0
  28. {briskapi-0.3.0 → briskapi-0.3.1}/briskapi.egg-info/top_level.txt +0 -0
  29. {briskapi-0.3.0 → briskapi-0.3.1}/setup.cfg +0 -0
  30. {briskapi-0.3.0 → briskapi-0.3.1}/tests/test_api.py +0 -0
  31. {briskapi-0.3.0 → briskapi-0.3.1}/tests/test_archive.py +0 -0
  32. {briskapi-0.3.0 → briskapi-0.3.1}/tests/test_package.py +0 -0
  33. {briskapi-0.3.0 → briskapi-0.3.1}/tests/test_sbi.py +0 -0
  34. {briskapi-0.3.0 → briskapi-0.3.1}/tests/test_timing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: briskapi
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Unofficial Python API, live feed and shared archive for BRiSK auction data
5
5
  License-Expression: MIT
6
6
  Project-URL: Source, https://github.com/honvl/BRiSKapi
@@ -18,7 +18,7 @@ from briskapi._live import Feed, connect as _connect, record as _record, stream
18
18
  from briskapi._market import Market, Ticker
19
19
  from briskapi._recording import JST, BriskError, NotFoundError, Recording, Table
20
20
 
21
- __version__ = '0.3.0'
21
+ __version__ = '0.3.1'
22
22
  __all__ = ['JST', 'Archive', 'BriskError', 'Feed', 'Market', 'NotFoundError', 'Recording', 'Table', 'Ticker',
23
23
  'connect', 'consent', 'current', 'load', 'pull', 'record', 'recordings', 'stream']
24
24
 
@@ -52,10 +52,37 @@ function loadAssets(cache) {
52
52
  return assets;
53
53
  }
54
54
 
55
+ // Position of the stock view's fields, relative to a version's base. Protocol 16000 (the 2021
56
+ // demo, and the vendor client's own parse of it) puts a header word first. 18000 (SBI's current
57
+ // build, as an independent parser of it reads the same view) drops that word, so every index is
58
+ // one lower. The fields and their order are otherwise identical. Any other version is refused
59
+ // rather than read at a guessed offset.
60
+ const STOCK_VIEW_SHIFT = { 16000: 0, 18000: -1 };
61
+
62
+ function stockViewShift(protocolVersion) {
63
+ if (!(protocolVersion in STOCK_VIEW_SHIFT)) {
64
+ throw new Error(`Stock view layout for protocol ${protocolVersion} is not known (known: ${Object.keys(STOCK_VIEW_SHIFT).join(', ')})`);
65
+ }
66
+ return STOCK_VIEW_SHIFT[protocolVersion];
67
+ }
68
+
69
+ // `words` is the raw _getStockView buffer; `ohlc` the number of OHLC bars ahead of the quote fields.
70
+ function parseStockView(words, ohlc, shift) {
71
+ const a = words.subarray(6 * ohlc + shift);
72
+ return { frame: a[19], max_frame: a[20], source_time_us: u64(a, 17), last_price10: words[1 + shift],
73
+ open_price10: words[2 + shift], bid_price10: a[7], ask_price10: a[8], indicative_price10: a[9],
74
+ indicative_volume: u64(a, 10), indicative_side: a[12], closing_indicative_price10: a[13],
75
+ closing_indicative_volume: u64(a, 14), quote_flag: a[21], quote_side: a[22],
76
+ special_quote_time_us: u64(a, 23), indicative_open_price10: a[32], auction_reference_price10: a[35],
77
+ volume: u64(a, 3) };
78
+ }
79
+
55
80
  class Decoder {
56
81
  // options.protocolVersion: 16000 for the Next demo (default), 18000 for SBI BRiSK.
57
- // options.callbacks: { send(Buffer), heartbeat(ns BigInt), frameNumbers(Array), basePrice(n),
82
+ // options.callbacks: { send(Buffer), heartbeat(ns BigInt), frameNumbers(Array), basePrice(issueIndex),
58
83
  // authError(), marketFinished() }, the vendor client's six WASM callbacks.
84
+ // options.boot: { exceptional_sq, base_prices } from the app boot response, applied in the
85
+ // vendor client's order: after the master is loaded, before the snapshot.
59
86
  static async create(assets, options = {}) {
60
87
  // Isolate the legacy glue's globals and process exception handlers. No UI,
61
88
  // network, filesystem access or account state is needed inside this VM.
@@ -74,8 +101,10 @@ class Decoder {
74
101
  return new Decoder(wasm, assets, options);
75
102
  }
76
103
 
77
- constructor(w, assets, { protocolVersion = manifest.protocol_version, callbacks = {} } = {}) {
104
+ constructor(w, assets, { protocolVersion = manifest.protocol_version, callbacks = {}, boot = {} } = {}) {
78
105
  this.w = w;
106
+ this.layoutShift = stockViewShift(protocolVersion);
107
+ this.basePriceQueue = new Set();
79
108
  this.authError = false;
80
109
  this.marketFinished = false;
81
110
  this.initialFrames = null;
@@ -96,7 +125,8 @@ class Decoder {
96
125
  this.lastHeartbeat = BigInt(low >>> 0) + (BigInt(high >>> 0) << 32n);
97
126
  callbacks.heartbeat?.(this.lastHeartbeat);
98
127
  }, 'viii'),
99
- add((id, price) => callbacks.basePrice?.(price), 'vii'),
128
+ // basePrice(id, issueIndex): the decoder changed an issue's base price or limits.
129
+ add((id, issueIndex) => { this.basePriceQueue.add(issueIndex); callbacks.basePrice?.(issueIndex); }, 'vii'),
100
130
  add(() => { this.authError = true; callbacks.authError?.(); }, 'vii'),
101
131
  add(() => { this.marketFinished = true; callbacks.marketFinished?.(); }, 'vi'),
102
132
  protocolVersion);
@@ -114,10 +144,54 @@ class Decoder {
114
144
  name: Buffer.from(w.HEAPU8.subarray(this.buf + 144 * issue_id + 68,
115
145
  this.buf + 144 * issue_id + 68 + a[16])).toString('utf8') };
116
146
  });
147
+ this.boot = this.applyBoot(boot);
117
148
  this.unserialize(assets['snapshot.dat']);
118
149
  this.date = String(w._getDate(this.id));
119
150
  }
120
151
 
152
+ // The vendor client pushes the boot response's exceptional special quotes and base-price
153
+ // changes into the decoder right after the master, then re-reads the master's limits.
154
+ applyBoot({ exceptional_sq = [], base_prices = [] } = {}) {
155
+ const byCode = new Map(this.master.map(m => [m.code, m.issue_id]));
156
+ const number = (value, what) => {
157
+ if (!Number.isFinite(value)) throw new Error(`Boot ${what} is not a number: ${JSON.stringify(value)}`);
158
+ return value;
159
+ };
160
+ for (const sq of exceptional_sq) {
161
+ if (!/^\d+$/.test(String(sq.issue_code))) {
162
+ throw new Error(`Boot exceptional_sq names issue code ${sq.issue_code}; only numeric codes are supported`);
163
+ }
164
+ this.w._pushSQ(this.id, Number(sq.issue_code), sq.buy_sell === 'B' ? 0 : 1, 10 * number(sq.jump_range, 'jump_range'),
165
+ number(sq.jump_sec, 'jump_sec'), 10 * number(sq.quote_limit_down, 'quote_limit_down'),
166
+ 10 * number(sq.quote_limit_up, 'quote_limit_up'));
167
+ }
168
+ for (const change of base_prices) {
169
+ const issue = byCode.get(String(change.issue_code));
170
+ if (issue === undefined) throw new Error(`Boot base_prices names issue code ${change.issue_code}, which is not in the master`);
171
+ this.w._pushBasePrice(this.id, issue, number(change.base_price10, 'base_price10'),
172
+ number(change.limit_down10, 'limit_down10'), number(change.limit_up10, 'limit_up10'));
173
+ this.basePriceQueue.add(issue);
174
+ }
175
+ return { exceptional_sq: exceptional_sq.length, base_prices: base_prices.length, applied: this.applyBasePrice() };
176
+ }
177
+
178
+ // Re-read the master's price limits for issues whose base price changed (the vendor client
179
+ // does this after boot and after every frame); cheap when nothing is queued.
180
+ applyBasePrice() {
181
+ if (!this.basePriceQueue.size) return 0;
182
+ this.w._getStockMaster(this.id, this.buf, this.master.length * 144);
183
+ let applied = 0;
184
+ for (const issue of this.basePriceQueue) {
185
+ const m = this.master[issue];
186
+ if (!m) throw new Error(`Base price change for unknown issue index ${issue}`);
187
+ const a = new Uint32Array(this.w.HEAPU8.buffer, this.buf + 144 * issue, 36);
188
+ m.base_price10 = a[2]; m.limit_up10 = a[3]; m.limit_down10 = a[4];
189
+ applied++;
190
+ }
191
+ this.basePriceQueue.clear();
192
+ return applied;
193
+ }
194
+
121
195
  push(data) {
122
196
  for (let offset = 0; offset < data.length; offset += BUFFER) {
123
197
  const part = data.subarray(offset, offset + BUFFER);
@@ -214,15 +288,7 @@ class Decoder {
214
288
  if (!m) throw new Error(`Unknown issue ID: ${issue_id}`);
215
289
  if (!this.w._getStockView(this.id, issue_id, this.buf)) throw new Error(`Stock view unavailable: ${m.code}`);
216
290
  const words = new Uint32Array(this.w.HEAPU8.buffer, this.buf, this.viewWords);
217
- const a = words.subarray(6 * this.ohlc);
218
- const quote = { issue_id, code: m.code, frame: a[19], max_frame: a[20],
219
- source_time_us: u64(a, 17), last_price10: words[1], open_price10: words[2],
220
- bid_price10: a[7], ask_price10: a[8], indicative_price10: a[9],
221
- indicative_volume: u64(a, 10), indicative_side: a[12],
222
- closing_indicative_price10: a[13], closing_indicative_volume: u64(a, 14),
223
- quote_flag: a[21], quote_side: a[22], special_quote_time_us: u64(a, 23),
224
- indicative_open_price10: a[32], auction_reference_price10: a[35],
225
- volume: u64(a, 3) };
291
+ const quote = { issue_id, code: m.code, ...parseStockView(words, this.ohlc, this.layoutShift) };
226
292
  // SBI's build has no portfolio export, so its quotes carry no issue_status.
227
293
  if (this.w._getPortfolio) {
228
294
  if (!this.w._getPortfolio(this.id, Number(m.code), this.buf)) throw new Error(`Portfolio unavailable: ${m.code}`);
@@ -315,7 +381,7 @@ async function main(argv) {
315
381
  } });
316
382
  }
317
383
 
318
- module.exports = { Decoder, QuoteHistory, frames, u64, loadAssets, replay, main };
384
+ module.exports = { Decoder, QuoteHistory, frames, u64, loadAssets, replay, main, parseStockView, stockViewShift, STOCK_VIEW_SHIFT };
319
385
  if (require.main === module) main(process.argv.slice(2)).catch(error => {
320
386
  console.error(error.message); process.exitCode = 1;
321
387
  });
@@ -248,14 +248,38 @@ class Link {
248
248
 
249
249
  // The vendor client closes the connection when its last heartbeat is older than 7 s, but
250
250
  // only once it has seen one. Beats come from the WASM's heartbeat callback.
251
- function heartbeatMonitor({ intervalMs = 7000, checkEveryMs = 1000, onFail, now = () => performance.now() }) {
251
+ function heartbeatMonitor({ intervalMs = 7000, checkEveryMs = 1000, onFail, now = () => performance.now(), staleCheck = null }) {
252
252
  let last = null;
253
253
  const timer = setInterval(() => {
254
254
  if (last !== null && now() - last > intervalMs) {
255
255
  onFail(new Error(`SBI BRiSK connection check failure: no heartbeat for ${Math.round((now() - last) / 1000)}s`));
256
256
  }
257
+ const stale = staleCheck?.();
258
+ if (stale) onFail(stale);
257
259
  }, checkEveryMs);
258
260
  return { beat() { last = now(); }, stop() { clearInterval(timer); } };
259
261
  }
260
262
 
261
- module.exports = { Link, SBI_PROFILE, resolveProfile, heartbeatMonitor, redact };
263
+ // The vendor client's second liveness rule: during trading hours (08:00-15:00 JST, not the
264
+ // 11:30-12:10 break) the decoder's data time must not lag the server clock by more than 90 s.
265
+ // The server clock comes from the heartbeat; nothing is checked before the first one, or on a
266
+ // day other than the market date.
267
+ function staleDataCheck({ date, thresholdUs = 90_000_000, now = Date.now }) {
268
+ let server = null;
269
+ return {
270
+ beat(ns) { server = { serverMs: Number(ns / 1_000_000n), localMs: now() }; },
271
+ check(decoderTimeUs) {
272
+ if (!server || !decoderTimeUs) return null;
273
+ const jst = new Date(server.serverMs + (now() - server.localMs) + 9 * 3600e3);
274
+ if (jst.toISOString().slice(0, 10) !== date) return null;
275
+ const hour = jst.getUTCHours(), minute = jst.getUTCMinutes();
276
+ if (hour >= 15 || hour <= 7 || (hour === 11 && minute >= 30) || (hour === 12 && minute < 10)) return null;
277
+ const lag = 1e6 * (60 * (60 * hour + minute) + jst.getUTCSeconds()) - decoderTimeUs;
278
+ return lag > thresholdUs
279
+ ? new Error(`SBI BRiSK data is stale: the decoder's clock is ${Math.round(lag / 1e6)}s behind the server (limit ${thresholdUs / 1e6}s)`)
280
+ : null;
281
+ },
282
+ };
283
+ }
284
+
285
+ module.exports = { Link, SBI_PROFILE, resolveProfile, heartbeatMonitor, staleDataCheck, redact };
@@ -13,7 +13,7 @@
13
13
  const crypto = require('node:crypto');
14
14
  const { once } = require('node:events');
15
15
  const { Decoder } = require('./decoder.cjs');
16
- const { Link, resolveProfile, heartbeatMonitor, redact } = require('./engineio.cjs');
16
+ const { Link, resolveProfile, heartbeatMonitor, staleDataCheck, redact } = require('./engineio.cjs');
17
17
 
18
18
  const ORIGIN = 'https://sbi.brisk.jp';
19
19
  const PROTOCOL_VERSION = 18000;
@@ -113,21 +113,51 @@ async function decoderAssets(session) {
113
113
  throw new Error('SBI BRiSK decoder not found in the app bundles; the site layout may have changed');
114
114
  }
115
115
 
116
+ // The master's record layout also depends on the protocol version, and SBI's is not independently
117
+ // confirmed. A wrong layout would read tick types as codes, limits as lot sizes and so on, so
118
+ // refuse a master that does not look like a market. (The 2021 demo's passes every test at 99.98%.)
119
+ function checkMaster(master, protocolVersion) {
120
+ const fraction = predicate => master.filter(predicate).length / (master.length || 1);
121
+ const problems = [];
122
+ if (master.length < 1000) problems.push(`only ${master.length} issues`);
123
+ const rules = [
124
+ ['numeric codes', 0.95, m => /^\d{3,4}$/.test(m.code)],
125
+ ['positive base prices', 0.98, m => m.base_price10 > 0],
126
+ ['limits around the base price', 0.98, m => m.limit_up10 > m.base_price10 && m.base_price10 > m.limit_down10],
127
+ ['lot sizes of 1 to 1,000,000', 0.98, m => m.lot_size >= 1 && m.lot_size <= 1e6],
128
+ ['names', 0.95, m => m.name.length > 0],
129
+ ];
130
+ for (const [what, minimum, predicate] of rules) {
131
+ const seen = fraction(predicate);
132
+ if (seen < minimum) problems.push(`${what}: ${(100 * seen).toFixed(1)}% (need ${(100 * minimum).toFixed(0)}%)`);
133
+ }
134
+ const distinct = new Set(master.map(m => m.code)).size / (master.length || 1);
135
+ if (distinct < 0.99) problems.push(`distinct codes: ${(100 * distinct).toFixed(1)}% (need 99%)`);
136
+ if (problems.length) {
137
+ throw new Error(`SBI BRiSK stock master layout not recognised for protocol ${protocolVersion}: ${problems.join('; ')}`);
138
+ }
139
+ }
140
+
116
141
  function checkAbi(wasm) {
117
142
  const missing = REQUIRED.filter(name => typeof wasm[name] !== 'function');
118
143
  if (missing.length) throw new Error(`SBI BRiSK decoder changed; missing ${missing.join(', ')}`);
119
144
  }
120
145
 
121
146
  async function live({ cookies, codes = [], emit, fetchImpl = fetch, WebSocketImpl = WebSocket, startTimeoutMs = 60000,
122
- protocolVersion = PROTOCOL_VERSION, profile: overrides = {}, trace = null, catchUp = null, heartbeatMs = 7000,
123
- connectTimeoutMs = 15000, catchUpBackoffMs }) {
147
+ protocolVersion, profile: overrides = {}, trace = null, catchUp = null, heartbeatMs = 7000,
148
+ staleSeconds = 90, connectTimeoutMs = 15000, catchUpBackoffMs }) {
124
149
  const profile = resolveProfile(overrides);
125
150
  const session = new Session(cookies, fetchImpl);
126
151
  const began = performance.now();
127
152
  const frontend = await session.get('/api/frontend/boot');
128
153
  session.token = frontend.api_token;
129
154
  const app = await session.get('/api/app/boot');
130
- trace?.(`boot: date ${app.date}, series ${app.series}, ws_url ${redact(app.ws_url)}`);
155
+ // The server says which decoder protocol it speaks (the vendor client passes it to the WASM and
156
+ // falls back to 16000); an explicit option wins, then the boot response, then SBI's 18000.
157
+ protocolVersion ??= app.flex_version ?? PROTOCOL_VERSION;
158
+ trace?.(`boot: date ${app.date}, series ${app.series}, flex_version ${protocolVersion}, `
159
+ + `${(app.base_prices || []).length} base prices, ${(app.exceptional_sq || []).length} exceptional special quotes, `
160
+ + `ws_url ${redact(app.ws_url)}`);
131
161
  const { assets, paths } = await decoderAssets(session);
132
162
  assets['master.dat'] = await session.get(`/api/master/${encodeURIComponent(app.master)}`, 'bytes');
133
163
  assets['snapshot.dat'] = await session.get(`/api/snapshot/${encodeURIComponent(app.snapshot)}`, 'bytes');
@@ -141,18 +171,22 @@ async function live({ cookies, codes = [], emit, fetchImpl = fetch, WebSocketImp
141
171
  settle({ code: -1, reason: '' });
142
172
  };
143
173
  let monitor = null, timer = null; // started with the link and always stopped, so a failure cannot leave the process hanging
144
- const decoder = await Decoder.create(assets, { protocolVersion, check: checkAbi, callbacks: {
174
+ const stale = staleSeconds ? staleDataCheck({ date: app.date, thresholdUs: staleSeconds * 1e6 }) : null;
175
+ const decoder = await Decoder.create(assets, { protocolVersion, check: checkAbi,
176
+ boot: { exceptional_sq: app.exceptional_sq, base_prices: app.base_prices }, callbacks: {
145
177
  send: bytes => { if (link) link.send(bytes); },
146
- heartbeat: () => monitor?.beat(),
178
+ heartbeat: ns => { monitor?.beat(); stale?.beat(ns); },
147
179
  authError: () => fail(new Error('SBI BRiSK reports another WebSocket session for this user (only one is allowed); close the other one')),
148
180
  marketFinished: () => { marketFinished = true; },
149
181
  } });
182
+ checkMaster(decoder.master, protocolVersion);
150
183
  const selected = new Set(codes);
151
184
  const master = decoder.master.filter(m => !selected.size || selected.has(m.code));
152
185
  const missing = codes.filter(code => !master.some(m => m.code === code));
153
186
  if (missing.length) throw new Error(`Codes not in the SBI master: ${missing.join(',')}`);
154
187
  const ids = new Set(master.map(m => m.issue_id));
155
188
  const input_transport = { kind: 'sbi_websocket', origin: ORIGIN + '/', series: app.series,
189
+ protocol_version: protocolVersion, boot_applied: decoder.boot,
156
190
  decoder: paths, decoder_sha256: crypto.createHash('sha256').update(assets['fita.wasm']).digest('hex'),
157
191
  setup_ms: performance.now() - began };
158
192
 
@@ -209,6 +243,7 @@ async function live({ cookies, codes = [], emit, fetchImpl = fetch, WebSocketImp
209
243
  const handle = async (frame, received_unix_ms) => {
210
244
  const t0 = process.hrtime.bigint();
211
245
  decoder.feed(frame);
246
+ decoder.applyBasePrice();
212
247
  const quotes = decoder.changed().filter(id => ids.has(id)).map(id => decoder.quote(id));
213
248
  updates += quotes.length;
214
249
  await emit({ type: 'quotes', seq: seq++, source_time_us: decoder.time(), received_unix_ms,
@@ -224,12 +259,14 @@ async function live({ cookies, codes = [], emit, fetchImpl = fetch, WebSocketImp
224
259
  }
225
260
  try {
226
261
  decoder.feed(frame);
262
+ decoder.applyBasePrice();
227
263
  if (!starting && decoder.initialFrames) { starting = true; begin(); }
228
264
  } catch (error) { fail(error); }
229
265
  };
230
266
 
231
267
  try {
232
- monitor = heartbeatMonitor({ intervalMs: heartbeatMs, checkEveryMs: Math.min(1000, heartbeatMs / 4), onFail: fail });
268
+ monitor = heartbeatMonitor({ intervalMs: heartbeatMs, checkEveryMs: Math.min(1000, heartbeatMs / 4), onFail: fail,
269
+ staleCheck: stale && (() => stale.check(decoder.time())) });
233
270
  timer = setTimeout(() => fail(new Error(`SBI BRiSK stream did not initialize within ${startTimeoutMs / 1000}s; `
234
271
  + 'no first frame numbers arrived (run with --trace-protocol to see what the server sent)')), startTimeoutMs);
235
272
  link = new Link({ WebSocketImpl, url: new URL(app.ws_url, ORIGIN.replace('https:', 'wss:')),
@@ -281,7 +318,7 @@ async function main(argv, env = process.env) {
281
318
  } });
282
319
  }
283
320
 
284
- module.exports = { Session, decoderAssets, checkAbi, live, main, fetchCatchUp, CATCH_UP_FORMATS, PROTOCOL_VERSION, REQUIRED };
321
+ module.exports = { Session, decoderAssets, checkAbi, checkMaster, live, main, fetchCatchUp, CATCH_UP_FORMATS, PROTOCOL_VERSION, REQUIRED };
285
322
  if (require.main === module) main(process.argv.slice(2)).catch(error => {
286
323
  console.error(error.message); process.exitCode = 1;
287
324
  });
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: briskapi
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Unofficial Python API, live feed and shared archive for BRiSK auction data
5
5
  License-Expression: MIT
6
6
  Project-URL: Source, https://github.com/honvl/BRiSKapi
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "briskapi"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "Unofficial Python API, live feed and shared archive for BRiSK auction data"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes