@dynamicu/chromedebug-mcp 2.5.15 → 2.6.2
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/chrome-extension/disable-react-devtools.js +19 -0
- package/chrome-extension/manifest.free.json +3 -3
- package/chrome-extension/options.html +2 -1
- package/chrome-extension/popup.html +15 -1
- package/chrome-extension/popup.js +191 -35
- package/package.json +3 -1
- package/scripts/package-webstore-extension.js +1 -0
- package/scripts/webpack.config.free.cjs +1 -0
- package/scripts/webpack.config.pro.cjs +3 -2
- package/src/cli.js +4 -0
- package/src/http-server.js +144 -140
- package/src/index.js +28 -2
- package/src/logger.js +109 -11
- package/src/standalone-server.js +4 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disable React DevTools to prevent semver validation error
|
|
3
|
+
*
|
|
4
|
+
* This prevents the React DevTools browser extension from trying to inject
|
|
5
|
+
* into ChromeDebug's extension pages, which causes a console error:
|
|
6
|
+
* "Invalid argument not valid semver ('' received)"
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: This only affects ChromeDebug extension pages (popup, options).
|
|
9
|
+
* It does NOT affect developers' React applications in other tabs.
|
|
10
|
+
* Each browser tab/window has its own isolated JavaScript context.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
|
14
|
+
isDisabled: true,
|
|
15
|
+
supportsFiber: true,
|
|
16
|
+
inject: function() {},
|
|
17
|
+
onCommitFiberRoot: function() {},
|
|
18
|
+
onCommitFiberUnmount: function() {}
|
|
19
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
|
-
"name": "ChromeDebug MCP Assistant FREE v2.
|
|
4
|
-
"version": "2.
|
|
5
|
-
"description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-10-
|
|
3
|
+
"name": "ChromeDebug MCP Assistant FREE v2.6.0",
|
|
4
|
+
"version": "2.6.0",
|
|
5
|
+
"description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-10-24-v2.6.0]",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"activeTab",
|
|
8
8
|
"scripting",
|
|
@@ -164,6 +164,19 @@
|
|
|
164
164
|
</div>
|
|
165
165
|
|
|
166
166
|
<div id="license-message" style="margin-top: 5px; font-size: 12px; text-align: center;"></div>
|
|
167
|
+
|
|
168
|
+
<!-- Inline Activation Manager (shown when activation limit reached) -->
|
|
169
|
+
<div id="inline-activation-manager" style="display: none; margin-top: 15px; padding: 12px; background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out;">
|
|
170
|
+
<div style="font-size: 13px; font-weight: 500; color: #856404; margin-bottom: 10px;">
|
|
171
|
+
⚠️ Activation Limit Reached
|
|
172
|
+
</div>
|
|
173
|
+
<div style="font-size: 12px; color: #856404; margin-bottom: 12px;">
|
|
174
|
+
Please deactivate an existing activation to continue
|
|
175
|
+
</div>
|
|
176
|
+
<div id="activations-list-inline" style="max-height: 250px; overflow-y: auto; background: white; border-radius: 4px; border: 1px solid #ddd;">
|
|
177
|
+
<!-- Activations will be loaded here dynamically -->
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
167
180
|
</div>
|
|
168
181
|
|
|
169
182
|
<div class="status">
|
|
@@ -526,7 +539,8 @@
|
|
|
526
539
|
color: #c62828;
|
|
527
540
|
}
|
|
528
541
|
</style>
|
|
529
|
-
|
|
542
|
+
|
|
543
|
+
<script src="disable-react-devtools.js"></script>
|
|
530
544
|
<script src="extension-config.js"></script>
|
|
531
545
|
<script type="module" src="popup.js"></script>
|
|
532
546
|
</body>
|
|
@@ -995,6 +995,193 @@ async function initializeLicenseUI() {
|
|
|
995
995
|
}
|
|
996
996
|
}
|
|
997
997
|
|
|
998
|
+
// Inline Activation Manager Functions
|
|
999
|
+
/**
|
|
1000
|
+
* Show the inline activation manager with slide-down animation
|
|
1001
|
+
*/
|
|
1002
|
+
async function showInlineActivationManager(licenseKey) {
|
|
1003
|
+
const inlineManager = document.getElementById('inline-activation-manager');
|
|
1004
|
+
const activationsList = document.getElementById('activations-list-inline');
|
|
1005
|
+
|
|
1006
|
+
// Show the container
|
|
1007
|
+
inlineManager.style.display = 'block';
|
|
1008
|
+
|
|
1009
|
+
// Trigger animation
|
|
1010
|
+
setTimeout(() => {
|
|
1011
|
+
inlineManager.style.maxHeight = '400px';
|
|
1012
|
+
}, 10);
|
|
1013
|
+
|
|
1014
|
+
// Load activations
|
|
1015
|
+
try {
|
|
1016
|
+
activationsList.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">Loading activations...</div>';
|
|
1017
|
+
|
|
1018
|
+
// Get current instance ID
|
|
1019
|
+
const stored = await chrome.storage.local.get(['chromedebug_instance_id']);
|
|
1020
|
+
const currentInstanceId = stored.chromedebug_instance_id;
|
|
1021
|
+
|
|
1022
|
+
// Fetch activations from Firebase
|
|
1023
|
+
const data = await licenseClient.listActivations(licenseKey);
|
|
1024
|
+
const activations = data.activations || [];
|
|
1025
|
+
|
|
1026
|
+
// Render activations
|
|
1027
|
+
renderInlineActivations(activations, currentInstanceId, licenseKey);
|
|
1028
|
+
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
console.error('[License] Error loading activations:', error);
|
|
1031
|
+
activationsList.innerHTML = `<div style="padding: 15px; text-align: center; color: #f44336;">Failed to load activations: ${error.message}</div>`;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Hide the inline activation manager with slide-up animation
|
|
1037
|
+
*/
|
|
1038
|
+
function hideInlineActivationManager() {
|
|
1039
|
+
const inlineManager = document.getElementById('inline-activation-manager');
|
|
1040
|
+
inlineManager.style.maxHeight = '0';
|
|
1041
|
+
|
|
1042
|
+
setTimeout(() => {
|
|
1043
|
+
inlineManager.style.display = 'none';
|
|
1044
|
+
}, 300);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Render activations in the inline manager
|
|
1049
|
+
*/
|
|
1050
|
+
function renderInlineActivations(activations, currentInstanceId, licenseKey) {
|
|
1051
|
+
const container = document.getElementById('activations-list-inline');
|
|
1052
|
+
container.innerHTML = '';
|
|
1053
|
+
|
|
1054
|
+
if (activations.length === 0) {
|
|
1055
|
+
container.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">No activations found.</div>';
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
activations.forEach((activation, index) => {
|
|
1060
|
+
const item = document.createElement('div');
|
|
1061
|
+
item.style.cssText = 'padding: 12px; border-bottom: 1px solid #e0e0e0; transition: background 0.2s;';
|
|
1062
|
+
item.onmouseenter = () => item.style.background = '#f9f9f9';
|
|
1063
|
+
item.onmouseleave = () => item.style.background = isCurrentDevice ? '#e8f5e9' : 'white';
|
|
1064
|
+
|
|
1065
|
+
// Check if this is the current device
|
|
1066
|
+
const isCurrentDevice = activation.name === currentInstanceId || activation.identifier === currentInstanceId;
|
|
1067
|
+
|
|
1068
|
+
if (isCurrentDevice) {
|
|
1069
|
+
item.style.background = '#e8f5e9';
|
|
1070
|
+
item.style.borderLeft = '3px solid #4caf50';
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Format date
|
|
1074
|
+
const date = new Date(activation.createdAt);
|
|
1075
|
+
const formattedDate = date.toLocaleString('en-US', {
|
|
1076
|
+
month: 'short',
|
|
1077
|
+
day: 'numeric',
|
|
1078
|
+
year: 'numeric',
|
|
1079
|
+
hour: '2-digit',
|
|
1080
|
+
minute: '2-digit'
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
// Build device info
|
|
1084
|
+
let deviceDisplay = 'Unknown Device';
|
|
1085
|
+
if (activation.deviceInfo) {
|
|
1086
|
+
deviceDisplay = activation.deviceInfo.deviceName ||
|
|
1087
|
+
`${activation.deviceInfo.platform || 'Unknown'} • ${activation.deviceInfo.browser || 'Unknown'}`;
|
|
1088
|
+
} else if (activation.name && activation.name !== currentInstanceId) {
|
|
1089
|
+
deviceDisplay = activation.name;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
item.innerHTML = `
|
|
1093
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1094
|
+
<span style="font-weight: 500; font-size: 13px; color: #333;">Activation ${index + 1}</span>
|
|
1095
|
+
${isCurrentDevice ? '<span style="background: #4caf50; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; font-weight: bold;">CURRENT</span>' : ''}
|
|
1096
|
+
</div>
|
|
1097
|
+
<div style="font-size: 12px; color: #666; margin-bottom: 4px;">
|
|
1098
|
+
<strong style="color: #333;">${deviceDisplay}</strong>
|
|
1099
|
+
</div>
|
|
1100
|
+
<div style="font-size: 11px; color: #999; margin-bottom: 8px;">
|
|
1101
|
+
${formattedDate}
|
|
1102
|
+
</div>
|
|
1103
|
+
<button
|
|
1104
|
+
class="deactivate-btn-inline"
|
|
1105
|
+
data-instance-id="${activation.identifier}"
|
|
1106
|
+
data-activation-number="${index + 1}"
|
|
1107
|
+
data-license-key="${licenseKey}"
|
|
1108
|
+
style="padding: 5px 10px; background: ${isCurrentDevice ? '#ccc' : '#f44336'}; color: white; border: none; border-radius: 3px; cursor: ${isCurrentDevice ? 'not-allowed' : 'pointer'}; font-size: 11px; width: 100%;"
|
|
1109
|
+
${isCurrentDevice ? 'disabled' : ''}
|
|
1110
|
+
>
|
|
1111
|
+
${isCurrentDevice ? '✓ Current Device' : 'Deactivate'}
|
|
1112
|
+
</button>
|
|
1113
|
+
`;
|
|
1114
|
+
|
|
1115
|
+
// Add click handler for deactivate button
|
|
1116
|
+
if (!isCurrentDevice) {
|
|
1117
|
+
const deactivateBtn = item.querySelector('.deactivate-btn-inline');
|
|
1118
|
+
deactivateBtn.addEventListener('click', () => {
|
|
1119
|
+
handleInlineDeactivate(activation.identifier, index + 1, licenseKey);
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
container.appendChild(item);
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Handle deactivation of an instance (inline)
|
|
1129
|
+
*/
|
|
1130
|
+
async function handleInlineDeactivate(instanceId, activationNumber, licenseKey) {
|
|
1131
|
+
const confirmed = confirm(
|
|
1132
|
+
`Are you sure you want to deactivate Activation ${activationNumber}?\n\n` +
|
|
1133
|
+
`This will free up an activation slot and automatically activate on this device.`
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
if (!confirmed) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
const messageDiv = document.getElementById('license-message');
|
|
1141
|
+
|
|
1142
|
+
try {
|
|
1143
|
+
// Disable all deactivate buttons
|
|
1144
|
+
const buttons = document.querySelectorAll('.deactivate-btn-inline');
|
|
1145
|
+
buttons.forEach(btn => btn.disabled = true);
|
|
1146
|
+
|
|
1147
|
+
messageDiv.textContent = 'Deactivating...';
|
|
1148
|
+
messageDiv.style.color = '#2196F3';
|
|
1149
|
+
|
|
1150
|
+
// Deactivate the instance
|
|
1151
|
+
const result = await licenseClient.deactivateInstance(licenseKey, instanceId);
|
|
1152
|
+
console.log('[License] Deactivation result:', result);
|
|
1153
|
+
|
|
1154
|
+
if (result.deactivated) {
|
|
1155
|
+
messageDiv.textContent = 'Deactivated! Activating on this device...';
|
|
1156
|
+
messageDiv.style.color = '#4caf50';
|
|
1157
|
+
|
|
1158
|
+
// Hide inline activation manager
|
|
1159
|
+
hideInlineActivationManager();
|
|
1160
|
+
|
|
1161
|
+
// Wait a moment, then retry activation
|
|
1162
|
+
setTimeout(async () => {
|
|
1163
|
+
// Set the license key in the input
|
|
1164
|
+
document.getElementById('license-key-input').value = licenseKey;
|
|
1165
|
+
|
|
1166
|
+
// Retry activation automatically
|
|
1167
|
+
await handleLicenseActivation();
|
|
1168
|
+
}, 1000);
|
|
1169
|
+
|
|
1170
|
+
} else {
|
|
1171
|
+
throw new Error(result.error || 'Deactivation failed');
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
console.error('[License] Deactivation error:', error);
|
|
1176
|
+
messageDiv.textContent = `Deactivation failed: ${error.message}`;
|
|
1177
|
+
messageDiv.style.color = '#f44336';
|
|
1178
|
+
|
|
1179
|
+
// Re-enable buttons
|
|
1180
|
+
const buttons = document.querySelectorAll('.deactivate-btn-inline:not([disabled])');
|
|
1181
|
+
buttons.forEach(btn => btn.disabled = false);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
998
1185
|
// License activation handler
|
|
999
1186
|
async function handleLicenseActivation() {
|
|
1000
1187
|
const licenseKey = document.getElementById('license-key-input').value.trim();
|
|
@@ -1033,43 +1220,12 @@ async function handleLicenseActivation() {
|
|
|
1033
1220
|
|
|
1034
1221
|
if (result.error === 'ACTIVATION_LIMIT_REACHED' ||
|
|
1035
1222
|
(result.error && result.error.toLowerCase().includes('activation limit'))) {
|
|
1036
|
-
console.log('[License] Activation limit reached,
|
|
1037
|
-
messageDiv.textContent = '
|
|
1223
|
+
console.log('[License] Activation limit reached, showing inline activation manager');
|
|
1224
|
+
messageDiv.textContent = 'Please deactivate an existing activation to continue';
|
|
1038
1225
|
messageDiv.style.color = '#ff9800';
|
|
1039
1226
|
|
|
1040
|
-
//
|
|
1041
|
-
await
|
|
1042
|
-
'ls_license_key': licenseKey.trim(),
|
|
1043
|
-
'activation_manager_open': true
|
|
1044
|
-
});
|
|
1045
|
-
|
|
1046
|
-
// Open activation manager in a new window
|
|
1047
|
-
const width = 500;
|
|
1048
|
-
const height = 600;
|
|
1049
|
-
const left = Math.round((screen.width - width) / 2);
|
|
1050
|
-
const top = Math.round((screen.height - height) / 2);
|
|
1051
|
-
|
|
1052
|
-
const activationManagerUrl = chrome.runtime.getURL('activation-manager.html');
|
|
1053
|
-
console.log('[License] Opening activation manager at URL:', activationManagerUrl);
|
|
1054
|
-
|
|
1055
|
-
chrome.windows.create({
|
|
1056
|
-
url: activationManagerUrl,
|
|
1057
|
-
type: 'popup',
|
|
1058
|
-
width: width,
|
|
1059
|
-
height: height,
|
|
1060
|
-
left: left,
|
|
1061
|
-
top: top
|
|
1062
|
-
}, (window) => {
|
|
1063
|
-
if (chrome.runtime.lastError) {
|
|
1064
|
-
console.error('[License] Failed to open activation manager:', chrome.runtime.lastError);
|
|
1065
|
-
messageDiv.textContent = 'Error: Could not open activation manager. See console.';
|
|
1066
|
-
messageDiv.style.color = '#f44336';
|
|
1067
|
-
} else {
|
|
1068
|
-
console.log('[License] Activation manager window created:', window);
|
|
1069
|
-
messageDiv.textContent = 'Please deactivate an existing activation to continue';
|
|
1070
|
-
messageDiv.style.color = '#ff9800';
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1227
|
+
// Show inline activation manager
|
|
1228
|
+
await showInlineActivationManager(licenseKey.trim());
|
|
1073
1229
|
} else {
|
|
1074
1230
|
messageDiv.textContent = result.error || 'Invalid license key';
|
|
1075
1231
|
messageDiv.style.color = '#f44336';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamicu/chromedebug-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "ChromeDebug MCP - MCP server that provides full control over a Chrome browser instance for debugging and automation with AI assistants like Claude Code",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -118,6 +118,8 @@
|
|
|
118
118
|
"joi": "^18.0.0",
|
|
119
119
|
"jsonwebtoken": "^9.0.2",
|
|
120
120
|
"multer": "^2.0.2",
|
|
121
|
+
"pino": "^10.1.0",
|
|
122
|
+
"pino-pretty": "^13.1.2",
|
|
121
123
|
"puppeteer": "^24.25.0",
|
|
122
124
|
"uuid": "^11.1.0",
|
|
123
125
|
"web-vitals": "^5.1.0",
|
|
@@ -50,6 +50,7 @@ module.exports = {
|
|
|
50
50
|
{ from: 'chrome-extension/web-vitals.iife.js', to: 'web-vitals.iife.js' },
|
|
51
51
|
|
|
52
52
|
// Copy supporting modules (these are imported by bundled scripts)
|
|
53
|
+
{ from: 'chrome-extension/disable-react-devtools.js', to: 'disable-react-devtools.js' },
|
|
53
54
|
{ from: 'chrome-extension/pii-redactor.js', to: 'pii-redactor.js' },
|
|
54
55
|
{ from: 'chrome-extension/data-buffer.js', to: 'data-buffer.js' },
|
|
55
56
|
{ from: 'chrome-extension/performance-monitor.js', to: 'performance-monitor.js' },
|
|
@@ -52,6 +52,7 @@ module.exports = {
|
|
|
52
52
|
{ from: 'chrome-extension/web-vitals.iife.js', to: 'web-vitals.iife.js' },
|
|
53
53
|
|
|
54
54
|
// Copy supporting modules (these are imported by bundled scripts)
|
|
55
|
+
{ from: 'chrome-extension/disable-react-devtools.js', to: 'disable-react-devtools.js' },
|
|
55
56
|
{ from: 'chrome-extension/pii-redactor.js', to: 'pii-redactor.js' },
|
|
56
57
|
{ from: 'chrome-extension/data-buffer.js', to: 'data-buffer.js' },
|
|
57
58
|
{ from: 'chrome-extension/performance-monitor.js', to: 'performance-monitor.js' },
|
|
@@ -62,8 +63,8 @@ module.exports = {
|
|
|
62
63
|
{ from: 'chrome-extension/logger.js', to: 'logger.js' },
|
|
63
64
|
{ from: 'chrome-extension/license-helper.js', to: 'license-helper.js' },
|
|
64
65
|
{ from: 'chrome-extension/firebase-client.js', to: 'firebase-client.js' },
|
|
65
|
-
{ from: 'chrome-extension/firebase-config.js', to: 'firebase-config.js' },
|
|
66
|
-
{ from: 'chrome-extension/firebase-config.
|
|
66
|
+
{ from: 'chrome-extension/firebase-config.public.js', to: 'firebase-config.public.js' },
|
|
67
|
+
{ from: 'chrome-extension/firebase-config.public-sw.js', to: 'firebase-config.js' },
|
|
67
68
|
{ from: 'chrome-extension/activation-manager.js', to: 'activation-manager.js' },
|
|
68
69
|
{ from: 'chrome-extension/frame-capture.js', to: 'frame-capture.js' },
|
|
69
70
|
{ from: 'chrome-extension/chrome-session-manager.js', to: 'chrome-session-manager.js' },
|
package/src/cli.js
CHANGED
|
@@ -18,8 +18,12 @@ import {
|
|
|
18
18
|
findActiveSessions
|
|
19
19
|
} from './services/unified-session-manager.js';
|
|
20
20
|
import logger from './utils/logger.js';
|
|
21
|
+
import { interceptStdout } from './logger.js';
|
|
21
22
|
import { installPro } from './commands/install-pro.js';
|
|
22
23
|
|
|
24
|
+
// CRITICAL: Intercept stdout FIRST to prevent MCP JSON-RPC pollution
|
|
25
|
+
interceptStdout();
|
|
26
|
+
|
|
23
27
|
/**
|
|
24
28
|
* Main CLI entry point
|
|
25
29
|
*/
|
package/src/http-server.js
CHANGED
|
@@ -8,6 +8,10 @@ import { findAvailablePort } from './utils.js';
|
|
|
8
8
|
import { writePortFile, removePortFile } from './port-discovery.js';
|
|
9
9
|
import { configLoader } from './config-loader.js';
|
|
10
10
|
import { setupProcessTracking } from './services/process-tracker.js';
|
|
11
|
+
import { createLogger, logHttpRequest } from './logger.js';
|
|
12
|
+
|
|
13
|
+
// Create component-specific logger
|
|
14
|
+
const logger = createLogger('HTTP-Server');
|
|
11
15
|
|
|
12
16
|
// Security imports
|
|
13
17
|
import { authenticate, authorize, PERMISSIONS } from './middleware/auth.js';
|
|
@@ -83,10 +87,10 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
83
87
|
const contentType = req.get('content-type') || 'none';
|
|
84
88
|
const contentLength = req.get('content-length') || '0';
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
logger.debug(` ${timestamp} - ${req.method} ${req.originalUrl}`);
|
|
91
|
+
logger.debug(` IP: ${ip} | UA: ${userAgent}`);
|
|
92
|
+
logger.debug(` Content-Type: ${contentType} | Length: ${contentLength}bytes`);
|
|
93
|
+
logger.debug(` Headers:`, Object.keys(req.headers).map(h => `${h}=${req.headers[h]}`).join(', '));
|
|
90
94
|
|
|
91
95
|
// Capture body parsing errors
|
|
92
96
|
const originalJson = express.json();
|
|
@@ -96,8 +100,8 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
96
100
|
const originalStatus = res.status;
|
|
97
101
|
res.status = function(code) {
|
|
98
102
|
if (code === 400) {
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
logger.debug(` HTTP 400 ERROR for ${req.method} ${req.originalUrl}`);
|
|
104
|
+
logger.debug(` This request will be rejected with 400`);
|
|
101
105
|
}
|
|
102
106
|
return originalStatus.call(this, code);
|
|
103
107
|
};
|
|
@@ -108,10 +112,10 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
108
112
|
// ENHANCED BODY PARSING - For debugging body parsing issues (only for screen-interactions)
|
|
109
113
|
app.use('/chromedebug/screen-interactions/*', (req, res, next) => {
|
|
110
114
|
if (req.method === 'POST') {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
logger.debug(` Processing screen-interactions request for ${req.originalUrl}`);
|
|
116
|
+
logger.debug(` Content-Type: ${req.get('content-type')}`);
|
|
117
|
+
logger.debug(` Content-Length: ${req.get('content-length')}`);
|
|
118
|
+
logger.debug(` User-Agent: ${req.get('user-agent')}`);
|
|
115
119
|
|
|
116
120
|
// Store original data handler to capture raw body
|
|
117
121
|
const originalData = req.on;
|
|
@@ -130,16 +134,16 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
130
134
|
const wrappedHandler = () => {
|
|
131
135
|
if (chunks.length > 0) {
|
|
132
136
|
const rawBody = Buffer.concat(chunks).toString();
|
|
133
|
-
|
|
137
|
+
logger.debug(` Raw body captured (${rawBody.length} chars):`, rawBody);
|
|
134
138
|
req.rawBody = rawBody;
|
|
135
139
|
|
|
136
140
|
// Try to parse manually
|
|
137
141
|
try {
|
|
138
142
|
const parsedBody = JSON.parse(rawBody);
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
logger.debug(` Manual JSON parse successful`);
|
|
144
|
+
logger.debug(` Parsed data:`, JSON.stringify(parsedBody, null, 2));
|
|
141
145
|
} catch (parseError) {
|
|
142
|
-
|
|
146
|
+
logger.debug(` Manual JSON parse FAILED:`, parseError.message);
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
return originalHandler();
|
|
@@ -155,9 +159,9 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
155
159
|
// JSON PARSING ERROR HANDLER - Catch malformed JSON before it reaches our handlers
|
|
156
160
|
app.use('/chromedebug/screen-interactions/*', (err, req, res, next) => {
|
|
157
161
|
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
logger.error(` Malformed JSON in screen-interactions request:`, err.message);
|
|
163
|
+
logger.error(` Raw body:`, req.rawBody || 'not captured');
|
|
164
|
+
logger.error(` Request body:`, req.body);
|
|
161
165
|
return res.status(400).json({
|
|
162
166
|
error: 'Invalid JSON format',
|
|
163
167
|
details: err.message
|
|
@@ -169,13 +173,13 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
169
173
|
// ENHANCED VALIDATION ERROR HANDLER - Catch and log detailed validation errors
|
|
170
174
|
app.use('/chromedebug/screen-interactions/*', (err, req, res, next) => {
|
|
171
175
|
if (err && err.status === 400 && err.details) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
logger.warn(` Screen interactions validation failed:`);
|
|
177
|
+
logger.warn(` Request path:`, req.originalUrl);
|
|
178
|
+
logger.warn(` User agent:`, req.get('user-agent'));
|
|
179
|
+
logger.warn(` Content type:`, req.get('content-type'));
|
|
180
|
+
logger.warn(` Raw body:`, req.rawBody || 'not captured');
|
|
181
|
+
logger.warn(` Parsed body:`, JSON.stringify(req.body, null, 2));
|
|
182
|
+
logger.warn(` Validation errors:`, JSON.stringify(err.details, null, 2));
|
|
179
183
|
}
|
|
180
184
|
next(err);
|
|
181
185
|
});
|
|
@@ -208,7 +212,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
208
212
|
// JSON error handling middleware
|
|
209
213
|
app.use((err, req, res, next) => {
|
|
210
214
|
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
|
|
211
|
-
|
|
215
|
+
logger.error('[JSON Parse Error]', {
|
|
212
216
|
url: req.url,
|
|
213
217
|
method: req.method,
|
|
214
218
|
contentType: req.get('content-type'),
|
|
@@ -288,8 +292,8 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
288
292
|
createValidator(workflowRecordingSchema),
|
|
289
293
|
async (req, res) => {
|
|
290
294
|
const { sessionId, url, title, includeLogs, actions, logs, functionTraces, name, screenshotSettings } = req.body;
|
|
291
|
-
|
|
292
|
-
|
|
295
|
+
logger.info('[HTTP Server] Received workflow recording with name:', name);
|
|
296
|
+
logger.info('[HTTP Server] Function traces count:', functionTraces ? functionTraces.length : 0);
|
|
293
297
|
if (!sessionId || !actions) {
|
|
294
298
|
return res.status(400).json({ error: 'sessionId and actions are required' });
|
|
295
299
|
}
|
|
@@ -298,7 +302,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
298
302
|
const result = await activeController.storeWorkflowRecording(sessionId, url, title, includeLogs, actions, logs, name, screenshotSettings, functionTraces);
|
|
299
303
|
res.json(result);
|
|
300
304
|
} catch (error) {
|
|
301
|
-
|
|
305
|
+
logger.error('Error storing workflow recording:', error);
|
|
302
306
|
res.status(500).json({ error: 'Failed to store workflow recording', details: error.message });
|
|
303
307
|
}
|
|
304
308
|
});
|
|
@@ -316,7 +320,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
316
320
|
}
|
|
317
321
|
res.json(result);
|
|
318
322
|
} catch (error) {
|
|
319
|
-
|
|
323
|
+
logger.error('Error retrieving workflow recording:', error);
|
|
320
324
|
res.status(500).json({ error: 'Failed to retrieve workflow recording', details: error.message });
|
|
321
325
|
}
|
|
322
326
|
});
|
|
@@ -329,7 +333,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
329
333
|
const result = await activeController.listWorkflowRecordings();
|
|
330
334
|
res.json(result);
|
|
331
335
|
} catch (error) {
|
|
332
|
-
|
|
336
|
+
logger.error('Error listing workflow recordings:', error);
|
|
333
337
|
res.status(500).json({ error: 'Failed to list workflow recordings', details: error.message });
|
|
334
338
|
}
|
|
335
339
|
});
|
|
@@ -360,18 +364,18 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
360
364
|
return res.status(400).json({ error: 'No data provided' });
|
|
361
365
|
}
|
|
362
366
|
|
|
363
|
-
|
|
367
|
+
logger.debug(` Received batch ${batchId} for recording ${recordingId} with ${events.length} events`);
|
|
364
368
|
|
|
365
369
|
// Ensure recording exists (auto-create if needed)
|
|
366
370
|
try {
|
|
367
371
|
const existingRecording = activeController.database.getRecording(recordingId);
|
|
368
372
|
if (!existingRecording) {
|
|
369
|
-
|
|
373
|
+
logger.debug(` Auto-creating recording ${recordingId} for full data upload`);
|
|
370
374
|
activeController.database.storeRecording(recordingId, 'full_data_recording');
|
|
371
375
|
}
|
|
372
376
|
} catch (error) {
|
|
373
377
|
// Recording doesn't exist, create it
|
|
374
|
-
|
|
378
|
+
logger.debug(` Auto-creating recording ${recordingId} for full data upload (error case)`);
|
|
375
379
|
activeController.database.storeRecording(recordingId, 'full_data_recording');
|
|
376
380
|
}
|
|
377
381
|
|
|
@@ -394,7 +398,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
394
398
|
await activeController.database.storePerformanceMetric(recordingId, event);
|
|
395
399
|
break;
|
|
396
400
|
default:
|
|
397
|
-
|
|
401
|
+
logger.debug(` Unknown event type: ${event.type}`);
|
|
398
402
|
}
|
|
399
403
|
}
|
|
400
404
|
|
|
@@ -404,7 +408,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
404
408
|
eventsProcessed: events.length
|
|
405
409
|
});
|
|
406
410
|
} catch (error) {
|
|
407
|
-
|
|
411
|
+
logger.debug(' Error processing batch:', error);
|
|
408
412
|
res.status(500).json({
|
|
409
413
|
error: 'Failed to process batch',
|
|
410
414
|
details: error.message
|
|
@@ -434,7 +438,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
434
438
|
data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
|
435
439
|
}
|
|
436
440
|
|
|
437
|
-
|
|
441
|
+
logger.debug(` Received ${dataType} data for recording ${recordingId}`);
|
|
438
442
|
|
|
439
443
|
// Store data based on type
|
|
440
444
|
const storeMethod = `store${dataType.charAt(0).toUpperCase() + dataType.slice(1).replace(/-/g, '')}`;
|
|
@@ -445,7 +449,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
445
449
|
res.status(400).json({ error: `Unknown data type: ${dataType}` });
|
|
446
450
|
}
|
|
447
451
|
} catch (error) {
|
|
448
|
-
|
|
452
|
+
logger.debug(` Error processing ${req.params.dataType}:`, error);
|
|
449
453
|
res.status(500).json({
|
|
450
454
|
error: 'Failed to process data',
|
|
451
455
|
details: error.message
|
|
@@ -461,23 +465,23 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
461
465
|
authorize(PERMISSIONS.FRAME_WRITE),
|
|
462
466
|
upload.none(),
|
|
463
467
|
async (req, res) => {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
468
|
+
logger.debug(' ==========================================');
|
|
469
|
+
logger.debug(' Frame batch request received');
|
|
470
|
+
logger.debug(' Content-Type:', req.headers['content-type']);
|
|
471
|
+
logger.debug(' Request method:', req.method);
|
|
472
|
+
logger.debug(' User:', req.user?.name || 'unknown');
|
|
473
|
+
logger.debug(' Raw body keys:', Object.keys(req.body || {}));
|
|
474
|
+
logger.debug(' Raw body:', req.body);
|
|
471
475
|
|
|
472
476
|
// Handle both JSON and FormData
|
|
473
477
|
let sessionId, frames, sessionName;
|
|
474
478
|
|
|
475
479
|
if (req.headers['content-type']?.includes('application/json')) {
|
|
476
|
-
|
|
480
|
+
logger.debug(' Processing as JSON request');
|
|
477
481
|
// JSON request - validate with schema
|
|
478
482
|
const { error, value } = frameBatchSchema.validate(req.body);
|
|
479
483
|
if (error) {
|
|
480
|
-
|
|
484
|
+
logger.debug(' JSON validation failed:', error.details);
|
|
481
485
|
return res.status(400).json({
|
|
482
486
|
error: 'Validation failed',
|
|
483
487
|
details: error.details.map(detail => ({
|
|
@@ -490,37 +494,37 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
490
494
|
frames = value.frames;
|
|
491
495
|
sessionName = value.sessionName || null;
|
|
492
496
|
|
|
493
|
-
|
|
497
|
+
logger.debug(' JSON parsed successfully:', { sessionId, frameCount: frames?.length, sessionName });
|
|
494
498
|
} else {
|
|
495
|
-
|
|
499
|
+
logger.debug(' Processing as FormData request');
|
|
496
500
|
|
|
497
501
|
// FormData request - manual validation
|
|
498
502
|
sessionId = req.body.sessionId;
|
|
499
503
|
sessionName = req.body.sessionName || null;
|
|
500
504
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
505
|
+
logger.debug(' SessionId from FormData:', sessionId);
|
|
506
|
+
logger.debug(' SessionName from FormData:', sessionName);
|
|
507
|
+
logger.debug(' Frames field type:', typeof req.body.frames);
|
|
508
|
+
logger.debug(' Frames field length:', req.body.frames?.length);
|
|
509
|
+
logger.debug(' Frames field preview:', req.body.frames?.substring(0, 200));
|
|
506
510
|
|
|
507
511
|
try {
|
|
508
512
|
if (!req.body.frames) {
|
|
509
|
-
|
|
513
|
+
logger.debug(' ❌ No frames field in FormData');
|
|
510
514
|
frames = null;
|
|
511
515
|
} else {
|
|
512
|
-
|
|
516
|
+
logger.debug(' 🔄 Attempting to parse frames JSON...');
|
|
513
517
|
frames = JSON.parse(req.body.frames);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
518
|
+
logger.debug(' ✅ Frames JSON parsed successfully');
|
|
519
|
+
logger.debug(' Parsed frames count:', frames?.length);
|
|
520
|
+
logger.debug(' First frame structure:', frames?.[0] ? Object.keys(frames[0]) : 'no frames');
|
|
517
521
|
}
|
|
518
522
|
|
|
519
|
-
|
|
523
|
+
logger.debug(' 🔄 Validating parsed FormData...');
|
|
520
524
|
// Validate the parsed data
|
|
521
525
|
const { error, value } = frameBatchSchema.validate({ sessionId, frames, sessionName });
|
|
522
526
|
if (error) {
|
|
523
|
-
|
|
527
|
+
logger.debug(' ❌ FormData validation failed:', error.details);
|
|
524
528
|
return res.status(400).json({
|
|
525
529
|
error: 'Validation failed',
|
|
526
530
|
details: error.details.map(detail => ({
|
|
@@ -533,47 +537,47 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
533
537
|
frames = value.frames;
|
|
534
538
|
sessionName = value.sessionName;
|
|
535
539
|
|
|
536
|
-
|
|
540
|
+
logger.debug(' ✅ FormData validation successful');
|
|
537
541
|
} catch (parseError) {
|
|
538
|
-
|
|
539
|
-
|
|
542
|
+
logger.debug(' ❌ Failed to parse frames JSON:', parseError);
|
|
543
|
+
logger.debug(' Raw frames data:', req.body.frames);
|
|
540
544
|
return res.status(400).json({ error: 'Invalid frames JSON format' });
|
|
541
545
|
}
|
|
542
546
|
}
|
|
543
547
|
|
|
544
548
|
if (!sessionId || !frames) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
549
|
+
logger.debug(' ❌ Missing required data');
|
|
550
|
+
logger.debug(' SessionId:', sessionId);
|
|
551
|
+
logger.debug(' Frames:', frames ? 'present' : 'null');
|
|
548
552
|
return res.status(400).json({ error: 'sessionId and frames are required' });
|
|
549
553
|
}
|
|
550
554
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
555
|
+
logger.debug(' ✅ All data validated, proceeding to storage');
|
|
556
|
+
logger.debug(' Session ID:', sessionId);
|
|
557
|
+
logger.debug(' Frame count:', frames?.length);
|
|
554
558
|
|
|
555
559
|
try {
|
|
556
|
-
|
|
560
|
+
logger.debug(' 🔄 Calling activeController.storeFrameBatch...');
|
|
557
561
|
if (frames && frames.length > 0) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
562
|
+
logger.debug(' First frame structure:', Object.keys(frames[0]));
|
|
563
|
+
logger.debug(' First frame has imageData:', !!frames[0].imageData);
|
|
564
|
+
logger.debug(' ImageData length:', frames[0].imageData ? frames[0].imageData.length : 'N/A');
|
|
561
565
|
}
|
|
562
566
|
|
|
563
|
-
|
|
567
|
+
logger.debug(' Session name:', sessionName || 'none');
|
|
564
568
|
|
|
565
569
|
const result = await activeController.storeFrameBatch(sessionId, frames, sessionName);
|
|
566
|
-
|
|
570
|
+
logger.debug(' ✅ Frame batch storage result:', result);
|
|
567
571
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
572
|
+
logger.debug(' Result recording ID:', result?.id);
|
|
573
|
+
logger.debug(' Result frame count:', result?.total_frames);
|
|
574
|
+
logger.debug(' ==========================================');
|
|
571
575
|
|
|
572
576
|
res.json(result);
|
|
573
577
|
} catch (error) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
578
|
+
logger.debug(' ❌ Storage error:', error);
|
|
579
|
+
logger.debug(' Error stack:', error.stack);
|
|
580
|
+
logger.debug(' ==========================================');
|
|
577
581
|
res.status(500).json({ error: 'Failed to store frame batch', details: error.message });
|
|
578
582
|
}
|
|
579
583
|
});
|
|
@@ -592,12 +596,12 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
592
596
|
|
|
593
597
|
// Debug: Verify logs are properly included and formatted
|
|
594
598
|
if (result.frames?.length > 0 && result.frames[0]?.logs?.length > 0) {
|
|
595
|
-
|
|
599
|
+
logger.info(` Frame session ${sessionId}: Loaded ${result.frames.length} frames with console logs`);
|
|
596
600
|
}
|
|
597
601
|
|
|
598
602
|
res.json(result);
|
|
599
603
|
} catch (error) {
|
|
600
|
-
|
|
604
|
+
logger.error('Error retrieving frame session:', error);
|
|
601
605
|
res.status(500).json({ error: 'Failed to retrieve frame session', details: error.message });
|
|
602
606
|
}
|
|
603
607
|
});
|
|
@@ -633,7 +637,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
633
637
|
limit: limit
|
|
634
638
|
});
|
|
635
639
|
} catch (error) {
|
|
636
|
-
|
|
640
|
+
logger.error('Error retrieving snapshots:', error);
|
|
637
641
|
res.status(500).json({ error: 'Failed to retrieve snapshots', details: error.message });
|
|
638
642
|
}
|
|
639
643
|
});
|
|
@@ -655,7 +659,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
655
659
|
if (req.body.logs) {
|
|
656
660
|
const compliance = analyzeLogFieldCompliance(req.body.logs);
|
|
657
661
|
if (!compliance.isCompliant) {
|
|
658
|
-
|
|
662
|
+
logger.debug(' Applied field filtering:', {
|
|
659
663
|
originalLogs: req.body.logs.length,
|
|
660
664
|
nonSchemaFields: compliance.nonSchemaFields,
|
|
661
665
|
missingFields: compliance.missingRequiredFields
|
|
@@ -690,7 +694,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
690
694
|
if (logs) {
|
|
691
695
|
const compliance = analyzeLogFieldCompliance(logs);
|
|
692
696
|
if (!compliance.isCompliant) {
|
|
693
|
-
|
|
697
|
+
logger.debug(' Applied field filtering (FormData):', {
|
|
694
698
|
originalLogs: logs.length,
|
|
695
699
|
nonSchemaFields: compliance.nonSchemaFields,
|
|
696
700
|
missingFields: compliance.missingRequiredFields
|
|
@@ -724,7 +728,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
724
728
|
const result = await activeController.associateLogsWithFrames(sessionId, logs);
|
|
725
729
|
res.json(result);
|
|
726
730
|
} catch (error) {
|
|
727
|
-
|
|
731
|
+
logger.error('Error associating logs with frames:', error);
|
|
728
732
|
res.status(500).json({ error: 'Failed to associate logs', details: error.message });
|
|
729
733
|
}
|
|
730
734
|
});
|
|
@@ -784,7 +788,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
784
788
|
const result = await activeController.streamLogsToFrames(sessionId, logs);
|
|
785
789
|
res.json(result);
|
|
786
790
|
} catch (error) {
|
|
787
|
-
|
|
791
|
+
logger.error('Error streaming logs to frames:', error);
|
|
788
792
|
res.status(500).json({ error: 'Failed to stream logs', details: error.message });
|
|
789
793
|
}
|
|
790
794
|
});
|
|
@@ -834,7 +838,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
834
838
|
chromeConnected: status.connected
|
|
835
839
|
});
|
|
836
840
|
} catch (error) {
|
|
837
|
-
|
|
841
|
+
logger.error('Error handling DOM intent:', error);
|
|
838
842
|
res.status(500).json({
|
|
839
843
|
error: 'Failed to process DOM intent',
|
|
840
844
|
details: error.message
|
|
@@ -1060,7 +1064,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1060
1064
|
res.status(404).json(result);
|
|
1061
1065
|
}
|
|
1062
1066
|
} catch (error) {
|
|
1063
|
-
|
|
1067
|
+
logger.error('Error deleting recording:', error);
|
|
1064
1068
|
res.status(500).json({ error: 'Failed to delete recording', details: error.message });
|
|
1065
1069
|
}
|
|
1066
1070
|
});
|
|
@@ -1080,7 +1084,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1080
1084
|
res.status(404).json(result);
|
|
1081
1085
|
}
|
|
1082
1086
|
} catch (error) {
|
|
1083
|
-
|
|
1087
|
+
logger.error('Error deleting workflow recording:', error);
|
|
1084
1088
|
res.status(500).json({ error: 'Failed to delete workflow recording', details: error.message });
|
|
1085
1089
|
}
|
|
1086
1090
|
});
|
|
@@ -1096,33 +1100,33 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1096
1100
|
const { interactions } = req.body;
|
|
1097
1101
|
|
|
1098
1102
|
// Debug logging to identify validation issues
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1103
|
+
logger.debug(' ==========================================');
|
|
1104
|
+
logger.debug(' Request received for session:', sessionId);
|
|
1105
|
+
logger.debug(' Content-Type:', req.get('content-type'));
|
|
1106
|
+
logger.debug(' User agent:', req.get('user-agent'));
|
|
1107
|
+
logger.debug(' Auth user:', req.user?.name || 'unknown');
|
|
1108
|
+
logger.debug(' Request body keys:', Object.keys(req.body || {}));
|
|
1109
|
+
logger.debug(' Interactions field present:', !!interactions);
|
|
1110
|
+
logger.debug(' Interactions type:', typeof interactions);
|
|
1111
|
+
logger.debug(' Interactions length:', Array.isArray(interactions) ? interactions.length : 'N/A');
|
|
1108
1112
|
if (Array.isArray(interactions) && interactions.length > 0) {
|
|
1109
|
-
|
|
1113
|
+
logger.debug(' First interaction:', JSON.stringify(interactions[0], null, 2));
|
|
1110
1114
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1115
|
+
logger.debug(' Raw body:', JSON.stringify(req.body, null, 2));
|
|
1116
|
+
logger.debug(' ==========================================');
|
|
1113
1117
|
|
|
1114
1118
|
try {
|
|
1115
1119
|
const { database } = await import('./database.js');
|
|
1116
1120
|
const result = database.storeScreenInteractions(sessionId, interactions);
|
|
1117
1121
|
|
|
1118
1122
|
if (result.success) {
|
|
1119
|
-
|
|
1123
|
+
logger.info(` Stored ${result.count} screen interactions for recording ${sessionId}`);
|
|
1120
1124
|
res.json({ success: true, count: result.count });
|
|
1121
1125
|
} else {
|
|
1122
1126
|
res.status(500).json({ success: false, error: result.error });
|
|
1123
1127
|
}
|
|
1124
1128
|
} catch (error) {
|
|
1125
|
-
|
|
1129
|
+
logger.error('Error storing screen interactions:', error);
|
|
1126
1130
|
res.status(500).json({ error: error.message });
|
|
1127
1131
|
}
|
|
1128
1132
|
});
|
|
@@ -1131,14 +1135,14 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1131
1135
|
app.post('/chromedebug/debug-interactions/:sessionId',
|
|
1132
1136
|
authenticate,
|
|
1133
1137
|
(req, res) => {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1138
|
+
logger.debug(' ==========================================');
|
|
1139
|
+
logger.debug(' Auth bypass test successful!');
|
|
1140
|
+
logger.debug(' User:', JSON.stringify(req.user, null, 2));
|
|
1141
|
+
logger.debug(' Session ID:', req.params.sessionId);
|
|
1142
|
+
logger.debug(' Content-Type:', req.get('content-type'));
|
|
1143
|
+
logger.debug(' Request headers:', Object.keys(req.headers));
|
|
1144
|
+
logger.debug(' Request body:', JSON.stringify(req.body, null, 2));
|
|
1145
|
+
logger.debug(' ==========================================');
|
|
1142
1146
|
|
|
1143
1147
|
res.json({
|
|
1144
1148
|
debug: true,
|
|
@@ -1163,7 +1167,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1163
1167
|
const interactions = database.getScreenInteractions(sessionId);
|
|
1164
1168
|
res.json({ interactions });
|
|
1165
1169
|
} catch (error) {
|
|
1166
|
-
|
|
1170
|
+
logger.error('Error retrieving screen interactions:', error);
|
|
1167
1171
|
res.status(500).json({ error: error.message });
|
|
1168
1172
|
}
|
|
1169
1173
|
});
|
|
@@ -1185,7 +1189,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1185
1189
|
|
|
1186
1190
|
res.json(result);
|
|
1187
1191
|
} catch (error) {
|
|
1188
|
-
|
|
1192
|
+
logger.error('License validation error:', error);
|
|
1189
1193
|
res.status(500).json({ error: 'License validation failed' });
|
|
1190
1194
|
}
|
|
1191
1195
|
});
|
|
@@ -1202,7 +1206,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1202
1206
|
|
|
1203
1207
|
res.json(result);
|
|
1204
1208
|
} catch (error) {
|
|
1205
|
-
|
|
1209
|
+
logger.error('Usage check error:', error);
|
|
1206
1210
|
res.status(500).json({ error: 'Usage check failed' });
|
|
1207
1211
|
}
|
|
1208
1212
|
});
|
|
@@ -1220,7 +1224,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1220
1224
|
|
|
1221
1225
|
res.json(result);
|
|
1222
1226
|
} catch (error) {
|
|
1223
|
-
|
|
1227
|
+
logger.error('Usage increment error:', error);
|
|
1224
1228
|
res.status(500).json({ error: 'Usage increment failed' });
|
|
1225
1229
|
}
|
|
1226
1230
|
});
|
|
@@ -1237,7 +1241,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1237
1241
|
|
|
1238
1242
|
res.json(result);
|
|
1239
1243
|
} catch (error) {
|
|
1240
|
-
|
|
1244
|
+
logger.error('License info error:', error);
|
|
1241
1245
|
res.status(500).json({ error: 'Failed to get license info' });
|
|
1242
1246
|
}
|
|
1243
1247
|
});
|
|
@@ -1258,7 +1262,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1258
1262
|
|
|
1259
1263
|
res.json({ instances });
|
|
1260
1264
|
} catch (error) {
|
|
1261
|
-
|
|
1265
|
+
logger.error('List instances error:', error);
|
|
1262
1266
|
res.status(500).json({ error: 'Failed to list instances' });
|
|
1263
1267
|
}
|
|
1264
1268
|
});
|
|
@@ -1279,7 +1283,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1279
1283
|
|
|
1280
1284
|
res.json(result);
|
|
1281
1285
|
} catch (error) {
|
|
1282
|
-
|
|
1286
|
+
logger.info('Deactivate instance error:', error);
|
|
1283
1287
|
res.status(500).json({ error: 'Failed to deactivate instance' });
|
|
1284
1288
|
}
|
|
1285
1289
|
});
|
|
@@ -1294,7 +1298,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1294
1298
|
|
|
1295
1299
|
res.json(status);
|
|
1296
1300
|
} catch (error) {
|
|
1297
|
-
|
|
1301
|
+
logger.info('Cache status error:', error);
|
|
1298
1302
|
res.status(500).json({ error: 'Failed to get cache status' });
|
|
1299
1303
|
}
|
|
1300
1304
|
});
|
|
@@ -1309,14 +1313,14 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1309
1313
|
|
|
1310
1314
|
res.json({ success: true, message: 'License cache cleared' });
|
|
1311
1315
|
} catch (error) {
|
|
1312
|
-
|
|
1316
|
+
logger.info('Clear cache error:', error);
|
|
1313
1317
|
res.status(500).json({ error: 'Failed to clear cache' });
|
|
1314
1318
|
}
|
|
1315
1319
|
});
|
|
1316
1320
|
|
|
1317
1321
|
// Add a catch-all route for debugging
|
|
1318
1322
|
app.use('*', (req, res, next) => {
|
|
1319
|
-
|
|
1323
|
+
logger.info(` ${req.method} ${req.originalUrl}`);
|
|
1320
1324
|
next();
|
|
1321
1325
|
});
|
|
1322
1326
|
|
|
@@ -1325,7 +1329,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1325
1329
|
|
|
1326
1330
|
// 404 handler - must be last
|
|
1327
1331
|
app.use((req, res) => {
|
|
1328
|
-
|
|
1332
|
+
logger.info(` 404 - Route not found: ${req.method} ${req.originalUrl}`);
|
|
1329
1333
|
res.status(404).json({
|
|
1330
1334
|
success: false,
|
|
1331
1335
|
error: 'Endpoint not found',
|
|
@@ -1353,7 +1357,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1353
1357
|
if (targetPort) {
|
|
1354
1358
|
// Use the target port provided by session manager
|
|
1355
1359
|
portsToTry = [targetPort];
|
|
1356
|
-
|
|
1360
|
+
logger.info(`Attempting to start HTTP server on session manager allocated port: ${targetPort}`);
|
|
1357
1361
|
} else {
|
|
1358
1362
|
// Fall back to configured ports
|
|
1359
1363
|
const configuredPorts = configLoader.getHttpServerPorts();
|
|
@@ -1364,7 +1368,7 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1364
1368
|
? [startPort, ...configuredPorts.filter(p => p !== startPort)]
|
|
1365
1369
|
: configuredPorts;
|
|
1366
1370
|
|
|
1367
|
-
|
|
1371
|
+
logger.info('Attempting to start HTTP server on configured ports:', portsToTry);
|
|
1368
1372
|
}
|
|
1369
1373
|
|
|
1370
1374
|
// Try configured ports in order
|
|
@@ -1373,14 +1377,14 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1373
1377
|
await new Promise((resolve, reject) => {
|
|
1374
1378
|
server = app.listen(port, () => {
|
|
1375
1379
|
actualPort = port;
|
|
1376
|
-
|
|
1380
|
+
logger.info(`HTTP server listening on port ${actualPort}`);
|
|
1377
1381
|
resolve();
|
|
1378
1382
|
}).on('error', reject);
|
|
1379
1383
|
});
|
|
1380
1384
|
break;
|
|
1381
1385
|
} catch (error) {
|
|
1382
1386
|
if (error.code === 'EADDRINUSE') {
|
|
1383
|
-
|
|
1387
|
+
logger.info(`Port ${port} is in use, trying next...`);
|
|
1384
1388
|
continue;
|
|
1385
1389
|
}
|
|
1386
1390
|
throw error;
|
|
@@ -1388,12 +1392,12 @@ async function startHttpServer(chromeController = null, targetPort = null) {
|
|
|
1388
1392
|
}
|
|
1389
1393
|
|
|
1390
1394
|
if (!server || !actualPort) {
|
|
1391
|
-
|
|
1392
|
-
|
|
1395
|
+
logger.info('ERROR: All configured ports are in use:', portsToTry);
|
|
1396
|
+
logger.info('Please free up one of these ports or configure different ports');
|
|
1393
1397
|
throw new Error(`Could not find available port for HTTP server. All ports in use: ${portsToTry.join(', ')}`);
|
|
1394
1398
|
}
|
|
1395
1399
|
|
|
1396
|
-
|
|
1400
|
+
logger.info(`HTTP server started on port ${actualPort}`);
|
|
1397
1401
|
|
|
1398
1402
|
// Write port file for discovery
|
|
1399
1403
|
writePortFile(actualPort);
|
|
@@ -1419,7 +1423,7 @@ async function startWebSocketServer() {
|
|
|
1419
1423
|
const wss = new WebSocketServer({ port: wsPort });
|
|
1420
1424
|
|
|
1421
1425
|
wss.on('connection', async (ws) => {
|
|
1422
|
-
|
|
1426
|
+
logger.info(`WebSocket client connected`);
|
|
1423
1427
|
wsClients.add(ws);
|
|
1424
1428
|
|
|
1425
1429
|
// Send initial status
|
|
@@ -1459,10 +1463,10 @@ async function startWebSocketServer() {
|
|
|
1459
1463
|
break;
|
|
1460
1464
|
|
|
1461
1465
|
default:
|
|
1462
|
-
|
|
1466
|
+
logger.info(`Unknown WebSocket message type: ${msg.type}`);
|
|
1463
1467
|
}
|
|
1464
1468
|
} catch (error) {
|
|
1465
|
-
|
|
1469
|
+
logger.info('WebSocket message error:', error);
|
|
1466
1470
|
ws.send(JSON.stringify({
|
|
1467
1471
|
type: 'error',
|
|
1468
1472
|
data: { message: error.message }
|
|
@@ -1471,17 +1475,17 @@ async function startWebSocketServer() {
|
|
|
1471
1475
|
});
|
|
1472
1476
|
|
|
1473
1477
|
ws.on('close', () => {
|
|
1474
|
-
|
|
1478
|
+
logger.info('WebSocket client disconnected');
|
|
1475
1479
|
wsClients.delete(ws);
|
|
1476
1480
|
});
|
|
1477
1481
|
|
|
1478
1482
|
ws.on('error', (error) => {
|
|
1479
|
-
|
|
1483
|
+
logger.info('WebSocket error:', error);
|
|
1480
1484
|
wsClients.delete(ws);
|
|
1481
1485
|
});
|
|
1482
1486
|
});
|
|
1483
1487
|
|
|
1484
|
-
|
|
1488
|
+
logger.info(`WebSocket server listening on port ${wsPort}`);
|
|
1485
1489
|
}
|
|
1486
1490
|
|
|
1487
1491
|
// Export for use by MCP server
|
|
@@ -1499,12 +1503,12 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
1499
1503
|
// Start WebSocket server for Chrome extension
|
|
1500
1504
|
await startWebSocketServer();
|
|
1501
1505
|
|
|
1502
|
-
|
|
1506
|
+
logger.info('ChromeDebug MCP HTTP server running with WebSocket support');
|
|
1503
1507
|
}
|
|
1504
1508
|
|
|
1505
1509
|
// Clean shutdown
|
|
1506
1510
|
const cleanup = async (signal) => {
|
|
1507
|
-
|
|
1511
|
+
logger.info(`Received ${signal}, shutting down HTTP server...`);
|
|
1508
1512
|
await activeController.close();
|
|
1509
1513
|
process.exit(0);
|
|
1510
1514
|
};
|
|
@@ -1512,5 +1516,5 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
1512
1516
|
process.on('SIGINT', () => cleanup('SIGINT'));
|
|
1513
1517
|
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
|
1514
1518
|
|
|
1515
|
-
main().catch(
|
|
1519
|
+
main().catch((err) => logger.error(err, "Fatal error in HTTP server"));
|
|
1516
1520
|
}
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,11 @@ import {
|
|
|
17
17
|
findActiveSessions
|
|
18
18
|
} from './services/unified-session-manager.js';
|
|
19
19
|
import logger from './utils/logger.js';
|
|
20
|
+
import { interceptStdout } from './logger.js';
|
|
21
|
+
|
|
22
|
+
// CRITICAL: Intercept stdout FIRST to prevent MCP JSON-RPC pollution
|
|
23
|
+
// This must happen before any other code runs
|
|
24
|
+
interceptStdout();
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
27
|
* Main application class that orchestrates all components
|
|
@@ -255,5 +260,26 @@ For more information, see the documentation in CLAUDE.md
|
|
|
255
260
|
}
|
|
256
261
|
|
|
257
262
|
// Export for library usage and testing
|
|
258
|
-
|
|
259
|
-
|
|
263
|
+
export { ChromePilotApp };
|
|
264
|
+
|
|
265
|
+
// Auto-start when run directly (not imported as a module)
|
|
266
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
267
|
+
const app = new ChromePilotApp();
|
|
268
|
+
|
|
269
|
+
// Handle graceful shutdown
|
|
270
|
+
process.on('SIGINT', async () => {
|
|
271
|
+
await app.shutdown();
|
|
272
|
+
process.exit(0);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
process.on('SIGTERM', async () => {
|
|
276
|
+
await app.shutdown();
|
|
277
|
+
process.exit(0);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Start the application
|
|
281
|
+
app.start().catch(error => {
|
|
282
|
+
logger.error('Failed to start ChromeDebug:', error);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
});
|
|
285
|
+
}
|
package/src/logger.js
CHANGED
|
@@ -1,11 +1,109 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Structured logging for ChromeDebug MCP
|
|
3
|
+
* Uses pino for performance and structured output
|
|
4
|
+
* All logs go to stderr to preserve MCP stdout for JSON-RPC
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import pino from 'pino';
|
|
8
|
+
|
|
9
|
+
// Determine log level based on environment
|
|
10
|
+
const getLogLevel = () => {
|
|
11
|
+
if (process.env.LOG_LEVEL) {
|
|
12
|
+
return process.env.LOG_LEVEL;
|
|
13
|
+
}
|
|
14
|
+
if (process.env.DEBUG === 'true' || process.env.DEBUG === '1') {
|
|
15
|
+
return 'debug';
|
|
16
|
+
}
|
|
17
|
+
if (process.env.NODE_ENV === 'production') {
|
|
18
|
+
return 'warn';
|
|
19
|
+
}
|
|
20
|
+
return 'info'; // Default for development
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Create base pino instance
|
|
24
|
+
const baseLogger = pino({
|
|
25
|
+
level: getLogLevel(),
|
|
26
|
+
transport: {
|
|
27
|
+
target: 'pino-pretty',
|
|
28
|
+
options: {
|
|
29
|
+
destination: 2, // stderr
|
|
30
|
+
colorize: true,
|
|
31
|
+
ignore: 'pid,hostname',
|
|
32
|
+
translateTime: 'HH:MM:ss',
|
|
33
|
+
singleLine: false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a child logger with component context
|
|
40
|
+
* @param {string} component - Component name for log context
|
|
41
|
+
* @returns {pino.Logger} Configured pino logger
|
|
42
|
+
*/
|
|
43
|
+
export function createLogger(component) {
|
|
44
|
+
return baseLogger.child({ component });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* HTTP request logger helper
|
|
49
|
+
* Only logs if DEBUG_HTTP is enabled or in debug level
|
|
50
|
+
* @param {pino.Logger} logger - Pino logger instance
|
|
51
|
+
* @param {object} req - Express request object
|
|
52
|
+
* @param {object} options - Logging options
|
|
53
|
+
*/
|
|
54
|
+
export function logHttpRequest(logger, req, options = {}) {
|
|
55
|
+
const shouldLog = process.env.DEBUG_HTTP === 'true' ||
|
|
56
|
+
process.env.DEBUG === 'true' ||
|
|
57
|
+
logger.level === 'debug';
|
|
58
|
+
|
|
59
|
+
if (!shouldLog) return;
|
|
60
|
+
|
|
61
|
+
const requestData = {
|
|
62
|
+
method: req.method,
|
|
63
|
+
url: req.originalUrl || req.url,
|
|
64
|
+
ip: req.ip,
|
|
65
|
+
userAgent: req.get('user-agent')
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (options.verbose) {
|
|
69
|
+
requestData.headers = req.headers;
|
|
70
|
+
requestData.contentType = req.get('content-type');
|
|
71
|
+
requestData.contentLength = req.get('content-length');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
logger.debug(requestData, 'HTTP Request');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Intercept stdout to prevent MCP JSON-RPC pollution
|
|
79
|
+
* Call this at application startup for MCP servers
|
|
80
|
+
* Allows MCP JSON-RPC messages through while redirecting other output
|
|
81
|
+
*/
|
|
82
|
+
export function interceptStdout() {
|
|
83
|
+
const originalWrite = process.stdout.write;
|
|
84
|
+
|
|
85
|
+
process.stdout.write = function(chunk, encoding, callback) {
|
|
86
|
+
const message = chunk.toString();
|
|
87
|
+
|
|
88
|
+
// Allow MCP JSON-RPC messages through (they start with { and contain "jsonrpc")
|
|
89
|
+
if (message.trim().startsWith('{') && message.includes('"jsonrpc"')) {
|
|
90
|
+
// This is an MCP message - let it through to stdout unchanged
|
|
91
|
+
return originalWrite.call(process.stdout, chunk, encoding, callback);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Redirect all other stdout to stderr with a warning prefix
|
|
95
|
+
const redirectedMessage = `[STDOUT-REDIRECT] ${message}`;
|
|
96
|
+
return process.stderr.write(redirectedMessage, encoding, callback);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
baseLogger.info('Stdout interception enabled - all output redirected to stderr for MCP safety');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Default export for backward compatibility
|
|
103
|
+
export default {
|
|
104
|
+
error: (...args) => baseLogger.error(...args),
|
|
105
|
+
warn: (...args) => baseLogger.warn(...args),
|
|
106
|
+
info: (...args) => baseLogger.info(...args),
|
|
107
|
+
debug: (...args) => baseLogger.debug(...args),
|
|
108
|
+
log: (...args) => baseLogger.info(...args)
|
|
109
|
+
};
|
package/src/standalone-server.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { startHttpServer, startWebSocketServer } from './http-server.js';
|
|
7
7
|
import { removePortFile } from './port-discovery.js';
|
|
8
|
+
import { interceptStdout } from './logger.js';
|
|
9
|
+
|
|
10
|
+
// CRITICAL: Intercept stdout FIRST to prevent MCP JSON-RPC pollution
|
|
11
|
+
interceptStdout();
|
|
8
12
|
|
|
9
13
|
console.error('╔════════════════════════════════════════════════════════════════╗');
|
|
10
14
|
console.error('║ Chrome Debug HTTP Server ║');
|