ghost-bridge 0.5.1 → 0.5.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/README.md +1 -1
- package/dist/server.js +21 -16
- package/extension/background.js +30 -15
- package/extension/manifest.json +1 -1
- package/extension/offscreen.js +33 -25
- package/extension/popup.html +9 -0
- package/extension/popup.js +16 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ ghost-bridge init
|
|
|
139
139
|
|
|
140
140
|
| Setting | Default | Description |
|
|
141
141
|
|---------|---------|-------------|
|
|
142
|
-
| **Base Port** | `33333` | WS port.
|
|
142
|
+
| **Base Port** | `33333` | Fixed WS port. If occupied, Ghost Bridge fails fast and asks you to free it or set `GHOST_BRIDGE_PORT`. |
|
|
143
143
|
| **Token** | *Monthly UUID* | Local WS auth token, auto-rotates on the 1st of every month. |
|
|
144
144
|
| **Auto Detach** | `false` | Keeps debugger attached to actively buffer invisible exceptions and network calls. |
|
|
145
145
|
|
package/dist/server.js
CHANGED
|
@@ -28536,7 +28536,6 @@ var GHOST_BRIDGE_VERSION = packageJson.version;
|
|
|
28536
28536
|
|
|
28537
28537
|
// src/server.js
|
|
28538
28538
|
var BASE_PORT = Number(process.env.GHOST_BRIDGE_PORT || 33333);
|
|
28539
|
-
var MAX_PORT_RETRIES = 10;
|
|
28540
28539
|
function getMonthlyToken() {
|
|
28541
28540
|
const now = /* @__PURE__ */ new Date();
|
|
28542
28541
|
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
|
|
@@ -28567,6 +28566,10 @@ function getExistingService() {
|
|
|
28567
28566
|
if (!fs2.existsSync(PORT_INFO_FILE)) return null;
|
|
28568
28567
|
const info = JSON.parse(fs2.readFileSync(PORT_INFO_FILE, "utf-8"));
|
|
28569
28568
|
if (!info.pid || !info.port) return null;
|
|
28569
|
+
if (info.port !== BASE_PORT) {
|
|
28570
|
+
fs2.unlinkSync(PORT_INFO_FILE);
|
|
28571
|
+
return null;
|
|
28572
|
+
}
|
|
28570
28573
|
if (!isProcessRunning(info.pid)) {
|
|
28571
28574
|
log(`\u65E7\u670D\u52A1 PID ${info.pid} \u5DF2\u4E0D\u5B58\u5728\uFF0C\u6E05\u7406\u65E7\u4FE1\u606F`);
|
|
28572
28575
|
fs2.unlinkSync(PORT_INFO_FILE);
|
|
@@ -28618,22 +28621,14 @@ function isPortAvailable(port) {
|
|
|
28618
28621
|
});
|
|
28619
28622
|
}
|
|
28620
28623
|
async function startWebSocketServer() {
|
|
28621
|
-
|
|
28622
|
-
|
|
28623
|
-
|
|
28624
|
-
if (available) {
|
|
28625
|
-
actualPort = port;
|
|
28626
|
-
const wss2 = new import_websocket_server.default({ port });
|
|
28627
|
-
if (port !== BASE_PORT) {
|
|
28628
|
-
log(`\u26A0\uFE0F \u7AEF\u53E3 ${BASE_PORT} \u88AB\u5360\u7528\uFF0C\u5DF2\u5207\u6362\u5230\u7AEF\u53E3 ${port}`);
|
|
28629
|
-
}
|
|
28630
|
-
log(`\u{1F680} WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${port}${WS_TOKEN ? "\uFF08\u542F\u7528 token \u6821\u9A8C\uFF09" : ""}`);
|
|
28631
|
-
return wss2;
|
|
28632
|
-
} else {
|
|
28633
|
-
log(`\u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5\u4E0B\u4E00\u4E2A...`);
|
|
28634
|
-
}
|
|
28624
|
+
const available = await isPortAvailable(BASE_PORT);
|
|
28625
|
+
if (!available) {
|
|
28626
|
+
throw new Error(`\u56FA\u5B9A\u7AEF\u53E3 ${BASE_PORT} \u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CA\u653E\u8BE5\u7AEF\u53E3\u6216\u901A\u8FC7 GHOST_BRIDGE_PORT \u6307\u5B9A\u5176\u4ED6\u7AEF\u53E3`);
|
|
28635
28627
|
}
|
|
28636
|
-
|
|
28628
|
+
actualPort = BASE_PORT;
|
|
28629
|
+
const wss2 = new import_websocket_server.default({ port: BASE_PORT });
|
|
28630
|
+
log(`\u{1F680} WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${BASE_PORT}${WS_TOKEN ? "\uFF08\u542F\u7528 token \u6821\u9A8C\uFF09" : ""}`);
|
|
28631
|
+
return wss2;
|
|
28637
28632
|
}
|
|
28638
28633
|
async function initWebSocketService() {
|
|
28639
28634
|
const existing = getExistingService();
|
|
@@ -28653,6 +28648,16 @@ async function initWebSocketService() {
|
|
|
28653
28648
|
}
|
|
28654
28649
|
}
|
|
28655
28650
|
}
|
|
28651
|
+
if (!await isPortAvailable(BASE_PORT)) {
|
|
28652
|
+
const valid = await verifyExistingService(BASE_PORT);
|
|
28653
|
+
if (valid) {
|
|
28654
|
+
actualPort = BASE_PORT;
|
|
28655
|
+
isMainInstance = false;
|
|
28656
|
+
log(`\u2705 \u590D\u7528\u56FA\u5B9A\u7AEF\u53E3\u4E0A\u7684\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
|
|
28657
|
+
return null;
|
|
28658
|
+
}
|
|
28659
|
+
throw new Error(`\u56FA\u5B9A\u7AEF\u53E3 ${BASE_PORT} \u5DF2\u88AB\u5176\u4ED6\u8FDB\u7A0B\u5360\u7528\uFF0C\u8BF7\u91CA\u653E\u8BE5\u7AEF\u53E3\u6216\u901A\u8FC7 GHOST_BRIDGE_PORT \u6307\u5B9A\u5176\u4ED6\u7AEF\u53E3`);
|
|
28660
|
+
}
|
|
28656
28661
|
const wss2 = await startWebSocketServer();
|
|
28657
28662
|
isMainInstance = true;
|
|
28658
28663
|
fs2.writeFileSync(
|
package/extension/background.js
CHANGED
|
@@ -7,7 +7,6 @@ function getMonthlyToken() {
|
|
|
7
7
|
|
|
8
8
|
const CONFIG = {
|
|
9
9
|
basePort: 33333,
|
|
10
|
-
maxPortRetries: 10,
|
|
11
10
|
token: getMonthlyToken(),
|
|
12
11
|
autoDetach: false,
|
|
13
12
|
maxErrors: 100,
|
|
@@ -23,7 +22,7 @@ let lastErrors = []
|
|
|
23
22
|
let lastErrorLocation = null
|
|
24
23
|
let requestMap = new Map()
|
|
25
24
|
let networkRequests = []
|
|
26
|
-
let state = { enabled: false, connected: false, port: null, currentPort: null,
|
|
25
|
+
let state = { enabled: false, connected: false, port: null, currentPort: null, connectionStatus: 'disconnected', connectionError: '' }
|
|
27
26
|
|
|
28
27
|
// 待处理的请求(等待 offscreen 响应)
|
|
29
28
|
const pendingRequests = new Map()
|
|
@@ -1396,10 +1395,8 @@ function broadcastStatus() {
|
|
|
1396
1395
|
status = 'disconnected'
|
|
1397
1396
|
} else if (state.connected) {
|
|
1398
1397
|
status = 'connected'
|
|
1399
|
-
} else if (state.scanRound >= 4) {
|
|
1400
|
-
status = 'not_found'
|
|
1401
1398
|
} else {
|
|
1402
|
-
status = '
|
|
1399
|
+
status = state.connectionStatus || 'connecting'
|
|
1403
1400
|
}
|
|
1404
1401
|
|
|
1405
1402
|
let tabUrl = ''
|
|
@@ -1425,7 +1422,7 @@ function broadcastStatus() {
|
|
|
1425
1422
|
port: state.port,
|
|
1426
1423
|
currentPort: state.currentPort,
|
|
1427
1424
|
basePort: CONFIG.basePort,
|
|
1428
|
-
|
|
1425
|
+
connectionError: state.connectionError,
|
|
1429
1426
|
errorCount: actualErrors.length,
|
|
1430
1427
|
recentErrors: actualErrors.slice(0, 5),
|
|
1431
1428
|
tabTitle,
|
|
@@ -1474,18 +1471,32 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1474
1471
|
if (message.status === 'connected') {
|
|
1475
1472
|
state.connected = true
|
|
1476
1473
|
state.port = message.port
|
|
1474
|
+
state.currentPort = message.port
|
|
1475
|
+
state.connectionStatus = 'connected'
|
|
1476
|
+
state.connectionError = ''
|
|
1477
1477
|
setBadgeState('on')
|
|
1478
1478
|
log(`✅ 已连接到 ghost-bridge 服务 (端口 ${message.port})`)
|
|
1479
1479
|
ensureAttached().catch((e) => log(`attach 失败:${e.message}`))
|
|
1480
1480
|
} else if (message.status === 'disconnected') {
|
|
1481
1481
|
state.connected = false
|
|
1482
1482
|
state.port = null
|
|
1483
|
+
state.connectionStatus = 'connecting'
|
|
1484
|
+
state.connectionError = ''
|
|
1483
1485
|
if (state.enabled) setBadgeState('connecting')
|
|
1484
|
-
} else if (message.status === '
|
|
1486
|
+
} else if (message.status === 'connecting') {
|
|
1485
1487
|
state.currentPort = message.currentPort
|
|
1488
|
+
state.connectionStatus = 'connecting'
|
|
1489
|
+
state.connectionError = ''
|
|
1486
1490
|
setBadgeState('connecting')
|
|
1491
|
+
} else if (message.status === 'error') {
|
|
1492
|
+
state.currentPort = message.currentPort
|
|
1493
|
+
state.connectionStatus = 'error'
|
|
1494
|
+
state.connectionError = message.errorMessage || ''
|
|
1495
|
+
setBadgeState('err')
|
|
1487
1496
|
} else if (message.status === 'not_found') {
|
|
1488
|
-
state.
|
|
1497
|
+
state.currentPort = message.currentPort
|
|
1498
|
+
state.connectionStatus = 'not_found'
|
|
1499
|
+
state.connectionError = ''
|
|
1489
1500
|
setBadgeState('connecting')
|
|
1490
1501
|
}
|
|
1491
1502
|
broadcastStatus() // 状态变化时主动推送
|
|
@@ -1516,10 +1527,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1516
1527
|
status = 'disconnected'
|
|
1517
1528
|
} else if (state.connected) {
|
|
1518
1529
|
status = 'connected'
|
|
1519
|
-
} else if (state.scanRound >= 4) {
|
|
1520
|
-
status = 'not_found'
|
|
1521
1530
|
} else {
|
|
1522
|
-
status = '
|
|
1531
|
+
status = state.connectionStatus || 'connecting'
|
|
1523
1532
|
}
|
|
1524
1533
|
|
|
1525
1534
|
let tabUrl = ''
|
|
@@ -1544,7 +1553,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1544
1553
|
port: state.port,
|
|
1545
1554
|
currentPort: state.currentPort,
|
|
1546
1555
|
basePort: CONFIG.basePort,
|
|
1547
|
-
|
|
1556
|
+
connectionError: state.connectionError,
|
|
1548
1557
|
errorCount: actualErrors.length,
|
|
1549
1558
|
recentErrors: actualErrors.slice(0, 5),
|
|
1550
1559
|
tabTitle,
|
|
@@ -1561,7 +1570,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1561
1570
|
chrome.storage.local.set({ basePort: message.port })
|
|
1562
1571
|
}
|
|
1563
1572
|
state.enabled = true
|
|
1564
|
-
state.
|
|
1573
|
+
state.connected = false
|
|
1574
|
+
state.port = null
|
|
1575
|
+
state.currentPort = CONFIG.basePort
|
|
1576
|
+
state.connectionStatus = 'connecting'
|
|
1577
|
+
state.connectionError = ''
|
|
1565
1578
|
setBadgeState('connecting')
|
|
1566
1579
|
|
|
1567
1580
|
// 启动 offscreen 并开始连接
|
|
@@ -1570,7 +1583,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1570
1583
|
type: 'connect',
|
|
1571
1584
|
basePort: CONFIG.basePort,
|
|
1572
1585
|
token: CONFIG.token,
|
|
1573
|
-
maxPortRetries: CONFIG.maxPortRetries,
|
|
1574
1586
|
}).catch(() => {})
|
|
1575
1587
|
})
|
|
1576
1588
|
|
|
@@ -1582,7 +1594,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1582
1594
|
if (message.type === 'disconnect') {
|
|
1583
1595
|
state.enabled = false
|
|
1584
1596
|
state.connected = false
|
|
1585
|
-
state.
|
|
1597
|
+
state.port = null
|
|
1598
|
+
state.currentPort = null
|
|
1599
|
+
state.connectionStatus = 'disconnected'
|
|
1600
|
+
state.connectionError = ''
|
|
1586
1601
|
setBadgeState('off')
|
|
1587
1602
|
detachAllTargets().catch(() => {})
|
|
1588
1603
|
|
package/extension/manifest.json
CHANGED
package/extension/offscreen.js
CHANGED
|
@@ -7,7 +7,6 @@ let manualDisconnect = false // 用户主动断开标志,防止 onclose 触
|
|
|
7
7
|
let config = {
|
|
8
8
|
basePort: 33333,
|
|
9
9
|
token: '',
|
|
10
|
-
maxPortRetries: 10,
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
function log(msg) {
|
|
@@ -23,29 +22,17 @@ function getMonthlyToken() {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
// 连接到服务器
|
|
26
|
-
function connect(
|
|
25
|
+
function connect() {
|
|
27
26
|
// 如果已手动断开,不再尝试连接
|
|
28
27
|
if (manualDisconnect) return
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
log(`扫描完毕,未找到服务,2秒后重试...`)
|
|
32
|
-
reconnectTimer = setTimeout(() => connect(0, true), 2000)
|
|
33
|
-
chrome.runtime.sendMessage({ type: 'status', status: 'not_found' }).catch(() => {})
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const port = config.basePort + portIndex
|
|
29
|
+
const port = config.basePort
|
|
38
30
|
const url = new URL(`ws://localhost:${port}`)
|
|
39
31
|
url.searchParams.set('token', config.token)
|
|
40
|
-
|
|
41
|
-
if (portIndex === 0 && isNewRound) {
|
|
42
|
-
log(`开始扫描端口 ${config.basePort}-${config.basePort + config.maxPortRetries - 1}`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
log(`尝试连接端口 ${port}...`)
|
|
32
|
+
log(`尝试连接固定端口 ${port}...`)
|
|
46
33
|
chrome.runtime.sendMessage({
|
|
47
34
|
type: 'status',
|
|
48
|
-
status: '
|
|
35
|
+
status: 'connecting',
|
|
49
36
|
currentPort: port,
|
|
50
37
|
}).catch(() => {})
|
|
51
38
|
|
|
@@ -59,8 +46,11 @@ function connect(portIndex = 0, isNewRound = false) {
|
|
|
59
46
|
}, 2000) // 增加到 2 秒
|
|
60
47
|
|
|
61
48
|
let identityVerified = false
|
|
49
|
+
let socketOpened = false
|
|
50
|
+
let terminalErrorMessage = ''
|
|
62
51
|
|
|
63
52
|
ws.onopen = () => {
|
|
53
|
+
socketOpened = true
|
|
64
54
|
clearTimeout(connectionTimeout)
|
|
65
55
|
log(`WebSocket 已连接端口 ${port},等待身份验证...`)
|
|
66
56
|
}
|
|
@@ -84,9 +74,15 @@ function connect(portIndex = 0, isNewRound = false) {
|
|
|
84
74
|
port: port,
|
|
85
75
|
}).catch(() => {})
|
|
86
76
|
} else {
|
|
87
|
-
|
|
77
|
+
terminalErrorMessage = `Port ${port} is occupied by a non-matching service, or the token does not match.`
|
|
78
|
+
log('身份验证失败,将在固定端口上重试...')
|
|
79
|
+
chrome.runtime.sendMessage({
|
|
80
|
+
type: 'status',
|
|
81
|
+
status: 'error',
|
|
82
|
+
currentPort: port,
|
|
83
|
+
errorMessage: terminalErrorMessage,
|
|
84
|
+
}).catch(() => {})
|
|
88
85
|
ws.close()
|
|
89
|
-
setTimeout(() => connect(portIndex + 1), 50)
|
|
90
86
|
}
|
|
91
87
|
return
|
|
92
88
|
}
|
|
@@ -107,15 +103,28 @@ function connect(portIndex = 0, isNewRound = false) {
|
|
|
107
103
|
if (manualDisconnect) return
|
|
108
104
|
|
|
109
105
|
if (!identityVerified) {
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
if (terminalErrorMessage || socketOpened) {
|
|
107
|
+
const errorMessage = terminalErrorMessage || `Port ${port} is occupied or responding with a non-ghost-bridge protocol.`
|
|
108
|
+
log(`${errorMessage} 2秒后重试...`)
|
|
109
|
+
chrome.runtime.sendMessage({
|
|
110
|
+
type: 'status',
|
|
111
|
+
status: 'error',
|
|
112
|
+
currentPort: port,
|
|
113
|
+
errorMessage,
|
|
114
|
+
}).catch(() => {})
|
|
115
|
+
reconnectTimer = setTimeout(() => connect(), 2000)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
log(`固定端口 ${port} 未发现可用服务,2秒后重试...`)
|
|
119
|
+
chrome.runtime.sendMessage({ type: 'status', status: 'not_found', currentPort: port }).catch(() => {})
|
|
120
|
+
reconnectTimer = setTimeout(() => connect(), 2000)
|
|
112
121
|
return
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
// 连接断开,重试
|
|
116
|
-
log(
|
|
125
|
+
log(`端口 ${port} 连接断开,尝试重连...`)
|
|
117
126
|
chrome.runtime.sendMessage({ type: 'status', status: 'disconnected' }).catch(() => {})
|
|
118
|
-
reconnectTimer = setTimeout(() => connect(
|
|
127
|
+
reconnectTimer = setTimeout(() => connect(), 1000)
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
ws.onerror = () => {
|
|
@@ -151,10 +160,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
151
160
|
if (message.type === 'connect') {
|
|
152
161
|
config.basePort = message.basePort || 33333
|
|
153
162
|
config.token = message.token || getMonthlyToken()
|
|
154
|
-
config.maxPortRetries = message.maxPortRetries || 10
|
|
155
163
|
disconnect()
|
|
156
164
|
manualDisconnect = false // 用户重新连接,清除断开标志
|
|
157
|
-
connect(
|
|
165
|
+
connect()
|
|
158
166
|
sendResponse({ ok: true })
|
|
159
167
|
return true
|
|
160
168
|
}
|
package/extension/popup.html
CHANGED
|
@@ -121,6 +121,15 @@
|
|
|
121
121
|
}
|
|
122
122
|
body.connected-state .bg-ripple-2 { animation-delay: 1.25s; }
|
|
123
123
|
|
|
124
|
+
.ghost-error .ghost-body { fill: #ef4444; }
|
|
125
|
+
.ghost-error .ghost-eyes-normal { opacity: 1; }
|
|
126
|
+
|
|
127
|
+
body.error-state .bg-ripple {
|
|
128
|
+
stroke: #ef4444;
|
|
129
|
+
animation: bg-ripple-scan 1.8s infinite cubic-bezier(0, 0, 0.2, 1);
|
|
130
|
+
}
|
|
131
|
+
body.error-state .bg-ripple-2 { animation-delay: 0.9s; }
|
|
132
|
+
|
|
124
133
|
@keyframes bg-ripple-scan {
|
|
125
134
|
0% { r: 10%; opacity: 0.8; stroke-width: 3; }
|
|
126
135
|
100% { r: 80%; opacity: 0; stroke-width: 0; }
|
package/extension/popup.js
CHANGED
|
@@ -53,7 +53,7 @@ const STATUS_MAP = {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function renderUI(state) {
|
|
56
|
-
const { status, port,
|
|
56
|
+
const { status, port, enabled, currentPort, basePort, connectionError, errorCount, recentErrors, tabTitle } = state
|
|
57
57
|
const config = STATUS_MAP[status] || STATUS_MAP.disconnected
|
|
58
58
|
|
|
59
59
|
// Update classes for color & animations
|
|
@@ -66,6 +66,9 @@ function renderUI(state) {
|
|
|
66
66
|
} else if (config.statusClass === 'connecting') {
|
|
67
67
|
headerGhost.className = 'ghost-wrapper ghost-connecting'
|
|
68
68
|
document.body.className = 'connecting-state'
|
|
69
|
+
} else if (config.statusClass === 'error') {
|
|
70
|
+
headerGhost.className = 'ghost-wrapper ghost-error'
|
|
71
|
+
document.body.className = 'error-state'
|
|
69
72
|
} else {
|
|
70
73
|
headerGhost.className = 'ghost-wrapper ghost-disconnected'
|
|
71
74
|
document.body.className = 'disconnected-state'
|
|
@@ -117,8 +120,7 @@ function renderUI(state) {
|
|
|
117
120
|
|
|
118
121
|
detailContainer.classList.remove('collapsed')
|
|
119
122
|
} else if ((status === 'connecting' || status === 'verifying' || status === 'scanning') && currentPort) {
|
|
120
|
-
|
|
121
|
-
portVal.textContent = `Scanning: ${currentPort}${roundText}`
|
|
123
|
+
portVal.textContent = `Connecting: ${currentPort}`
|
|
122
124
|
portVal.className = 'detail-value highlight'
|
|
123
125
|
tabRow.classList.add('hidden')
|
|
124
126
|
errorRow.classList.add('hidden')
|
|
@@ -131,6 +133,12 @@ function renderUI(state) {
|
|
|
131
133
|
tabRow.classList.add('hidden')
|
|
132
134
|
errorRow.classList.add('hidden')
|
|
133
135
|
detailContainer.classList.remove('collapsed')
|
|
136
|
+
} else if (status === 'error') {
|
|
137
|
+
portVal.textContent = `Port ${currentPort || basePort || '-'} blocked`
|
|
138
|
+
portVal.className = 'detail-value warning'
|
|
139
|
+
tabRow.classList.add('hidden')
|
|
140
|
+
errorRow.classList.add('hidden')
|
|
141
|
+
detailContainer.classList.remove('collapsed')
|
|
134
142
|
} else {
|
|
135
143
|
detailContainer.classList.add('collapsed')
|
|
136
144
|
}
|
|
@@ -145,8 +153,11 @@ function renderUI(state) {
|
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
// Scan info text
|
|
148
|
-
if (
|
|
149
|
-
scanInfo.textContent =
|
|
156
|
+
if (status === 'error' && connectionError) {
|
|
157
|
+
scanInfo.textContent = connectionError
|
|
158
|
+
scanInfo.classList.remove('collapsed')
|
|
159
|
+
} else if (status === 'not_found' && basePort) {
|
|
160
|
+
scanInfo.textContent = `Port ${basePort} unavailable. Is your MCP server running on this port?`
|
|
150
161
|
scanInfo.classList.remove('collapsed')
|
|
151
162
|
} else {
|
|
152
163
|
scanInfo.classList.add('collapsed')
|