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.
- package/functions/computation-system/helpers/computation_manifest_builder.js +7 -18
- package/functions/core/utils/intelligent_proxy_manager.js +49 -16
- package/functions/fetch-insights/helpers/handler_helpers.js +8 -0
- package/functions/task-engine/helpers/update_helpers.js +52 -5
- package/package.json +1 -1
|
@@ -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
|
|
19
|
-
process.env.TEMP
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
* ---
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// 4.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
}
|
|
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,
|
|
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
|
-
|
|
157
|
-
|
|
177
|
+
// --- END OF THE FIX ---
|
|
178
|
+
|
|
158
179
|
} else {
|
|
159
|
-
|
|
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);
|