tardis-machine 18.0.0 → 18.0.1

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 (208) hide show
  1. package/benchmark.js +35 -14
  2. package/bin/tardis-machine.js +0 -1
  3. package/dist/tardismachine.d.ts.map +1 -1
  4. package/dist/tardismachine.js +20 -55
  5. package/dist/tardismachine.js.map +1 -1
  6. package/dist/ws/replay.d.ts +2 -2
  7. package/dist/ws/replay.d.ts.map +1 -1
  8. package/dist/ws/replay.js +11 -5
  9. package/dist/ws/replay.js.map +1 -1
  10. package/dist/ws/replaynormalized.d.ts +2 -2
  11. package/dist/ws/replaynormalized.d.ts.map +1 -1
  12. package/dist/ws/replaynormalized.js +7 -5
  13. package/dist/ws/replaynormalized.js.map +1 -1
  14. package/dist/ws/server.d.ts +20 -0
  15. package/dist/ws/server.d.ts.map +1 -0
  16. package/dist/ws/server.js +154 -0
  17. package/dist/ws/server.js.map +1 -0
  18. package/dist/ws/streamnormalized.d.ts +2 -2
  19. package/dist/ws/streamnormalized.d.ts.map +1 -1
  20. package/dist/ws/streamnormalized.js +7 -5
  21. package/dist/ws/streamnormalized.js.map +1 -1
  22. package/dist/ws/subscriptionsmappers.d.ts +1 -1
  23. package/dist/ws/subscriptionsmappers.d.ts.map +1 -1
  24. package/node_modules/tardis-dev/README.md +1 -1
  25. package/node_modules/tardis-dev/dist/debug.d.ts +12 -2
  26. package/node_modules/tardis-dev/dist/debug.d.ts.map +1 -1
  27. package/node_modules/tardis-dev/dist/debug.js +2 -1
  28. package/node_modules/tardis-dev/dist/debug.js.map +1 -1
  29. package/node_modules/tardis-dev/dist/handy.d.ts +3 -1
  30. package/node_modules/tardis-dev/dist/handy.d.ts.map +1 -1
  31. package/node_modules/tardis-dev/dist/handy.js +72 -86
  32. package/node_modules/tardis-dev/dist/handy.js.map +1 -1
  33. package/node_modules/tardis-dev/dist/realtimefeeds/mexc.d.ts +0 -7
  34. package/node_modules/tardis-dev/dist/realtimefeeds/mexc.d.ts.map +1 -1
  35. package/node_modules/tardis-dev/dist/realtimefeeds/mexc.js +2 -70
  36. package/node_modules/tardis-dev/dist/realtimefeeds/mexc.js.map +1 -1
  37. package/node_modules/tardis-dev/dist/realtimefeeds/mexcprotobuf.d.ts +43 -0
  38. package/node_modules/tardis-dev/dist/realtimefeeds/mexcprotobuf.d.ts.map +1 -0
  39. package/node_modules/tardis-dev/dist/realtimefeeds/mexcprotobuf.js +294 -0
  40. package/node_modules/tardis-dev/dist/realtimefeeds/mexcprotobuf.js.map +1 -0
  41. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts +3 -3
  42. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
  43. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js +3 -3
  44. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js.map +1 -1
  45. package/node_modules/tardis-dev/dist/worker.js +2 -2
  46. package/node_modules/tardis-dev/dist/worker.js.map +1 -1
  47. package/node_modules/tardis-dev/package.json +3 -7
  48. package/node_modules/tardis-dev/src/debug.ts +16 -1
  49. package/node_modules/tardis-dev/src/external-modules.d.ts +5 -0
  50. package/node_modules/tardis-dev/src/handy.ts +93 -89
  51. package/node_modules/tardis-dev/src/realtimefeeds/mexc.ts +2 -72
  52. package/node_modules/tardis-dev/src/realtimefeeds/mexcprotobuf.ts +364 -0
  53. package/node_modules/tardis-dev/src/realtimefeeds/realtimefeed.ts +5 -5
  54. package/node_modules/tardis-dev/src/worker.ts +2 -2
  55. package/package.json +8 -34
  56. package/src/tardismachine.ts +25 -66
  57. package/src/ws/replay.ts +13 -8
  58. package/src/ws/replaynormalized.ts +8 -6
  59. package/src/ws/server.ts +173 -0
  60. package/src/ws/streamnormalized.ts +8 -6
  61. package/src/ws/subscriptionsmappers.ts +1 -1
  62. package/node_modules/follow-redirects/LICENSE +0 -18
  63. package/node_modules/follow-redirects/README.md +0 -157
  64. package/node_modules/follow-redirects/debug.js +0 -15
  65. package/node_modules/follow-redirects/http.js +0 -1
  66. package/node_modules/follow-redirects/https.js +0 -1
  67. package/node_modules/follow-redirects/index.js +0 -709
  68. package/node_modules/follow-redirects/package.json +0 -58
  69. package/node_modules/long/LICENSE +0 -202
  70. package/node_modules/long/README.md +0 -286
  71. package/node_modules/long/index.d.ts +0 -2
  72. package/node_modules/long/index.js +0 -1581
  73. package/node_modules/long/package.json +0 -58
  74. package/node_modules/long/types.d.ts +0 -474
  75. package/node_modules/long/umd/index.d.ts +0 -3
  76. package/node_modules/long/umd/index.js +0 -1622
  77. package/node_modules/long/umd/package.json +0 -3
  78. package/node_modules/long/umd/types.d.ts +0 -474
  79. package/node_modules/protobufjs/LICENSE +0 -39
  80. package/node_modules/protobufjs/README.md +0 -440
  81. package/node_modules/protobufjs/dist/light/protobuf.js +0 -9276
  82. package/node_modules/protobufjs/dist/light/protobuf.js.map +0 -1
  83. package/node_modules/protobufjs/dist/light/protobuf.min.js +0 -8
  84. package/node_modules/protobufjs/dist/light/protobuf.min.js.map +0 -1
  85. package/node_modules/protobufjs/dist/minimal/protobuf.js +0 -3556
  86. package/node_modules/protobufjs/dist/minimal/protobuf.js.map +0 -1
  87. package/node_modules/protobufjs/dist/minimal/protobuf.min.js +0 -8
  88. package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +0 -1
  89. package/node_modules/protobufjs/dist/protobuf.js +0 -11233
  90. package/node_modules/protobufjs/dist/protobuf.js.map +0 -1
  91. package/node_modules/protobufjs/dist/protobuf.min.js +0 -8
  92. package/node_modules/protobufjs/dist/protobuf.min.js.map +0 -1
  93. package/node_modules/protobufjs/ext/README.md +0 -70
  94. package/node_modules/protobufjs/ext/debug/README.md +0 -4
  95. package/node_modules/protobufjs/ext/debug/index.js +0 -71
  96. package/node_modules/protobufjs/ext/descriptor/README.md +0 -5
  97. package/node_modules/protobufjs/ext/descriptor/index.d.ts +0 -2
  98. package/node_modules/protobufjs/ext/descriptor/index.js +0 -2
  99. package/node_modules/protobufjs/ext/descriptor.d.ts +0 -87
  100. package/node_modules/protobufjs/ext/descriptor.generated.d.ts +0 -417
  101. package/node_modules/protobufjs/ext/descriptor.js +0 -1356
  102. package/node_modules/protobufjs/ext/protojson.LICENSE +0 -201
  103. package/node_modules/protobufjs/ext/protojson.d.ts +0 -20
  104. package/node_modules/protobufjs/ext/protojson.generated.d.ts +0 -49
  105. package/node_modules/protobufjs/ext/protojson.js +0 -951
  106. package/node_modules/protobufjs/ext/textformat.d.ts +0 -19
  107. package/node_modules/protobufjs/ext/textformat.generated.d.ts +0 -33
  108. package/node_modules/protobufjs/ext/textformat.js +0 -1276
  109. package/node_modules/protobufjs/google/LICENSE +0 -27
  110. package/node_modules/protobufjs/google/README.md +0 -1
  111. package/node_modules/protobufjs/google/api/annotations.json +0 -85
  112. package/node_modules/protobufjs/google/api/annotations.proto +0 -11
  113. package/node_modules/protobufjs/google/api/http.json +0 -87
  114. package/node_modules/protobufjs/google/api/http.proto +0 -31
  115. package/node_modules/protobufjs/google/protobuf/api.json +0 -124
  116. package/node_modules/protobufjs/google/protobuf/api.proto +0 -34
  117. package/node_modules/protobufjs/google/protobuf/compiler/plugin.json +0 -126
  118. package/node_modules/protobufjs/google/protobuf/compiler/plugin.proto +0 -47
  119. package/node_modules/protobufjs/google/protobuf/descriptor.json +0 -1465
  120. package/node_modules/protobufjs/google/protobuf/descriptor.proto +0 -527
  121. package/node_modules/protobufjs/google/protobuf/source_context.json +0 -21
  122. package/node_modules/protobufjs/google/protobuf/source_context.proto +0 -7
  123. package/node_modules/protobufjs/google/protobuf/type.json +0 -209
  124. package/node_modules/protobufjs/google/protobuf/type.proto +0 -89
  125. package/node_modules/protobufjs/index.d.ts +0 -3203
  126. package/node_modules/protobufjs/index.js +0 -4
  127. package/node_modules/protobufjs/light.d.ts +0 -2
  128. package/node_modules/protobufjs/light.js +0 -4
  129. package/node_modules/protobufjs/minimal.d.ts +0 -2
  130. package/node_modules/protobufjs/minimal.js +0 -4
  131. package/node_modules/protobufjs/package.json +0 -93
  132. package/node_modules/protobufjs/src/common.js +0 -405
  133. package/node_modules/protobufjs/src/converter.js +0 -352
  134. package/node_modules/protobufjs/src/decoder.js +0 -279
  135. package/node_modules/protobufjs/src/encoder.js +0 -123
  136. package/node_modules/protobufjs/src/enum.js +0 -239
  137. package/node_modules/protobufjs/src/field.js +0 -505
  138. package/node_modules/protobufjs/src/index-light.js +0 -104
  139. package/node_modules/protobufjs/src/index-minimal.js +0 -35
  140. package/node_modules/protobufjs/src/index.js +0 -12
  141. package/node_modules/protobufjs/src/mapfield.js +0 -144
  142. package/node_modules/protobufjs/src/message.js +0 -137
  143. package/node_modules/protobufjs/src/method.js +0 -183
  144. package/node_modules/protobufjs/src/namespace.js +0 -574
  145. package/node_modules/protobufjs/src/object.js +0 -392
  146. package/node_modules/protobufjs/src/oneof.js +0 -233
  147. package/node_modules/protobufjs/src/parse.js +0 -1111
  148. package/node_modules/protobufjs/src/reader.js +0 -788
  149. package/node_modules/protobufjs/src/reader_buffer.js +0 -77
  150. package/node_modules/protobufjs/src/root.js +0 -424
  151. package/node_modules/protobufjs/src/roots.js +0 -18
  152. package/node_modules/protobufjs/src/rpc/service.js +0 -155
  153. package/node_modules/protobufjs/src/rpc.js +0 -36
  154. package/node_modules/protobufjs/src/service.js +0 -206
  155. package/node_modules/protobufjs/src/tokenize.js +0 -421
  156. package/node_modules/protobufjs/src/type.js +0 -676
  157. package/node_modules/protobufjs/src/types.js +0 -196
  158. package/node_modules/protobufjs/src/typescript.js +0 -25
  159. package/node_modules/protobufjs/src/util/aspromise.d.ts +0 -13
  160. package/node_modules/protobufjs/src/util/aspromise.js +0 -52
  161. package/node_modules/protobufjs/src/util/base64.d.ts +0 -32
  162. package/node_modules/protobufjs/src/util/base64.js +0 -146
  163. package/node_modules/protobufjs/src/util/codegen.d.ts +0 -31
  164. package/node_modules/protobufjs/src/util/codegen.js +0 -118
  165. package/node_modules/protobufjs/src/util/eventemitter.d.ts +0 -45
  166. package/node_modules/protobufjs/src/util/eventemitter.js +0 -86
  167. package/node_modules/protobufjs/src/util/fetch.d.ts +0 -56
  168. package/node_modules/protobufjs/src/util/fetch.js +0 -112
  169. package/node_modules/protobufjs/src/util/float.d.ts +0 -83
  170. package/node_modules/protobufjs/src/util/float.js +0 -335
  171. package/node_modules/protobufjs/src/util/fs.js +0 -11
  172. package/node_modules/protobufjs/src/util/longbits.js +0 -204
  173. package/node_modules/protobufjs/src/util/minimal.js +0 -511
  174. package/node_modules/protobufjs/src/util/path.d.ts +0 -22
  175. package/node_modules/protobufjs/src/util/path.js +0 -100
  176. package/node_modules/protobufjs/src/util/patterns.js +0 -7
  177. package/node_modules/protobufjs/src/util/pool.d.ts +0 -32
  178. package/node_modules/protobufjs/src/util/pool.js +0 -48
  179. package/node_modules/protobufjs/src/util/utf8.d.ts +0 -24
  180. package/node_modules/protobufjs/src/util/utf8.js +0 -190
  181. package/node_modules/protobufjs/src/util.js +0 -264
  182. package/node_modules/protobufjs/src/verifier.js +0 -185
  183. package/node_modules/protobufjs/src/wrappers.js +0 -106
  184. package/node_modules/protobufjs/src/writer.js +0 -700
  185. package/node_modules/protobufjs/src/writer_buffer.js +0 -100
  186. package/node_modules/protobufjs/tsconfig.json +0 -6
  187. package/node_modules/uWebSockets.js/ESM_wrapper.mjs +0 -53
  188. package/node_modules/uWebSockets.js/LICENSE +0 -201
  189. package/node_modules/uWebSockets.js/README.md +0 -6
  190. package/node_modules/uWebSockets.js/index.d.ts +0 -468
  191. package/node_modules/uWebSockets.js/package.json +0 -12
  192. package/node_modules/uWebSockets.js/source_commit +0 -1
  193. package/node_modules/uWebSockets.js/uws.js +0 -76
  194. package/node_modules/uWebSockets.js/uws_darwin_arm64_127.node +0 -0
  195. package/node_modules/uWebSockets.js/uws_darwin_arm64_137.node +0 -0
  196. package/node_modules/uWebSockets.js/uws_darwin_arm64_147.node +0 -0
  197. package/node_modules/uWebSockets.js/uws_darwin_x64_127.node +0 -0
  198. package/node_modules/uWebSockets.js/uws_darwin_x64_137.node +0 -0
  199. package/node_modules/uWebSockets.js/uws_darwin_x64_147.node +0 -0
  200. package/node_modules/uWebSockets.js/uws_linux_arm64_127.node +0 -0
  201. package/node_modules/uWebSockets.js/uws_linux_arm64_137.node +0 -0
  202. package/node_modules/uWebSockets.js/uws_linux_arm64_147.node +0 -0
  203. package/node_modules/uWebSockets.js/uws_linux_x64_127.node +0 -0
  204. package/node_modules/uWebSockets.js/uws_linux_x64_137.node +0 -0
  205. package/node_modules/uWebSockets.js/uws_linux_x64_147.node +0 -0
  206. package/node_modules/uWebSockets.js/uws_win32_x64_127.node +0 -0
  207. package/node_modules/uWebSockets.js/uws_win32_x64_137.node +0 -0
  208. package/node_modules/uWebSockets.js/uws_win32_x64_147.node +0 -0
@@ -1,14 +1,13 @@
1
1
  import crypto, { createHash } from 'crypto'
2
2
  import { createWriteStream, mkdirSync, rmSync } from 'node:fs'
3
3
  import { rename } from 'node:fs/promises'
4
- import { Agent as HttpAgent } from 'node:http'
5
- import { Agent as HttpsAgent, type RequestOptions } from 'node:https'
6
- import followRedirects from 'follow-redirects'
4
+ import { Agent as HttpAgent, request as httpRequest, type IncomingMessage } from 'node:http'
5
+ import { Agent as HttpsAgent, request as httpsRequest, type RequestOptions } from 'node:https'
6
+ import { pipeline } from 'node:stream/promises'
7
7
  import path from 'path'
8
8
  import { debug } from './debug.ts'
9
9
  import { Mapper } from './mappers/index.ts'
10
10
  import { Disconnect, Exchange, Filter, FilterForExchange } from './types.ts'
11
- const { http: followRedirectsHttp, https: followRedirectsHttps } = followRedirects
12
11
 
13
12
  export function parseAsUTCDate(val: string) {
14
13
  // Treat date-only and minute-level strings as UTC instead of local time.
@@ -456,44 +455,75 @@ async function requestViaFetch(method: string, url: string, options: HttpRequest
456
455
  }
457
456
  }
458
457
 
459
- async function requestViaProxy(method: string, url: string, options: HttpRequestOptions): Promise<HttpResponse> {
460
- const requestClient = new URL(url).protocol === 'http:' ? followRedirectsHttp : followRedirectsHttps
461
- const preparedRequest = prepareRequest(method, options)
458
+ type DownloadRequestOptions = {
459
+ headers: Record<string, string>
460
+ timeout: number
461
+ }
462
462
 
463
- return await new Promise<HttpResponse>((resolve, reject) => {
464
- const request = requestClient
465
- .request(
466
- url,
467
- {
468
- method,
469
- agent: getProxyAgent(url),
470
- headers: preparedRequest.headers,
471
- timeout: options.timeout
472
- },
473
- (response) => {
474
- response.setEncoding('utf8')
475
- let body = ''
476
- response.on('error', reject)
477
- response.on('data', (chunk) => (body += chunk))
478
- response.on('end', () => {
479
- resolve(createHttpResponse(response.statusCode ?? 0, parseNodeResponseHeaders(response.headers), body))
480
- })
481
- }
482
- )
483
- .on('error', reject)
484
- .on('timeout', () => {
485
- reject(new Error('Request timed out'))
486
- request.destroy()
487
- })
488
-
489
- if (preparedRequest.body !== undefined) {
490
- request.write(preparedRequest.body)
491
- }
463
+ function sendHttpRequest(url: string | URL, options: RequestOptions, body?: string): Promise<IncomingMessage> {
464
+ const requestUrl = new URL(url)
465
+ const requestClient = requestUrl.protocol === 'http:' ? httpRequest : httpsRequest
466
+ const agent = getProxyAgent(requestUrl) ?? (requestUrl.protocol === 'https:' ? httpsAgent : undefined)
467
+
468
+ return new Promise((resolve, reject) => {
469
+ const request = requestClient(requestUrl, { ...options, agent }, resolve)
492
470
 
471
+ request.once('error', reject)
472
+ request.once('timeout', () => request.destroy(new Error('Request timed out')))
473
+ if (body !== undefined) {
474
+ request.write(body)
475
+ }
493
476
  request.end()
494
477
  })
495
478
  }
496
479
 
480
+ async function openDownloadResponse(url: string, options: DownloadRequestOptions) {
481
+ const response = await sendHttpRequest(url, options)
482
+ const statusCode = response.statusCode ?? 0
483
+ const location = response.headers.location
484
+ if (location === undefined || statusCode < 300 || statusCode >= 400) {
485
+ return response
486
+ }
487
+
488
+ response.destroy()
489
+ const sourceUrl = new URL(url)
490
+ const redirectUrl = new URL(location, sourceUrl)
491
+ const headers: Record<string, string> = { ...options.headers }
492
+ if (sourceUrl.origin !== redirectUrl.origin) {
493
+ delete headers.Authorization
494
+ delete headers.authorization
495
+ }
496
+
497
+ return sendHttpRequest(redirectUrl, {
498
+ ...options,
499
+ headers
500
+ })
501
+ }
502
+
503
+ async function readResponseText(response: IncomingMessage) {
504
+ response.setEncoding('utf8')
505
+ let body = ''
506
+ for await (const chunk of response) {
507
+ body += chunk
508
+ }
509
+ return body
510
+ }
511
+
512
+ async function requestViaProxy(method: string, url: string, options: HttpRequestOptions): Promise<HttpResponse> {
513
+ const preparedRequest = prepareRequest(method, options)
514
+ const response = await sendHttpRequest(
515
+ url,
516
+ {
517
+ method,
518
+ headers: preparedRequest.headers,
519
+ timeout: options.timeout
520
+ },
521
+ preparedRequest.body
522
+ )
523
+
524
+ return createHttpResponse(response.statusCode ?? 0, parseNodeResponseHeaders(response.headers), await readResponseText(response))
525
+ }
526
+
497
527
  async function request(method: string, url: string, options: HttpRequestOptions = {}) {
498
528
  const retrySettings = getRetrySettings(method, options.retry)
499
529
 
@@ -622,7 +652,12 @@ export function cleanTempFiles() {
622
652
  tmpFileCleanups.forEach((cleanup) => cleanup())
623
653
  }
624
654
 
625
- async function _downloadFile(requestOptions: RequestOptions, url: string, downloadPath: string, appendContentEncodingExtension: boolean) {
655
+ async function _downloadFile(
656
+ requestOptions: DownloadRequestOptions,
657
+ url: string,
658
+ downloadPath: string,
659
+ appendContentEncodingExtension: boolean
660
+ ) {
626
661
  // first ensure that directory where we want to download file exists
627
662
  mkdirSync(path.dirname(downloadPath), { recursive: true })
628
663
 
@@ -642,59 +677,28 @@ async function _downloadFile(requestOptions: RequestOptions, url: string, downlo
642
677
 
643
678
  try {
644
679
  // based on https://github.com/nodejs/node/issues/28172 - only reliable way to consume response stream and avoiding all the 'gotchas'
645
- let responseHeaders: Record<string, string> = {}
646
- await new Promise<void>((resolve, reject) => {
647
- const protocol = new URL(url).protocol
648
- const requestClient = protocol === 'http:' ? followRedirectsHttp : followRedirectsHttps
649
- const agent = getProxyAgent(url) ?? (protocol === 'https:' ? httpsAgent : undefined)
650
- const req = requestClient
651
- .get(url, { ...requestOptions, agent }, (res) => {
652
- const { statusCode } = res
653
- if (statusCode !== 200) {
654
- // read the error response text and throw it as an HttpError
655
- res.setEncoding('utf8')
656
- let body = ''
657
- res.on('error', reject)
658
- res.on('data', (chunk) => (body += chunk))
659
- res.on('end', () => {
660
- reject(new HttpError(statusCode!, body, url))
661
- })
662
- } else {
663
- responseHeaders = parseNodeResponseHeaders(res.headers)
664
- if (appendContentEncodingExtension) {
665
- const contentEncoding = asSingleHeaderValue(res.headers['content-encoding'])
666
- if (contentEncoding === 'zstd') {
667
- finalDownloadPath = `${downloadPath}.zst`
668
- } else if (contentEncoding === undefined || contentEncoding === 'gzip') {
669
- finalDownloadPath = `${downloadPath}.gz`
670
- } else {
671
- reject(new Error(`Unsupported data feed content encoding: ${contentEncoding}`))
672
- return
673
- }
674
- }
680
+ const response = await openDownloadResponse(url, requestOptions)
681
+ const { statusCode } = response
682
+ if (statusCode !== 200) {
683
+ throw new HttpError(statusCode ?? 0, await readResponseText(response), url)
684
+ }
675
685
 
676
- // consume the response stream by writing it to the file
677
- res
678
- .on('error', reject)
679
- .on('aborted', () => reject(new Error('Request aborted')))
680
- .pipe(fileWriteStream)
681
- .on('error', reject)
682
- .on('finish', () => {
683
- if (res.complete) {
684
- resolve()
685
- } else {
686
- reject(new Error('The connection was terminated while the message was still being sent'))
687
- }
688
- })
689
- }
690
- })
691
- .on('error', reject)
692
- .on('timeout', () => {
693
- debug('download file request timeout, %s', url)
694
- reject(new Error('Request timed out'))
695
- req.destroy()
696
- })
697
- })
686
+ const responseHeaders = parseNodeResponseHeaders(response.headers)
687
+ if (appendContentEncodingExtension) {
688
+ const contentEncoding = asSingleHeaderValue(response.headers['content-encoding'])
689
+ if (contentEncoding === 'zstd') {
690
+ finalDownloadPath = `${downloadPath}.zst`
691
+ } else if (contentEncoding === undefined || contentEncoding === 'gzip') {
692
+ finalDownloadPath = `${downloadPath}.gz`
693
+ } else {
694
+ throw new Error(`Unsupported data feed content encoding: ${contentEncoding}`)
695
+ }
696
+ }
697
+
698
+ await pipeline(response, fileWriteStream)
699
+ if (!response.complete) {
700
+ throw new Error('The connection was terminated while the message was still being sent')
701
+ }
698
702
 
699
703
  // finally when saving from the network to file has succeded, rename tmp file to normal name
700
704
  // then we're sure that responses is 100% saved and also even if different process was doing the same we're good
@@ -1,71 +1,13 @@
1
- import protobuf from 'protobufjs'
2
1
  import { CircularBuffer, getJSON, wait } from '../handy.ts'
3
2
  import type { MexcDepthSnapshotMessage } from '../mappers/mexc.ts'
4
3
  import { Filter } from '../types.ts'
5
4
  import { RealTimeFeedBase } from './realtimefeed.ts'
5
+ import { decodeMexcProtobufMessage } from './mexcprotobuf.ts'
6
6
 
7
7
  export class MexcRealTimeFeed extends RealTimeFeedBase {
8
8
  private static readonly depthChannel = 'spot@public.aggre.depth.v3.api.pb@10ms'
9
9
  private static readonly depthSnapshotChannel = 'spot@public.aggre.depth.snapshot.v3.api.pb@10ms'
10
10
  private static readonly jsonObjectStart = '{'.charCodeAt(0)
11
- private static pushDataV3ApiWrapper: protobuf.Type | undefined
12
- /**
13
- * MEXC protobuf docs at @see https://www.mexc.com/api-docs/spot-v3/websocket-market-streams/protocol-buffers-integration
14
- * and schemas from @see https://github.com/mexcdevelop/websocket-proto, mirrored in tardis-recorder/datafeeds/src/mexc/proto.
15
- */
16
- private static readonly pushDataV3ApiWrapperSchema = `
17
- syntax = "proto3";
18
-
19
- message PushDataV3ApiWrapper {
20
- string channel = 1;
21
- oneof body {
22
- PublicAggreDepthsV3Api publicAggreDepths = 313;
23
- PublicAggreDealsV3Api publicAggreDeals = 314;
24
- PublicAggreBookTickerV3Api publicAggreBookTicker = 315;
25
- }
26
-
27
- optional string symbol = 3;
28
- optional string symbolId = 4;
29
- optional int64 createTime = 5;
30
- optional int64 sendTime = 6;
31
- }
32
-
33
- message PublicAggreDealsV3Api {
34
- repeated PublicAggreDealsV3ApiItem deals = 1;
35
- string eventType = 2;
36
- }
37
-
38
- message PublicAggreDealsV3ApiItem {
39
- string price = 1;
40
- string quantity = 2;
41
- int32 tradeType = 3;
42
- int64 time = 4;
43
- string tradeId = 5;
44
- }
45
-
46
- message PublicAggreDepthsV3Api {
47
- repeated PublicAggreDepthV3ApiItem asks = 1;
48
- repeated PublicAggreDepthV3ApiItem bids = 2;
49
- string eventType = 3;
50
- string fromVersion = 4;
51
- string toVersion = 5;
52
- int64 lastOrderCreateTime = 6;
53
- }
54
-
55
- message PublicAggreDepthV3ApiItem {
56
- string price = 1;
57
- string quantity = 2;
58
- }
59
-
60
- message PublicAggreBookTickerV3Api {
61
- string bidPrice = 1;
62
- string bidQuantity = 2;
63
- string askPrice = 3;
64
- string askQuantity = 4;
65
- string version = 5;
66
- int64 lastOrderCreateTime = 6;
67
- }
68
- `
69
11
 
70
12
  private readonly pendingDepthSnapshotSymbols = new Set<string>()
71
13
  private readonly bufferedDepthUpdates = new Map<string, CircularBuffer<MexcDepthUpdateData>>()
@@ -114,11 +56,7 @@ export class MexcRealTimeFeed extends RealTimeFeedBase {
114
56
  return JSON.parse(buffer.toString())
115
57
  }
116
58
 
117
- const pushDataV3ApiWrapper = this.getPushDataV3ApiWrapper()
118
- return pushDataV3ApiWrapper.toObject(pushDataV3ApiWrapper.decode(buffer), {
119
- longs: String,
120
- arrays: true
121
- })
59
+ return decodeMexcProtobufMessage(buffer)
122
60
  }
123
61
 
124
62
  protected override messageIsError(message: any): boolean {
@@ -168,14 +106,6 @@ export class MexcRealTimeFeed extends RealTimeFeedBase {
168
106
  this.send({ method: 'PING' })
169
107
  }
170
108
 
171
- private getPushDataV3ApiWrapper() {
172
- MexcRealTimeFeed.pushDataV3ApiWrapper ??= protobuf
173
- .parse(MexcRealTimeFeed.pushDataV3ApiWrapperSchema)
174
- .root.lookupType('PushDataV3ApiWrapper')
175
-
176
- return MexcRealTimeFeed.pushDataV3ApiWrapper
177
- }
178
-
179
109
  private resetDepthSnapshotTracking(filters: Required<Filter<string>>[]) {
180
110
  this.pendingDepthSnapshotSymbols.clear()
181
111
  this.bufferedDepthUpdates.clear()
@@ -0,0 +1,364 @@
1
+ const textDecoder = new TextDecoder()
2
+
3
+ // Decodes only the current MEXC real-time channels supported by MexcRealTimeFeed.
4
+ // Field numbers mirror the official schemas at commit 7b8ac7a6681f28551612a5a7cefbb7e09b56bb85:
5
+ // https://github.com/mexcdevelop/websocket-proto/tree/7b8ac7a6681f28551612a5a7cefbb7e09b56bb85
6
+ // int64 values remain decimal strings and repeated fields remain empty arrays when absent,
7
+ // preserving the output shape previously produced by protobufjs.
8
+ export function decodeMexcProtobufMessage(data: Uint8Array) {
9
+ const reader = new ProtobufReader(data)
10
+ const message: {
11
+ channel?: string
12
+ symbol?: string
13
+ symbolId?: string
14
+ createTime?: string
15
+ sendTime?: string
16
+ publicAggreDepths?: ReturnType<typeof decodeDepths>
17
+ publicAggreDeals?: ReturnType<typeof decodeDeals>
18
+ publicAggreBookTicker?: ReturnType<typeof decodeBookTicker>
19
+ } = {}
20
+
21
+ while (reader.done === false) {
22
+ const { fieldNumber, wireType } = reader.readTag()
23
+
24
+ switch (fieldNumber) {
25
+ case 1:
26
+ message.channel = reader.readString(wireType)
27
+ break
28
+ case 3:
29
+ message.symbol = reader.readString(wireType)
30
+ break
31
+ case 4:
32
+ message.symbolId = reader.readString(wireType)
33
+ break
34
+ case 5:
35
+ message.createTime = reader.readInt64String(wireType)
36
+ break
37
+ case 6:
38
+ message.sendTime = reader.readInt64String(wireType)
39
+ break
40
+ case 313:
41
+ message.publicAggreDepths = decodeDepths(reader.readBytes(wireType))
42
+ break
43
+ case 314:
44
+ message.publicAggreDeals = decodeDeals(reader.readBytes(wireType))
45
+ break
46
+ case 315:
47
+ message.publicAggreBookTicker = decodeBookTicker(reader.readBytes(wireType))
48
+ break
49
+ default:
50
+ reader.skipField(wireType)
51
+ }
52
+ }
53
+
54
+ return message
55
+ }
56
+
57
+ function decodeDeals(data: Uint8Array) {
58
+ const reader = new ProtobufReader(data)
59
+ const message: { deals: ReturnType<typeof decodeTrade>[]; eventType?: string } = { deals: [] }
60
+
61
+ while (reader.done === false) {
62
+ const { fieldNumber, wireType } = reader.readTag()
63
+
64
+ switch (fieldNumber) {
65
+ case 1:
66
+ message.deals.push(decodeTrade(reader.readBytes(wireType)))
67
+ break
68
+ case 2:
69
+ message.eventType = reader.readString(wireType)
70
+ break
71
+ default:
72
+ reader.skipField(wireType)
73
+ }
74
+ }
75
+
76
+ return message
77
+ }
78
+
79
+ function decodeTrade(data: Uint8Array) {
80
+ const reader = new ProtobufReader(data)
81
+ const message: { price?: string; quantity?: string; tradeType?: number; time?: string; tradeId?: string } = {}
82
+
83
+ while (reader.done === false) {
84
+ const { fieldNumber, wireType } = reader.readTag()
85
+
86
+ switch (fieldNumber) {
87
+ case 1:
88
+ message.price = reader.readString(wireType)
89
+ break
90
+ case 2:
91
+ message.quantity = reader.readString(wireType)
92
+ break
93
+ case 3:
94
+ message.tradeType = reader.readInt32(wireType)
95
+ break
96
+ case 4:
97
+ message.time = reader.readInt64String(wireType)
98
+ break
99
+ case 5:
100
+ message.tradeId = reader.readString(wireType)
101
+ break
102
+ default:
103
+ reader.skipField(wireType)
104
+ }
105
+ }
106
+
107
+ return message
108
+ }
109
+
110
+ function decodeDepths(data: Uint8Array) {
111
+ const reader = new ProtobufReader(data)
112
+ const message: {
113
+ asks: ReturnType<typeof decodePriceLevel>[]
114
+ bids: ReturnType<typeof decodePriceLevel>[]
115
+ eventType?: string
116
+ fromVersion?: string
117
+ toVersion?: string
118
+ lastOrderCreateTime?: string
119
+ } = { asks: [], bids: [] }
120
+
121
+ while (reader.done === false) {
122
+ const { fieldNumber, wireType } = reader.readTag()
123
+
124
+ switch (fieldNumber) {
125
+ case 1:
126
+ message.asks.push(decodePriceLevel(reader.readBytes(wireType)))
127
+ break
128
+ case 2:
129
+ message.bids.push(decodePriceLevel(reader.readBytes(wireType)))
130
+ break
131
+ case 3:
132
+ message.eventType = reader.readString(wireType)
133
+ break
134
+ case 4:
135
+ message.fromVersion = reader.readString(wireType)
136
+ break
137
+ case 5:
138
+ message.toVersion = reader.readString(wireType)
139
+ break
140
+ case 6:
141
+ message.lastOrderCreateTime = reader.readInt64String(wireType)
142
+ break
143
+ default:
144
+ reader.skipField(wireType)
145
+ }
146
+ }
147
+
148
+ return message
149
+ }
150
+
151
+ function decodePriceLevel(data: Uint8Array) {
152
+ const reader = new ProtobufReader(data)
153
+ const message: { price?: string; quantity?: string } = {}
154
+
155
+ while (reader.done === false) {
156
+ const { fieldNumber, wireType } = reader.readTag()
157
+
158
+ switch (fieldNumber) {
159
+ case 1:
160
+ message.price = reader.readString(wireType)
161
+ break
162
+ case 2:
163
+ message.quantity = reader.readString(wireType)
164
+ break
165
+ default:
166
+ reader.skipField(wireType)
167
+ }
168
+ }
169
+
170
+ return message
171
+ }
172
+
173
+ function decodeBookTicker(data: Uint8Array) {
174
+ const reader = new ProtobufReader(data)
175
+ const message: {
176
+ bidPrice?: string
177
+ bidQuantity?: string
178
+ askPrice?: string
179
+ askQuantity?: string
180
+ version?: string
181
+ lastOrderCreateTime?: string
182
+ } = {}
183
+
184
+ while (reader.done === false) {
185
+ const { fieldNumber, wireType } = reader.readTag()
186
+
187
+ switch (fieldNumber) {
188
+ case 1:
189
+ message.bidPrice = reader.readString(wireType)
190
+ break
191
+ case 2:
192
+ message.bidQuantity = reader.readString(wireType)
193
+ break
194
+ case 3:
195
+ message.askPrice = reader.readString(wireType)
196
+ break
197
+ case 4:
198
+ message.askQuantity = reader.readString(wireType)
199
+ break
200
+ case 5:
201
+ message.version = reader.readString(wireType)
202
+ break
203
+ case 6:
204
+ message.lastOrderCreateTime = reader.readInt64String(wireType)
205
+ break
206
+ default:
207
+ reader.skipField(wireType)
208
+ }
209
+ }
210
+
211
+ return message
212
+ }
213
+
214
+ class ProtobufReader {
215
+ private offset = 0
216
+
217
+ constructor(private readonly data: Uint8Array) {}
218
+
219
+ get done() {
220
+ return this.offset === this.data.length
221
+ }
222
+
223
+ readTag() {
224
+ const tag = this.readVarint()
225
+ if (typeof tag === 'bigint' || tag > 0xffffffff) {
226
+ throw new Error('Invalid protobuf tag')
227
+ }
228
+
229
+ const fieldNumber = tag >>> 3
230
+ const wireType = tag & 7
231
+
232
+ if (fieldNumber === 0) {
233
+ throw new Error('Invalid protobuf field number 0')
234
+ }
235
+
236
+ return { fieldNumber, wireType }
237
+ }
238
+
239
+ readString(wireType: number) {
240
+ this.expectWireType(wireType, 2)
241
+ const length = this.readLength()
242
+ const start = this.offset
243
+ const end = start + length
244
+
245
+ if (end > this.data.length) {
246
+ throw new Error('Invalid protobuf message length')
247
+ }
248
+
249
+ this.offset = end
250
+ return Buffer.isBuffer(this.data) ? this.data.toString('utf8', start, end) : textDecoder.decode(this.data.subarray(start, end))
251
+ }
252
+
253
+ readBytes(wireType: number) {
254
+ this.expectWireType(wireType, 2)
255
+ const length = this.readLength()
256
+ const end = this.offset + length
257
+
258
+ if (end > this.data.length) {
259
+ throw new Error('Invalid protobuf message length')
260
+ }
261
+
262
+ const value = this.data.subarray(this.offset, end)
263
+ this.offset = end
264
+ return value
265
+ }
266
+
267
+ readInt32(wireType: number) {
268
+ this.expectWireType(wireType, 0)
269
+ const value = this.readVarint()
270
+ return typeof value === 'number' ? value | 0 : Number(BigInt.asIntN(32, value))
271
+ }
272
+
273
+ readInt64String(wireType: number) {
274
+ this.expectWireType(wireType, 0)
275
+ const value = this.readVarint()
276
+ return typeof value === 'number' ? value.toString() : BigInt.asIntN(64, value).toString()
277
+ }
278
+
279
+ skipField(wireType: number): void {
280
+ switch (wireType) {
281
+ case 0:
282
+ this.skipVarint()
283
+ return
284
+ case 1:
285
+ this.skipBytes(8)
286
+ return
287
+ case 2:
288
+ this.skipBytes(this.readLength())
289
+ return
290
+ case 5:
291
+ this.skipBytes(4)
292
+ return
293
+ default:
294
+ throw new Error(`Unsupported protobuf wire type ${wireType}`)
295
+ }
296
+ }
297
+
298
+ private readVarint() {
299
+ let value = 0
300
+ let multiplier = 1
301
+
302
+ // Seven base-128 digits use at most 49 bits, so they remain exact and fast as a number.
303
+ for (let index = 0; index < 7; index++) {
304
+ const byte = this.readByte()
305
+ value += (byte & 0x7f) * multiplier
306
+
307
+ if ((byte & 0x80) === 0) {
308
+ return value
309
+ }
310
+ multiplier *= 0x80
311
+ }
312
+
313
+ // Larger int64 values need BigInt to preserve every bit before converting them to strings.
314
+ let bigValue = BigInt(value)
315
+ for (let index = 7; index < 10; index++) {
316
+ const byte = this.readByte()
317
+ bigValue |= BigInt(byte & 0x7f) << BigInt(index * 7)
318
+
319
+ if ((byte & 0x80) === 0) {
320
+ return bigValue
321
+ }
322
+ }
323
+
324
+ throw new Error('Invalid protobuf varint')
325
+ }
326
+
327
+ private readLength() {
328
+ const length = this.readVarint()
329
+ if (typeof length === 'bigint' || Number.isSafeInteger(length) === false) {
330
+ throw new Error('Invalid protobuf message length')
331
+ }
332
+ return length
333
+ }
334
+
335
+ private skipVarint() {
336
+ for (let index = 0; index < 10; index++) {
337
+ if ((this.readByte() & 0x80) === 0) {
338
+ return
339
+ }
340
+ }
341
+ throw new Error('Invalid protobuf varint')
342
+ }
343
+
344
+ private readByte() {
345
+ if (this.offset >= this.data.length) {
346
+ throw new Error('Unexpected end of protobuf message')
347
+ }
348
+ return this.data[this.offset++]!
349
+ }
350
+
351
+ private skipBytes(length: number) {
352
+ const end = this.offset + length
353
+ if (Number.isSafeInteger(length) === false || end > this.data.length) {
354
+ throw new Error('Invalid protobuf message length')
355
+ }
356
+ this.offset = end
357
+ }
358
+
359
+ private expectWireType(actual: number, expected: number) {
360
+ if (actual !== expected) {
361
+ throw new Error(`Invalid protobuf wire type ${actual}, expected ${expected}`)
362
+ }
363
+ }
364
+ }