@fanboynz/network-scanner 1.0.35

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.
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Browser health monitoring module for nwss.js
3
+ * Provides health checks and recovery mechanisms to prevent protocol timeouts
4
+ */
5
+
6
+ const { formatLogMessage, messageColors } = require('./colorize');
7
+
8
+ /**
9
+ * Checks if browser instance is still responsive
10
+ * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
11
+ * @param {number} timeout - Timeout in milliseconds (default: 5000)
12
+ * @returns {Promise<object>} Health check result
13
+ */
14
+ async function checkBrowserHealth(browserInstance, timeout = 5000) {
15
+ const healthResult = {
16
+ healthy: false,
17
+ pageCount: 0,
18
+ error: null,
19
+ responseTime: 0,
20
+ recommendations: []
21
+ };
22
+
23
+ const startTime = Date.now();
24
+
25
+ try {
26
+ // Test 1: Check if browser is connected
27
+ if (!browserInstance || browserInstance.process() === null) {
28
+ healthResult.error = 'Browser process not running';
29
+ healthResult.recommendations.push('Create new browser instance');
30
+ return healthResult;
31
+ }
32
+
33
+ // Test 2: Try to get pages list with timeout
34
+ const pages = await Promise.race([
35
+ browserInstance.pages(),
36
+ new Promise((_, reject) =>
37
+ setTimeout(() => reject(new Error('Browser unresponsive - pages() timeout')), timeout)
38
+ )
39
+ ]);
40
+
41
+ healthResult.pageCount = pages.length;
42
+ healthResult.responseTime = Date.now() - startTime;
43
+
44
+ // Test 3: Check for excessive pages (memory leak indicator)
45
+ if (pages.length > 20) {
46
+ healthResult.recommendations.push('Too many open pages - consider browser restart');
47
+ }
48
+
49
+ // Test 4: Try to create a test page to verify browser functionality
50
+ let testPage = null;
51
+ try {
52
+ testPage = await Promise.race([
53
+ browserInstance.newPage(),
54
+ new Promise((_, reject) =>
55
+ setTimeout(() => reject(new Error('Page creation timeout')), timeout)
56
+ )
57
+ ]);
58
+
59
+ // Quick test navigation to about:blank
60
+ await Promise.race([
61
+ testPage.goto('about:blank'),
62
+ new Promise((_, reject) =>
63
+ setTimeout(() => reject(new Error('Navigation timeout')), timeout)
64
+ )
65
+ ]);
66
+
67
+ await testPage.close();
68
+
69
+ } catch (pageTestError) {
70
+ if (testPage && !testPage.isClosed()) {
71
+ try { await testPage.close(); } catch (e) { /* ignore */ }
72
+ }
73
+ healthResult.error = `Page creation/navigation failed: ${pageTestError.message}`;
74
+ healthResult.recommendations.push('Browser restart recommended');
75
+ return healthResult;
76
+ }
77
+
78
+ // Test 5: Check response time performance
79
+ if (healthResult.responseTime > 3000) {
80
+ healthResult.recommendations.push('Slow browser response - consider restart');
81
+ }
82
+
83
+ // If all tests pass
84
+ healthResult.healthy = true;
85
+
86
+ } catch (error) {
87
+ healthResult.error = error.message;
88
+ healthResult.responseTime = Date.now() - startTime;
89
+
90
+ // Categorize error types for better recommendations
91
+ if (error.message.includes('timeout') || error.message.includes('unresponsive')) {
92
+ healthResult.recommendations.push('Browser restart required - unresponsive');
93
+ } else if (error.message.includes('Protocol error') || error.message.includes('Target closed')) {
94
+ healthResult.recommendations.push('Browser restart required - protocol error');
95
+ } else {
96
+ healthResult.recommendations.push('Browser restart recommended - unknown error');
97
+ }
98
+ }
99
+
100
+ return healthResult;
101
+ }
102
+
103
+ /**
104
+ * Checks memory usage of browser process (if available)
105
+ * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
106
+ * @returns {Promise<object>} Memory usage information
107
+ */
108
+ async function checkBrowserMemory(browserInstance) {
109
+ const memoryResult = {
110
+ available: false,
111
+ usage: null,
112
+ error: null,
113
+ recommendations: []
114
+ };
115
+
116
+ try {
117
+ const browserProcess = browserInstance.process();
118
+ if (!browserProcess || !browserProcess.pid) {
119
+ memoryResult.error = 'No browser process available';
120
+ return memoryResult;
121
+ }
122
+
123
+ // Try to get process memory info (Linux/Unix)
124
+ try {
125
+ const { execSync } = require('child_process');
126
+ const memInfo = execSync(`ps -p ${browserProcess.pid} -o rss=`, { encoding: 'utf8', timeout: 2000 });
127
+ const memoryKB = parseInt(memInfo.trim());
128
+
129
+ if (!isNaN(memoryKB)) {
130
+ const memoryMB = Math.round(memoryKB / 1024);
131
+ memoryResult.available = true;
132
+ memoryResult.usage = {
133
+ rss: memoryKB,
134
+ rssMB: memoryMB
135
+ };
136
+
137
+ // Memory usage recommendations
138
+ if (memoryMB > 1000) {
139
+ memoryResult.recommendations.push(`High memory usage: ${memoryMB}MB - restart recommended`);
140
+ } else if (memoryMB > 500) {
141
+ memoryResult.recommendations.push(`Elevated memory usage: ${memoryMB}MB - monitor closely`);
142
+ }
143
+ }
144
+ } catch (psError) {
145
+ memoryResult.error = `Memory check failed: ${psError.message}`;
146
+ }
147
+
148
+ } catch (error) {
149
+ memoryResult.error = error.message;
150
+ }
151
+
152
+ return memoryResult;
153
+ }
154
+
155
+ /**
156
+ * Performs comprehensive browser health assessment
157
+ * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
158
+ * @param {object} options - Health check options
159
+ * @returns {Promise<object>} Comprehensive health report
160
+ */
161
+ async function performHealthAssessment(browserInstance, options = {}) {
162
+ const {
163
+ timeout = 5000,
164
+ checkMemory = true,
165
+ forceDebug = false
166
+ } = options;
167
+
168
+ const assessment = {
169
+ overall: 'unknown',
170
+ timestamp: new Date().toISOString(),
171
+ browser: {},
172
+ memory: {},
173
+ recommendations: [],
174
+ needsRestart: false
175
+ };
176
+
177
+ if (forceDebug) {
178
+ console.log(formatLogMessage('debug', 'Starting browser health assessment...'));
179
+ }
180
+
181
+ // Browser responsiveness check
182
+ assessment.browser = await checkBrowserHealth(browserInstance, timeout);
183
+
184
+ // Memory usage check (if enabled and available)
185
+ if (checkMemory) {
186
+ assessment.memory = await checkBrowserMemory(browserInstance);
187
+ }
188
+
189
+ // Combine recommendations
190
+ assessment.recommendations = [
191
+ ...assessment.browser.recommendations,
192
+ ...(assessment.memory.recommendations || [])
193
+ ];
194
+
195
+ // Determine overall health and restart necessity
196
+ if (!assessment.browser.healthy) {
197
+ assessment.overall = 'unhealthy';
198
+ assessment.needsRestart = true;
199
+ } else if (assessment.recommendations.length > 0) {
200
+ assessment.overall = 'degraded';
201
+ assessment.needsRestart = assessment.recommendations.some(rec =>
202
+ rec.includes('restart required') ||
203
+ rec.includes('High memory usage')
204
+ );
205
+ } else {
206
+ assessment.overall = 'healthy';
207
+ assessment.needsRestart = false;
208
+ }
209
+
210
+ if (forceDebug) {
211
+ console.log(formatLogMessage('debug', `Health assessment complete: ${assessment.overall}`));
212
+ if (assessment.recommendations.length > 0) {
213
+ console.log(formatLogMessage('debug', `Recommendations: ${assessment.recommendations.join(', ')}`));
214
+ }
215
+ }
216
+
217
+ return assessment;
218
+ }
219
+
220
+ /**
221
+ * Monitors browser health and suggests actions for nwss.js integration
222
+ * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
223
+ * @param {object} context - Context information for logging
224
+ * @param {object} options - Monitoring options
225
+ * @returns {Promise<object>} Monitoring result with action suggestions
226
+ */
227
+ async function monitorBrowserHealth(browserInstance, context = {}, options = {}) {
228
+ const {
229
+ siteIndex = 0,
230
+ totalSites = 0,
231
+ urlsSinceCleanup = 0,
232
+ cleanupInterval = 40,
233
+ forceDebug = false,
234
+ silentMode = false
235
+ } = options;
236
+
237
+ const result = {
238
+ shouldRestart: false,
239
+ shouldContinue: true,
240
+ reason: null,
241
+ assessment: null
242
+ };
243
+
244
+ try {
245
+ // Perform health assessment
246
+ const assessment = await performHealthAssessment(browserInstance, {
247
+ timeout: 5000,
248
+ checkMemory: true,
249
+ forceDebug
250
+ });
251
+
252
+ result.assessment = assessment;
253
+
254
+ // Decision logic for restart
255
+ if (assessment.needsRestart) {
256
+ result.shouldRestart = true;
257
+ result.reason = `Browser health: ${assessment.overall} - ${assessment.recommendations[0] || 'restart needed'}`;
258
+ } else if (urlsSinceCleanup >= cleanupInterval) {
259
+ result.shouldRestart = true;
260
+ result.reason = `Scheduled cleanup after ${urlsSinceCleanup} URLs`;
261
+ } else if (assessment.browser.responseTime > 4000) {
262
+ result.shouldRestart = true;
263
+ result.reason = `Slow browser response: ${assessment.browser.responseTime}ms`;
264
+ }
265
+
266
+ // Logging
267
+ if (!silentMode && result.shouldRestart) {
268
+ const progress = totalSites > 0 ? ` (${siteIndex + 1}/${totalSites})` : '';
269
+ console.log(`\n${messageColors.fileOp('?? Browser restart needed')} before site${progress}: ${result.reason}`);
270
+ }
271
+
272
+ if (forceDebug && !result.shouldRestart) {
273
+ console.log(formatLogMessage('debug', `Browser health OK - continuing (pages: ${assessment.browser.pageCount}, response: ${assessment.browser.responseTime}ms)`));
274
+ }
275
+
276
+ } catch (monitorError) {
277
+ result.shouldRestart = true;
278
+ result.reason = `Health monitoring failed: ${monitorError.message}`;
279
+
280
+ if (forceDebug) {
281
+ console.log(formatLogMessage('debug', `Browser health monitoring error: ${monitorError.message}`));
282
+ }
283
+ }
284
+
285
+ return result;
286
+ }
287
+
288
+ /**
289
+ * Simple health check function for quick integration
290
+ * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
291
+ * @returns {Promise<boolean>} True if browser is healthy, false otherwise
292
+ */
293
+ async function isBrowserHealthy(browserInstance) {
294
+ try {
295
+ const health = await checkBrowserHealth(browserInstance, 3000);
296
+ return health.healthy;
297
+ } catch (error) {
298
+ return false;
299
+ }
300
+ }
301
+
302
+ module.exports = {
303
+ checkBrowserHealth,
304
+ checkBrowserMemory,
305
+ performHealthAssessment,
306
+ monitorBrowserHealth,
307
+ isBrowserHealthy
308
+ };