recker 1.0.2-0 → 1.0.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.
Files changed (287) hide show
  1. package/LICENSE +0 -2
  2. package/README.md +121 -72
  3. package/dist/cache/memory-storage.d.ts.map +1 -1
  4. package/dist/cache/memory-storage.js +7 -1
  5. package/dist/constants/http-status.d.ts +74 -0
  6. package/dist/constants/http-status.d.ts.map +1 -0
  7. package/dist/constants/http-status.js +156 -0
  8. package/dist/constants.d.ts.map +1 -1
  9. package/dist/constants.js +6 -6
  10. package/dist/cookies/memory-cookie-jar.d.ts +31 -0
  11. package/dist/cookies/memory-cookie-jar.d.ts.map +1 -0
  12. package/dist/cookies/memory-cookie-jar.js +210 -0
  13. package/dist/core/client.d.ts +9 -0
  14. package/dist/core/client.d.ts.map +1 -1
  15. package/dist/core/client.js +252 -53
  16. package/dist/core/errors.d.ts +18 -2
  17. package/dist/core/errors.d.ts.map +1 -1
  18. package/dist/core/errors.js +66 -5
  19. package/dist/core/index.d.ts +6 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +5 -0
  22. package/dist/core/request-promise.d.ts.map +1 -1
  23. package/dist/core/request-promise.js +8 -2
  24. package/dist/core/request.d.ts +7 -1
  25. package/dist/core/request.d.ts.map +1 -1
  26. package/dist/core/request.js +32 -0
  27. package/dist/core/response.d.ts +2 -0
  28. package/dist/core/response.d.ts.map +1 -1
  29. package/dist/core/response.js +44 -19
  30. package/dist/events/request-events.d.ts +48 -0
  31. package/dist/events/request-events.d.ts.map +1 -0
  32. package/dist/events/request-events.js +85 -0
  33. package/dist/index.d.ts +28 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +28 -2
  36. package/dist/mcp/client.d.ts.map +1 -1
  37. package/dist/mcp/client.js +16 -5
  38. package/dist/mcp/contract.d.ts +77 -0
  39. package/dist/mcp/contract.d.ts.map +1 -0
  40. package/dist/mcp/contract.js +278 -0
  41. package/dist/mcp/types.d.ts +1 -0
  42. package/dist/mcp/types.d.ts.map +1 -1
  43. package/dist/plugins/auth.d.ts +45 -0
  44. package/dist/plugins/auth.d.ts.map +1 -0
  45. package/dist/plugins/auth.js +268 -0
  46. package/dist/plugins/cache.d.ts +7 -1
  47. package/dist/plugins/cache.d.ts.map +1 -1
  48. package/dist/plugins/cache.js +470 -49
  49. package/dist/plugins/circuit-breaker.js +1 -1
  50. package/dist/plugins/compression.d.ts.map +1 -1
  51. package/dist/plugins/compression.js +3 -3
  52. package/dist/plugins/dedup.d.ts.map +1 -1
  53. package/dist/plugins/dedup.js +2 -1
  54. package/dist/plugins/graphql.d.ts +4 -3
  55. package/dist/plugins/graphql.d.ts.map +1 -1
  56. package/dist/plugins/graphql.js +24 -5
  57. package/dist/plugins/grpc-web.d.ts +80 -0
  58. package/dist/plugins/grpc-web.d.ts.map +1 -0
  59. package/dist/plugins/grpc-web.js +261 -0
  60. package/dist/plugins/har-player.d.ts.map +1 -1
  61. package/dist/plugins/har-player.js +11 -2
  62. package/dist/plugins/hls.d.ts +33 -0
  63. package/dist/plugins/hls.d.ts.map +1 -0
  64. package/dist/plugins/hls.js +225 -0
  65. package/dist/plugins/http2-push.d.ts +64 -0
  66. package/dist/plugins/http2-push.d.ts.map +1 -0
  67. package/dist/plugins/http2-push.js +274 -0
  68. package/dist/plugins/http3.d.ts +76 -0
  69. package/dist/plugins/http3.d.ts.map +1 -0
  70. package/dist/plugins/http3.js +231 -0
  71. package/dist/plugins/interface-rotator.d.ts +10 -0
  72. package/dist/plugins/interface-rotator.d.ts.map +1 -0
  73. package/dist/plugins/interface-rotator.js +57 -0
  74. package/dist/plugins/jsonrpc.d.ts +76 -0
  75. package/dist/plugins/jsonrpc.d.ts.map +1 -0
  76. package/dist/plugins/jsonrpc.js +143 -0
  77. package/dist/plugins/logger.d.ts +8 -5
  78. package/dist/plugins/logger.d.ts.map +1 -1
  79. package/dist/plugins/logger.js +66 -30
  80. package/dist/plugins/odata.d.ts +182 -0
  81. package/dist/plugins/odata.d.ts.map +1 -0
  82. package/dist/plugins/odata.js +561 -0
  83. package/dist/plugins/retry.d.ts +1 -0
  84. package/dist/plugins/retry.d.ts.map +1 -1
  85. package/dist/plugins/retry.js +26 -2
  86. package/dist/plugins/scrape.d.ts +22 -0
  87. package/dist/plugins/scrape.d.ts.map +1 -0
  88. package/dist/plugins/scrape.js +87 -0
  89. package/dist/plugins/soap.d.ts +73 -0
  90. package/dist/plugins/soap.d.ts.map +1 -0
  91. package/dist/plugins/soap.js +347 -0
  92. package/dist/plugins/user-agent.d.ts +8 -0
  93. package/dist/plugins/user-agent.d.ts.map +1 -0
  94. package/dist/plugins/user-agent.js +46 -0
  95. package/dist/plugins/xml.d.ts +10 -0
  96. package/dist/plugins/xml.d.ts.map +1 -0
  97. package/dist/plugins/xml.js +194 -0
  98. package/dist/presets/anthropic.d.ts +7 -0
  99. package/dist/presets/anthropic.d.ts.map +1 -0
  100. package/dist/presets/anthropic.js +17 -0
  101. package/dist/presets/azure-openai.d.ts +9 -0
  102. package/dist/presets/azure-openai.d.ts.map +1 -0
  103. package/dist/presets/azure-openai.js +25 -0
  104. package/dist/presets/cloudflare.d.ts +13 -0
  105. package/dist/presets/cloudflare.d.ts.map +1 -0
  106. package/dist/presets/cloudflare.js +39 -0
  107. package/dist/presets/cohere.d.ts +6 -0
  108. package/dist/presets/cohere.d.ts.map +1 -0
  109. package/dist/presets/cohere.js +16 -0
  110. package/dist/presets/deepseek.d.ts +6 -0
  111. package/dist/presets/deepseek.d.ts.map +1 -0
  112. package/dist/presets/deepseek.js +16 -0
  113. package/dist/presets/digitalocean.d.ts +6 -0
  114. package/dist/presets/digitalocean.d.ts.map +1 -0
  115. package/dist/presets/digitalocean.js +16 -0
  116. package/dist/presets/discord.d.ts +7 -0
  117. package/dist/presets/discord.d.ts.map +1 -0
  118. package/dist/presets/discord.js +17 -0
  119. package/dist/presets/fireworks.d.ts +6 -0
  120. package/dist/presets/fireworks.d.ts.map +1 -0
  121. package/dist/presets/fireworks.js +16 -0
  122. package/dist/presets/gemini.d.ts +6 -0
  123. package/dist/presets/gemini.d.ts.map +1 -0
  124. package/dist/presets/gemini.js +16 -0
  125. package/dist/presets/github.d.ts +7 -0
  126. package/dist/presets/github.d.ts.map +1 -0
  127. package/dist/presets/github.js +17 -0
  128. package/dist/presets/gitlab.d.ts +7 -0
  129. package/dist/presets/gitlab.d.ts.map +1 -0
  130. package/dist/presets/gitlab.js +16 -0
  131. package/dist/presets/groq.d.ts +6 -0
  132. package/dist/presets/groq.d.ts.map +1 -0
  133. package/dist/presets/groq.js +16 -0
  134. package/dist/presets/huggingface.d.ts +6 -0
  135. package/dist/presets/huggingface.d.ts.map +1 -0
  136. package/dist/presets/huggingface.js +16 -0
  137. package/dist/presets/index.d.ts +28 -0
  138. package/dist/presets/index.d.ts.map +1 -0
  139. package/dist/presets/index.js +27 -0
  140. package/dist/presets/linear.d.ts +6 -0
  141. package/dist/presets/linear.d.ts.map +1 -0
  142. package/dist/presets/linear.js +16 -0
  143. package/dist/presets/mistral.d.ts +6 -0
  144. package/dist/presets/mistral.d.ts.map +1 -0
  145. package/dist/presets/mistral.js +16 -0
  146. package/dist/presets/notion.d.ts +7 -0
  147. package/dist/presets/notion.d.ts.map +1 -0
  148. package/dist/presets/notion.js +17 -0
  149. package/dist/presets/openai.d.ts +8 -0
  150. package/dist/presets/openai.d.ts.map +1 -0
  151. package/dist/presets/openai.js +23 -0
  152. package/dist/presets/perplexity.d.ts +6 -0
  153. package/dist/presets/perplexity.d.ts.map +1 -0
  154. package/dist/presets/perplexity.js +16 -0
  155. package/dist/presets/registry.d.ts +20 -0
  156. package/dist/presets/registry.d.ts.map +1 -0
  157. package/dist/presets/registry.js +311 -0
  158. package/dist/presets/replicate.d.ts +6 -0
  159. package/dist/presets/replicate.d.ts.map +1 -0
  160. package/dist/presets/replicate.js +16 -0
  161. package/dist/presets/slack.d.ts +6 -0
  162. package/dist/presets/slack.d.ts.map +1 -0
  163. package/dist/presets/slack.js +16 -0
  164. package/dist/presets/stripe.d.ts +8 -0
  165. package/dist/presets/stripe.d.ts.map +1 -0
  166. package/dist/presets/stripe.js +23 -0
  167. package/dist/presets/supabase.d.ts +7 -0
  168. package/dist/presets/supabase.d.ts.map +1 -0
  169. package/dist/presets/supabase.js +18 -0
  170. package/dist/presets/together.d.ts +6 -0
  171. package/dist/presets/together.d.ts.map +1 -0
  172. package/dist/presets/together.js +16 -0
  173. package/dist/presets/twilio.d.ts +7 -0
  174. package/dist/presets/twilio.d.ts.map +1 -0
  175. package/dist/presets/twilio.js +17 -0
  176. package/dist/presets/vercel.d.ts +7 -0
  177. package/dist/presets/vercel.d.ts.map +1 -0
  178. package/dist/presets/vercel.js +23 -0
  179. package/dist/presets/xai.d.ts +7 -0
  180. package/dist/presets/xai.d.ts.map +1 -0
  181. package/dist/presets/xai.js +17 -0
  182. package/dist/protocols/ftp.d.ts +63 -0
  183. package/dist/protocols/ftp.d.ts.map +1 -0
  184. package/dist/protocols/ftp.js +388 -0
  185. package/dist/protocols/index.d.ts +4 -0
  186. package/dist/protocols/index.d.ts.map +1 -0
  187. package/dist/protocols/index.js +3 -0
  188. package/dist/protocols/sftp.d.ts +65 -0
  189. package/dist/protocols/sftp.d.ts.map +1 -0
  190. package/dist/protocols/sftp.js +346 -0
  191. package/dist/protocols/telnet.d.ts +50 -0
  192. package/dist/protocols/telnet.d.ts.map +1 -0
  193. package/dist/protocols/telnet.js +139 -0
  194. package/dist/runner/request-runner.d.ts.map +1 -1
  195. package/dist/runner/request-runner.js +1 -0
  196. package/dist/scrape/document.d.ts +44 -0
  197. package/dist/scrape/document.d.ts.map +1 -0
  198. package/dist/scrape/document.js +198 -0
  199. package/dist/scrape/element.d.ts +50 -0
  200. package/dist/scrape/element.d.ts.map +1 -0
  201. package/dist/scrape/element.js +176 -0
  202. package/dist/scrape/extractors.d.ts +17 -0
  203. package/dist/scrape/extractors.d.ts.map +1 -0
  204. package/dist/scrape/extractors.js +356 -0
  205. package/dist/scrape/index.d.ts +5 -0
  206. package/dist/scrape/index.d.ts.map +1 -0
  207. package/dist/scrape/index.js +3 -0
  208. package/dist/scrape/types.d.ts +108 -0
  209. package/dist/scrape/types.d.ts.map +1 -0
  210. package/dist/scrape/types.js +1 -0
  211. package/dist/testing/index.d.ts +3 -0
  212. package/dist/testing/index.d.ts.map +1 -0
  213. package/dist/testing/index.js +1 -0
  214. package/dist/testing/mock.d.ts +58 -0
  215. package/dist/testing/mock.d.ts.map +1 -0
  216. package/dist/testing/mock.js +252 -0
  217. package/dist/transport/fetch.d.ts.map +1 -1
  218. package/dist/transport/fetch.js +12 -4
  219. package/dist/transport/undici.d.ts +17 -1
  220. package/dist/transport/undici.d.ts.map +1 -1
  221. package/dist/transport/undici.js +708 -47
  222. package/dist/types/index.d.ts +111 -10
  223. package/dist/types/index.d.ts.map +1 -1
  224. package/dist/types/index.js +1 -1
  225. package/dist/types/logger.d.ts +17 -0
  226. package/dist/types/logger.d.ts.map +1 -0
  227. package/dist/types/logger.js +66 -0
  228. package/dist/utils/agent-manager.d.ts.map +1 -1
  229. package/dist/utils/agent-manager.js +20 -4
  230. package/dist/utils/body.d.ts.map +1 -1
  231. package/dist/utils/body.js +14 -2
  232. package/dist/utils/charset.d.ts +16 -0
  233. package/dist/utils/charset.d.ts.map +1 -0
  234. package/dist/utils/charset.js +169 -0
  235. package/dist/utils/client-pool.d.ts +21 -0
  236. package/dist/utils/client-pool.d.ts.map +1 -0
  237. package/dist/utils/client-pool.js +49 -0
  238. package/dist/utils/concurrency.d.ts.map +1 -1
  239. package/dist/utils/concurrency.js +8 -4
  240. package/dist/utils/dns-toolkit.d.ts +13 -0
  241. package/dist/utils/dns-toolkit.d.ts.map +1 -0
  242. package/dist/utils/dns-toolkit.js +48 -0
  243. package/dist/utils/doh.d.ts.map +1 -1
  244. package/dist/utils/doh.js +16 -3
  245. package/dist/utils/download.d.ts +15 -0
  246. package/dist/utils/download.d.ts.map +1 -0
  247. package/dist/utils/download.js +44 -0
  248. package/dist/utils/env-proxy.d.ts +13 -0
  249. package/dist/utils/env-proxy.d.ts.map +1 -0
  250. package/dist/utils/env-proxy.js +105 -0
  251. package/dist/utils/header-parser.d.ts +15 -1
  252. package/dist/utils/header-parser.d.ts.map +1 -1
  253. package/dist/utils/header-parser.js +161 -1
  254. package/dist/utils/link-header.d.ts +70 -0
  255. package/dist/utils/link-header.d.ts.map +1 -0
  256. package/dist/utils/link-header.js +190 -0
  257. package/dist/utils/progress.d.ts +7 -2
  258. package/dist/utils/progress.d.ts.map +1 -1
  259. package/dist/utils/progress.js +48 -15
  260. package/dist/utils/rdap.d.ts +17 -0
  261. package/dist/utils/rdap.d.ts.map +1 -0
  262. package/dist/utils/rdap.js +32 -0
  263. package/dist/utils/request-pool.d.ts.map +1 -1
  264. package/dist/utils/request-pool.js +4 -3
  265. package/dist/utils/sse.d.ts.map +1 -1
  266. package/dist/utils/sse.js +8 -2
  267. package/dist/utils/status-codes.d.ts +84 -0
  268. package/dist/utils/status-codes.d.ts.map +1 -0
  269. package/dist/utils/status-codes.js +204 -0
  270. package/dist/utils/streaming.d.ts.map +1 -1
  271. package/dist/utils/streaming.js +1 -0
  272. package/dist/utils/tls-inspector.d.ts +21 -0
  273. package/dist/utils/tls-inspector.d.ts.map +1 -0
  274. package/dist/utils/tls-inspector.js +39 -0
  275. package/dist/utils/try-fn.d.ts.map +1 -1
  276. package/dist/utils/try-fn.js +11 -5
  277. package/dist/utils/upload.d.ts +1 -0
  278. package/dist/utils/upload.d.ts.map +1 -1
  279. package/dist/utils/upload.js +20 -3
  280. package/dist/utils/user-agent.d.ts +9 -9
  281. package/dist/utils/user-agent.js +9 -9
  282. package/dist/utils/whois.d.ts.map +1 -1
  283. package/dist/utils/whois.js +11 -2
  284. package/dist/websocket/client.d.ts +29 -1
  285. package/dist/websocket/client.d.ts.map +1 -1
  286. package/dist/websocket/client.js +145 -13
  287. package/package.json +45 -8
@@ -1,5 +1,8 @@
1
1
  import { WebSocket } from 'undici';
2
2
  import { EventEmitter } from 'events';
3
+ import { pipeline } from 'node:stream/promises';
4
+ import { webToNodeStream } from '../utils/streaming.js';
5
+ import { ReckerError } from '../core/errors.js';
3
6
  export class ReckerWebSocket extends EventEmitter {
4
7
  ws = null;
5
8
  url;
@@ -9,6 +12,9 @@ export class ReckerWebSocket extends EventEmitter {
9
12
  heartbeatTimer;
10
13
  isClosed = false;
11
14
  isReconnecting = false;
15
+ pongWatchdog;
16
+ backoff;
17
+ closedByUser = false;
12
18
  constructor(url, options = {}) {
13
19
  super();
14
20
  this.url = url;
@@ -19,12 +25,38 @@ export class ReckerWebSocket extends EventEmitter {
19
25
  reconnectDelay: options.reconnectDelay ?? 1000,
20
26
  maxReconnectAttempts: options.maxReconnectAttempts ?? 5,
21
27
  heartbeatInterval: options.heartbeatInterval ?? 30000,
28
+ heartbeatTimeout: options.heartbeatTimeout ?? 10000,
29
+ dispatcher: options.dispatcher,
30
+ proxy: options.proxy,
31
+ tls: options.tls,
32
+ perMessageDeflate: options.perMessageDeflate ?? false
33
+ };
34
+ this.backoff = {
35
+ base: this.options.reconnectDelay,
36
+ factor: 2,
37
+ jitter: true,
38
+ max: 30000
22
39
  };
23
40
  }
24
41
  async connect() {
25
42
  return new Promise((resolve, reject) => {
26
43
  try {
27
- this.ws = new WebSocket(this.url, this.options.protocols);
44
+ const wsOptions = {
45
+ headers: this.options.headers,
46
+ dispatcher: this.options.dispatcher,
47
+ perMessageDeflate: this.options.perMessageDeflate,
48
+ };
49
+ if (this.options.proxy) {
50
+ const proxyConfig = typeof this.options.proxy === 'string'
51
+ ? { url: this.options.proxy }
52
+ : this.options.proxy;
53
+ const { ProxyAgent } = require('undici');
54
+ wsOptions.dispatcher = new ProxyAgent(proxyConfig.url);
55
+ }
56
+ if (this.options.tls) {
57
+ wsOptions.tls = this.options.tls;
58
+ }
59
+ this.ws = new WebSocket(this.url, this.options.protocols, wsOptions);
28
60
  this.ws.addEventListener('open', () => {
29
61
  this.reconnectAttempts = 0;
30
62
  this.isReconnecting = false;
@@ -38,17 +70,26 @@ export class ReckerWebSocket extends EventEmitter {
38
70
  isBinary: event.data instanceof Buffer
39
71
  };
40
72
  this.emit('message', message);
73
+ this.stopPongWatchdog();
41
74
  });
42
75
  this.ws.addEventListener('close', (event) => {
43
76
  this.stopHeartbeat();
77
+ this.stopPongWatchdog();
44
78
  this.emit('close', event.code, event.reason);
45
- if (!this.isClosed && this.options.reconnect) {
79
+ if (!this.closedByUser && !this.isClosed && this.options.reconnect) {
46
80
  this.attemptReconnect();
47
81
  }
48
82
  });
49
83
  this.ws.addEventListener('error', (event) => {
50
- this.emit('error', event.error || new Error('WebSocket error'));
51
- reject(event.error || new Error('WebSocket connection failed'));
84
+ const err = event.error instanceof Error
85
+ ? event.error
86
+ : new ReckerError('WebSocket connection error', undefined, undefined, [
87
+ 'Verify the WebSocket endpoint URL and protocol (ws/wss).',
88
+ 'Check proxy/TLS settings if connecting through a proxy.',
89
+ 'Inspect server logs for handshake failures.'
90
+ ]);
91
+ this.emit('error', err);
92
+ reject(err);
52
93
  });
53
94
  }
54
95
  catch (error) {
@@ -56,17 +97,32 @@ export class ReckerWebSocket extends EventEmitter {
56
97
  }
57
98
  });
58
99
  }
59
- send(data) {
100
+ async send(data, options) {
60
101
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
61
- throw new Error('WebSocket is not connected');
102
+ throw new ReckerError('WebSocket is not connected', undefined, undefined, [
103
+ 'Call connect() before sending messages.',
104
+ 'Listen to the open event to ensure the socket is ready.',
105
+ 'Check for prior connection errors or closed states.'
106
+ ]);
62
107
  }
108
+ const awaitDrain = options?.awaitDrain ?? false;
109
+ const highWaterMark = options?.highWaterMark ?? 16 * 1024;
63
110
  this.ws.send(data);
111
+ if (awaitDrain) {
112
+ await this.waitForDrain(highWaterMark);
113
+ }
114
+ }
115
+ async sendStream(stream, options) {
116
+ for await (const chunk of stream) {
117
+ await this.send(chunk, options);
118
+ }
64
119
  }
65
120
  sendJSON(data) {
66
- this.send(JSON.stringify(data));
121
+ void this.send(JSON.stringify(data));
67
122
  }
68
123
  close(code = 1000, reason = '') {
69
124
  this.isClosed = true;
125
+ this.closedByUser = true;
70
126
  this.stopHeartbeat();
71
127
  this.clearReconnectTimer();
72
128
  if (this.ws) {
@@ -75,13 +131,22 @@ export class ReckerWebSocket extends EventEmitter {
75
131
  }
76
132
  }
77
133
  ping() {
78
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
134
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
135
+ return;
136
+ const anyWs = this.ws;
137
+ if (typeof anyWs.ping === 'function') {
79
138
  try {
80
- this.ws.send('__heartbeat__');
139
+ anyWs.ping();
140
+ return;
81
141
  }
82
- catch (e) {
142
+ catch {
83
143
  }
84
144
  }
145
+ try {
146
+ this.ws.send('__heartbeat__');
147
+ }
148
+ catch {
149
+ }
85
150
  }
86
151
  get readyState() {
87
152
  return this.ws?.readyState ?? WebSocket.CLOSED;
@@ -89,6 +154,29 @@ export class ReckerWebSocket extends EventEmitter {
89
154
  get isConnected() {
90
155
  return this.ws?.readyState === WebSocket.OPEN;
91
156
  }
157
+ toReadable() {
158
+ if (!this.ws)
159
+ return null;
160
+ const wsAny = this.ws;
161
+ if (wsAny.readable) {
162
+ return webToNodeStream(wsAny.readable);
163
+ }
164
+ return null;
165
+ }
166
+ async pipeFrom(source, options) {
167
+ await this.sendStream(source, options);
168
+ }
169
+ async pipeTo(destination) {
170
+ const readable = this.toReadable();
171
+ if (!readable) {
172
+ throw new ReckerError('WebSocket has no readable stream', undefined, undefined, [
173
+ 'Ensure the WebSocket is connected before piping.',
174
+ 'Verify the runtime supports readable streams on WebSocket.',
175
+ 'Fallback to event-based message handling if streaming is unavailable.'
176
+ ]);
177
+ }
178
+ await pipeline(readable, destination);
179
+ }
92
180
  async *[Symbol.asyncIterator]() {
93
181
  const queue = [];
94
182
  let resolveNext = null;
@@ -146,13 +234,15 @@ export class ReckerWebSocket extends EventEmitter {
146
234
  }
147
235
  this.isReconnecting = true;
148
236
  this.reconnectAttempts++;
149
- const delay = this.options.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
150
- this.emit('reconnecting', this.reconnectAttempts, delay);
237
+ const baseDelay = this.backoff.base * Math.pow(this.backoff.factor, this.reconnectAttempts - 1);
238
+ const capped = this.backoff.max ? Math.min(baseDelay, this.backoff.max) : baseDelay;
239
+ const jittered = this.backoff.jitter ? randomJitter(capped) : capped;
240
+ this.emit('reconnecting', this.reconnectAttempts, jittered);
151
241
  this.reconnectTimer = setTimeout(() => {
152
242
  this.connect().catch((error) => {
153
243
  this.emit('reconnect-error', error);
154
244
  });
155
- }, delay);
245
+ }, jittered);
156
246
  }
157
247
  clearReconnectTimer() {
158
248
  if (this.reconnectTimer) {
@@ -166,6 +256,7 @@ export class ReckerWebSocket extends EventEmitter {
166
256
  this.heartbeatTimer = setInterval(() => {
167
257
  if (this.isConnected) {
168
258
  this.ping();
259
+ this.startPongWatchdog();
169
260
  }
170
261
  }, this.options.heartbeatInterval);
171
262
  }
@@ -174,6 +265,43 @@ export class ReckerWebSocket extends EventEmitter {
174
265
  clearInterval(this.heartbeatTimer);
175
266
  this.heartbeatTimer = undefined;
176
267
  }
268
+ this.stopPongWatchdog();
269
+ }
270
+ startPongWatchdog() {
271
+ this.stopPongWatchdog();
272
+ if (this.options.heartbeatTimeout <= 0)
273
+ return;
274
+ this.pongWatchdog = setTimeout(() => {
275
+ this.emit('heartbeat-timeout');
276
+ if (!this.closedByUser && this.options.reconnect) {
277
+ this.ws?.close(4000, 'heartbeat timeout');
278
+ }
279
+ }, this.options.heartbeatTimeout);
280
+ }
281
+ stopPongWatchdog() {
282
+ if (this.pongWatchdog) {
283
+ clearTimeout(this.pongWatchdog);
284
+ this.pongWatchdog = undefined;
285
+ }
286
+ }
287
+ getBufferedAmount() {
288
+ return this.ws?.bufferedAmount ?? 0;
289
+ }
290
+ async waitForDrain(highWaterMark) {
291
+ const buffered = this.getBufferedAmount();
292
+ if (buffered <= highWaterMark)
293
+ return;
294
+ await new Promise((resolve) => {
295
+ const check = () => {
296
+ if (this.getBufferedAmount() <= highWaterMark || !this.isConnected) {
297
+ resolve();
298
+ }
299
+ else {
300
+ setTimeout(check, 10);
301
+ }
302
+ };
303
+ setTimeout(check, 10);
304
+ });
177
305
  }
178
306
  }
179
307
  export function websocket(url, options) {
@@ -182,3 +310,7 @@ export function websocket(url, options) {
182
310
  });
183
311
  return ws;
184
312
  }
313
+ function randomJitter(value) {
314
+ const jitter = 0.2 * value;
315
+ return value - jitter + Math.random() * (2 * jitter);
316
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recker",
3
- "version": "1.0.2-0",
3
+ "version": "1.0.3",
4
4
  "description": "AI & DevX focused HTTP client for Node.js 18+",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -26,9 +26,12 @@
26
26
  "whois",
27
27
  "dns",
28
28
  "proxy",
29
- "ai"
29
+ "ai",
30
+ "scraping",
31
+ "cheerio",
32
+ "html-parser"
30
33
  ],
31
- "author": "Forattini Dev",
34
+ "author": "fforattini",
32
35
  "license": "MIT",
33
36
  "homepage": "https://forattini-dev.github.io/recker",
34
37
  "repository": {
@@ -53,10 +56,22 @@
53
56
  "types": "./dist/core/client.d.ts",
54
57
  "import": "./dist/core/client.js"
55
58
  },
59
+ "./testing": {
60
+ "types": "./dist/testing/index.d.ts",
61
+ "import": "./dist/testing/index.js"
62
+ },
63
+ "./presets": {
64
+ "types": "./dist/presets/index.d.ts",
65
+ "import": "./dist/presets/index.js"
66
+ },
56
67
  "./plugins/*": {
57
68
  "types": "./dist/plugins/*.d.ts",
58
69
  "import": "./dist/plugins/*.js"
59
70
  },
71
+ "./scrape": {
72
+ "types": "./dist/scrape/index.d.ts",
73
+ "import": "./dist/scrape/index.js"
74
+ },
60
75
  "./utils/*": {
61
76
  "types": "./dist/utils/*.d.ts",
62
77
  "import": "./dist/utils/*.js"
@@ -70,20 +85,41 @@
70
85
  "access": "public"
71
86
  },
72
87
  "dependencies": {
88
+ "basic-ftp": "^5.0.5",
89
+ "glob": "^13.0.0",
90
+ "ssh2-sftp-client": "^12.0.1",
91
+ "telnet-client": "^2.2.9",
73
92
  "undici": "^7.16.0",
74
93
  "zod": "^4.1.13"
75
94
  },
95
+ "peerDependencies": {
96
+ "cheerio": "^1.0.0",
97
+ "ioredis": "^5.0.0"
98
+ },
99
+ "peerDependenciesMeta": {
100
+ "cheerio": {
101
+ "optional": true
102
+ },
103
+ "ioredis": {
104
+ "optional": true
105
+ }
106
+ },
76
107
  "devDependencies": {
108
+ "@types/needle": "^3.3.0",
77
109
  "@types/node": "^24.10.1",
110
+ "@types/ssh2-sftp-client": "^9.0.5",
111
+ "@types/superagent": "^8.1.9",
78
112
  "@vitest/coverage-v8": "^3.2.4",
79
113
  "axios": "^1.13.2",
80
- "docsify-darklight-theme": "^3.2.0",
81
- "docsify-mermaid": "^2.0.1",
114
+ "cheerio": "^1.0.0",
115
+ "docsify-cli": "^4.4.4",
116
+ "domhandler": "^5.0.3",
82
117
  "got": "^14.6.5",
83
118
  "husky": "^9.1.7",
84
119
  "ky": "^1.14.0",
85
- "mermaid": "^11.12.1",
86
120
  "mitata": "^1.0.34",
121
+ "needle": "^3.3.1",
122
+ "superagent": "^10.2.3",
87
123
  "tsx": "^4.20.6",
88
124
  "typescript": "^5.9.3",
89
125
  "vitest": "^3.2.4"
@@ -94,7 +130,8 @@
94
130
  "test:coverage": "vitest run --coverage",
95
131
  "bench": "tsx benchmark/index.ts",
96
132
  "bench:all": "tsx benchmark/run-all.ts",
97
- "docs:dev": "npx -y docsify-cli serve docs --port 3000",
98
- "docs:preview": "npx -y docsify-cli serve docs --port 3000 --open"
133
+ "docs:dev": "docsify serve docs --port 3000",
134
+ "docs:preview": "npx -y docsify-cli serve docs --port 3000 --open",
135
+ "lint": "echo \"No linting configured for this project.\" && exit 0"
99
136
  }
100
137
  }