bulltrackers-module 1.0.166 → 1.0.168

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.
@@ -12,26 +12,15 @@
12
12
  */
13
13
 
14
14
  const fs = require('fs');
15
- //Hacky solution to force tmp writes
16
- // --- TEMP PATCH START ---
15
+ //Hacky solution to force tmp writes TODO : This Tmp write is really dodgy, not ideal but works, consider less hacky solutions to writing to filesystem
17
16
  process.env.TMPDIR = '/tmp';
18
- process.env.TMP = '/tmp';
19
- process.env.TEMP = '/tmp';
20
-
21
- const os = require('os');
17
+ process.env.TMP = '/tmp';
18
+ process.env.TEMP = '/tmp';
19
+ const os = require('os');
22
20
  os.tmpdir = () => '/tmp';
23
-
24
- try {
25
- const temp = require('temp');
26
- const path = require('path');
27
- const fs = require('fs');
28
- const tmp = '/tmp';
29
- if (!fs.existsSync(tmp)) fs.mkdirSync(tmp);
30
- temp.dir = tmp;
31
- temp.path = () => path.join(tmp, 'temp-' + Math.random().toString(36).slice(2));
32
- } catch {}
33
- // --- TEMP PATCH END ---
34
- const Viz = require('graphviz'); // Uses your local module
21
+ try { const temp = require('temp'); const path = require('path'); const fs = require('fs'); const tmp = '/tmp';
22
+ if (!fs.existsSync(tmp)) fs.mkdirSync(tmp); temp.dir = tmp; temp.path = () => path.join(tmp, 'temp-' + Math.random().toString(36).slice(2)); } catch {}
23
+ const Viz = require('graphviz');
35
24
 
36
25
  /* --------------------------------------------------
37
26
  * Pretty Console Helpers
@@ -82,30 +82,63 @@ class IntelligentProxyManager {
82
82
  }
83
83
 
84
84
  /**
85
- * --- MODIFIED: Makes a fetch request with exponential backoff for rate limits ---
85
+ * --- CORRECTED LOGIC: Makes a fetch request by trying different proxies ---
86
86
  * @param {string} targetUrl - The URL to fetch.
87
87
  * @param {object} options - Fetch options (e.g., headers).
88
88
  * @returns {Promise<object>} A mock Response object.
89
89
  */
90
90
  async fetch(targetUrl, options = {}) {
91
- let proxy = null;
92
- try { proxy = await this._selectProxy(); } catch (error) { return { ok: false, status: 503, error: { message: error.message }, headers: new Headers() }; }
93
- let backoff = this.INITIAL_BACKOFF_MS;
94
91
  let lastResponse = null;
92
+
93
+ // Use MAX_RETRIES to define the number of *different proxies* we will try
94
+ // before giving up on the request.
95
95
  for (let attempt = 1; attempt <= this.MAX_RETRIES; attempt++) {
96
+ let proxy;
97
+ try {
98
+ // 1. Select a new, UNLOCKED proxy *inside* the loop.
99
+ proxy = await this._selectProxy();
100
+ } catch (error) {
101
+ // This fails if all proxies are locked.
102
+ this.logger.log('ERROR', '[ProxyManager] fetch failed: All proxies are locked.', { url: targetUrl });
103
+ return { ok: false, status: 503, error: { message: error.message }, headers: new Headers() };
104
+ }
105
+
106
+ // 2. Make a SINGLE attempt with this selected proxy.
96
107
  const response = await this._fetchViaAppsScript(proxy.url, targetUrl, options);
97
- lastResponse = response;
98
- // 1. Success
99
- if (response.ok) { return response; }
100
- // 2. Rate Limit Error (Retryable)
101
- if (response.isRateLimitError) { this.logger.log('WARN', `[ProxyManager] Rate limit hit on proxy ${proxy.owner} (Attempt ${attempt}/${this.MAX_RETRIES}). Backing off for ${backoff}ms...`, { url: targetUrl }); await sleep(backoff); backoff *= 2; continue; }
102
- // 3. Other Fetch Error (Non-Retryable, Lock Proxy)
103
- if (response.isUrlFetchError) { this.logger.log('ERROR', `[ProxyManager] Proxy ${proxy.owner} failed (non-rate-limit). Locking proxy.`, { url: targetUrl, status: response.status }); await this.lockProxy(proxy.owner); return response; }
104
- // 4. Standard Error (e.g., 404, 500 from *target* URL, not proxy)
105
- return response; }
106
- // If loop finishes, all retries failed (likely all were rate-limit errors)
107
- this.logger.log('ERROR', `[ProxyManager] Request failed after ${this.MAX_RETRIES} rate-limit retries.`, { url: targetUrl });
108
- return lastResponse;
108
+ lastResponse = response; // Save this response in case it's the last one
109
+
110
+ // 3. Case 1: Success! Return immediately.
111
+ if (response.ok) {
112
+ return response;
113
+ }
114
+
115
+ // 4. Case 2: Proxy-level error (Rate limit OR other fetch error)
116
+ // This is a *proxy* failure, not a *target* failure.
117
+ if (response.isUrlFetchError) {
118
+ this.logger.log('WARN', `[ProxyManager] Proxy ${proxy.owner} failed (Attempt ${attempt}/${this.MAX_RETRIES}). Locking it and trying a new proxy.`, {
119
+ reason: response.isRateLimitError ? "Rate Limit" : "Other Fetch Error",
120
+ status: response.status,
121
+ url: targetUrl
122
+ });
123
+
124
+ // LOCK THE FAILED PROXY so _selectProxy() won't pick it again.
125
+ await this.lockProxy(proxy.owner);
126
+
127
+ // Back off slightly before trying the *next* proxy to avoid a thundering herd.
128
+ await sleep(this.INITIAL_BACKOFF_MS * attempt);
129
+
130
+ continue; // Go to the next loop iteration to select a *new* proxy.
131
+ }
132
+
133
+ // 5. Case 3: Standard error from *target* URL (e.g., 404, 500)
134
+ // This means the proxy *worked* but the target URL is bad.
135
+ // This is a "successful" fetch. Do not retry, just return the response.
136
+ return response;
137
+ }
138
+
139
+ // 6. If loop finishes, all (this.MAX_RETRIES) proxy attempts failed.
140
+ this.logger.log('ERROR', `[ProxyManager] Request failed after ${this.MAX_RETRIES} proxy attempts.`, { url: targetUrl, lastStatus: lastResponse?.status });
141
+ return lastResponse; // Return the last failed response
109
142
  }
110
143
 
111
144
 
@@ -25,10 +25,18 @@ exports.fetchAndStoreInsights = async (config, dependencies) => {
25
25
  try {
26
26
  logger.log('INFO', '[FetchInsightsHelpers] Attempting fetch via proxy manager...');
27
27
  response = await proxyManager.fetch(config.etoroInsightsUrl, fetchOptions);
28
+ if (!response.ok) {
29
+ const errorText = await response.text();
30
+ throw new Error(`Proxy request failed with status ${response.status}: ${errorText}`);
31
+ }
28
32
  } catch (proxyError) {
29
33
  logger.log('WARNING', `[FetchInsightsHelpers] Proxy manager fetch failed. Attempting fallback with node-fetch. Error: ${proxyError.message}`);
30
34
  try {
31
35
  response = await fetch(config.etoroInsightsUrl, fetchOptions);
36
+ if (!response.ok) {
37
+ const errorText = await response.text();
38
+ throw new Error(`Fallback node-fetch failed with status ${response.status}: ${errorText}`);
39
+ }
32
40
  } catch (nodeFetchError) {
33
41
  logger.log('ERROR', `[FetchInsightsHelpers] Fallback node-fetch also failed. Error: ${nodeFetchError.message}`);
34
42
  throw nodeFetchError; // Throw the error from the last attempt
@@ -4,6 +4,8 @@
4
4
  * (MODIFIED: `lookupUsernames` runs batches in parallel)
5
5
  * (MODIFIED: `handleUpdate` fetches history and all portfolios in parallel)
6
6
  * (MODIFIED: `handleUpdate` now uses batchManager for history cache)
7
+ * (FIXED: Added try/catch around JSON.parse to log raw HTML error pages)
8
+ * (FIXED: Improved logging for proxy failures)
7
9
  */
8
10
 
9
11
  /**
@@ -149,14 +151,59 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
149
151
  isPrivate = true;
150
152
  logger.log('WARN', `User ${userId} is private. Removing from updates.`);
151
153
  break; // Stop processing more portfolios for this private user
152
- } else {
154
+ }
155
+
156
+ // --- START OF THE FIX ---
157
+ try {
158
+ // Try to parse the body as JSON
159
+ const portfolioJson = JSON.parse(body);
160
+
161
+ // If successful, proceed as normal
153
162
  wasPortfolioSuccess = true;
154
- await batchManager.addToPortfolioBatch(userId, portfolioBlockId, today, JSON.parse(body), userType, requestInfo.instrumentId);
163
+ await batchManager.addToPortfolioBatch(userId, portfolioBlockId, today, portfolioJson, userType, requestInfo.instrumentId);
164
+ logger.log('DEBUG', 'Processing portfolio for user (Success)', { userId, portfolioUrl: requestInfo.url });
165
+ logger.log('DEBUG', 'Response returned (parsed OK)', { body } , 'for user' , { userId });
166
+
167
+ } catch (parseError) {
168
+ // IT FAILED. This means 'body' is NOT JSON. It's the HTML block page.
169
+ wasPortfolioSuccess = false; // Mark as failure
170
+ logger.log('ERROR', `[handleUpdate] FAILED TO PARSE RESPONSE. RAW BODY:`, {
171
+ userId: userId,
172
+ url: requestInfo.url,
173
+ parseErrorMessage: parseError.message,
174
+ rawResponseText: body // <--- THIS WILL LOG THE FULL HTML RESPONSE
175
+ });
155
176
  }
156
- logger.log('DEBUG', 'Processing portfolio for user', { userId, portfolioUrl: requestInfo.url });
157
- logger.log('DEBUG', 'Response returned ', { body } , 'for user' , { userId })
177
+ // --- END OF THE FIX ---
178
+
158
179
  } else {
159
- logger.log('WARN', `Failed to fetch portfolio`, { userId, url: requestInfo.url, error: portfolioRes.reason || `status ${portfolioRes.value?.status}` });
180
+ // --- IMPROVED ERROR LOGGING FOR PROXY FAILURES ---
181
+ let errorLog = {};
182
+ if (portfolioRes.status === 'rejected') {
183
+ // Promise.allSettled rejected (e.g., unhandled exception)
184
+ errorLog = {
185
+ error: "Promise rejected",
186
+ reason: portfolioRes.reason?.message || portfolioRes.reason,
187
+ stack: portfolioRes.reason?.stack
188
+ };
189
+ } else {
190
+ // Proxy returned ok: false
191
+ const responseValue = portfolioRes.value || {};
192
+ // Get the detailed error object from the proxy manager (if it exists)
193
+ const errorDetails = responseValue.error || { message: `status ${responseValue.status}`, details: "No error object provided." };
194
+ errorLog = {
195
+ error: "Proxy fetch failed",
196
+ errorMessage: errorDetails.message,
197
+ errorDetails: errorDetails.details // This will log the raw error from the proxy
198
+ };
199
+ }
200
+
201
+ logger.log('WARN', `Failed to fetch portfolio (Proxy Error or Rejected)`, {
202
+ userId,
203
+ url: requestInfo.url,
204
+ ...errorLog
205
+ });
206
+ // --- END IMPROVED LOGGING ---
160
207
  }
161
208
  // Update performance for this specific header
162
209
  headerManager.updatePerformance(requestInfo.header.id, wasPortfolioSuccess);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.166",
3
+ "version": "1.0.168",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [