ghost-bridge 0.4.0 → 0.5.0

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.
@@ -10,10 +10,10 @@ const CONFIG = {
10
10
  maxPortRetries: 10,
11
11
  token: getMonthlyToken(),
12
12
  autoDetach: false,
13
- maxErrors: 40,
14
- maxStackFrames: 5,
13
+ maxErrors: 100,
14
+ maxStackFrames: 20,
15
15
  maxRequestsTracked: 200,
16
- maxRequestBodySize: 100000,
16
+ maxRequestBodySize: 500000, // 提升至 500KB,容纳较大的 API 请求
17
17
  }
18
18
 
19
19
  let attachedTabId = null
@@ -31,10 +31,10 @@ const pendingRequests = new Map()
31
31
  function setBadgeState(status) {
32
32
  const map = {
33
33
  connecting: { text: "…", color: "#999" },
34
- on: { text: "ON", color: "#34c759" },
34
+ on: { text: "ON", color: "#00d2ff" },
35
35
  off: { text: "OFF", color: "#999" },
36
36
  err: { text: "ERR", color: "#ff3b30" },
37
- att: { text: "ATT", color: "#ff9f0a" },
37
+ att: { text: "ATT", color: "#a252ff" },
38
38
  }
39
39
  const cfg = map[status] || map.off
40
40
  chrome.action.setBadgeText({ text: cfg.text })
@@ -111,7 +111,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => {
111
111
  url: topFrame?.url || detail.url,
112
112
  line: topFrame?.lineNumber,
113
113
  column: topFrame?.columnNumber,
114
- text: detail.text,
114
+ text: detail.exception?.description || detail.text,
115
115
  scriptId: topFrame?.scriptId,
116
116
  stack: compactStack(detail.stackTrace),
117
117
  timestamp: Date.now(),
@@ -139,7 +139,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => {
139
139
  }
140
140
 
141
141
  if (method === "Runtime.consoleAPICalled") {
142
- const args = (params.args || []).map((a) => a.value).filter(Boolean)
142
+ const args = (params.args || []).map((a) => a.description || a.value).filter(Boolean)
143
143
  pushError({
144
144
  type: params.type || "console",
145
145
  severity: params.type === "error" ? "error" : params.type === "warning" ? "warn" : "info",
@@ -253,15 +253,22 @@ chrome.debugger.onDetach.addListener((source, reason) => {
253
253
  scriptSourceCache = new Map()
254
254
  networkRequests = []
255
255
  requestMap = new Map()
256
- }
257
- if (!state.enabled) return
258
- if (reason === "canceled_by_user") {
259
- log("调试被用户取消,已关闭")
260
- state.enabled = false
261
- setBadgeState("off")
262
- } else {
263
- log(`调试已断开:${reason}`)
264
- setBadgeState("att")
256
+
257
+ if (!state.enabled) return
258
+ if (reason === "canceled_by_user") {
259
+ log("调试被用户取消,已关闭")
260
+ state.enabled = false
261
+ state.connected = false
262
+ setBadgeState("off")
263
+ chrome.runtime.sendMessage({ type: 'disconnect' }).catch(() => {})
264
+ } else {
265
+ log(`调试已断开:${reason}`)
266
+ if (state.connected) {
267
+ setBadgeState("on")
268
+ } else {
269
+ setBadgeState("att")
270
+ }
271
+ }
265
272
  }
266
273
  })
267
274
 
@@ -294,11 +301,19 @@ async function ensureAttached() {
294
301
  const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true })
295
302
  if (!tab) throw new Error("没有激活的标签页")
296
303
  if (attachedTabId !== tab.id) {
304
+ if (attachedTabId) {
305
+ try { await chrome.debugger.detach({ tabId: attachedTabId }) } catch (e) {}
306
+ }
297
307
  try {
298
308
  await chrome.debugger.attach({ tabId: tab.id }, "1.3")
299
309
  setBadgeState("on")
300
310
  } catch (e) {
301
- setBadgeState("att")
311
+ attachedTabId = null
312
+ if (state.connected) {
313
+ setBadgeState("on")
314
+ } else {
315
+ setBadgeState("att")
316
+ }
302
317
  throw e
303
318
  }
304
319
  attachedTabId = tab.id
@@ -312,6 +327,13 @@ async function ensureAttached() {
312
327
  await chrome.debugger.sendCommand({ tabId: attachedTabId }, "Debugger.enable")
313
328
  await chrome.debugger.sendCommand({ tabId: attachedTabId }, "Profiler.enable")
314
329
  await chrome.debugger.sendCommand({ tabId: attachedTabId }, "Network.enable").catch(() => {})
330
+
331
+ // Enable auto-attach to sub-targets (iframes, workers) for comprehensive capture
332
+ await chrome.debugger.sendCommand({ tabId: attachedTabId }, "Target.setAutoAttach", {
333
+ autoAttach: true,
334
+ waitForDebuggerOnStart: false,
335
+ flatten: true,
336
+ }).catch(() => {})
315
337
  }
316
338
  return { tabId: attachedTabId }
317
339
  }
@@ -1052,6 +1074,8 @@ async function handleDispatchAction(params = {}) {
1052
1074
  try {
1053
1075
  const el = document.querySelector('[data-ghost-ref="${ref}"]');
1054
1076
  if (!el) return { error: '元素未找到,ref 可能已失效,请重新获取快照' };
1077
+ // 关键修复:确保元素在视口内,否则超出屏幕的坐标无法被 CDP 模拟点击
1078
+ el.scrollIntoView({ block: 'center', inline: 'center' });
1055
1079
  const rect = el.getBoundingClientRect();
1056
1080
  if (rect.width === 0 && rect.height === 0) return { error: '元素不可见(宽高为 0)' };
1057
1081
  return {
@@ -1257,24 +1281,66 @@ function broadcastStatus() {
1257
1281
  status = 'disconnected'
1258
1282
  } else if (state.connected) {
1259
1283
  status = 'connected'
1260
- } else if (state.scanRound >= 2) {
1284
+ } else if (state.scanRound >= 4) {
1261
1285
  status = 'not_found'
1262
1286
  } else {
1263
1287
  status = 'scanning'
1264
1288
  }
1265
- chrome.runtime.sendMessage({
1266
- type: 'statusUpdate',
1267
- state: {
1268
- status,
1269
- enabled: state.enabled,
1270
- port: state.port,
1271
- currentPort: state.currentPort,
1272
- basePort: CONFIG.basePort,
1273
- scanRound: state.scanRound,
1274
- }
1275
- }).catch(() => {}) // popup 可能未打开,忽略错误
1289
+
1290
+ let tabUrl = ''
1291
+ let tabTitle = ''
1292
+
1293
+ if (attachedTabId) {
1294
+ chrome.tabs.get(attachedTabId).then(t => {
1295
+ tabUrl = t.url
1296
+ tabTitle = t.title
1297
+ doBroadcast()
1298
+ }).catch(() => doBroadcast())
1299
+ } else {
1300
+ doBroadcast()
1301
+ }
1302
+
1303
+ function doBroadcast() {
1304
+ const actualErrors = lastErrors.filter(e => e.severity === 'error')
1305
+ chrome.runtime.sendMessage({
1306
+ type: 'statusUpdate',
1307
+ state: {
1308
+ status,
1309
+ enabled: state.enabled,
1310
+ port: state.port,
1311
+ currentPort: state.currentPort,
1312
+ basePort: CONFIG.basePort,
1313
+ scanRound: state.scanRound,
1314
+ errorCount: actualErrors.length,
1315
+ recentErrors: actualErrors.slice(0, 5),
1316
+ tabTitle,
1317
+ tabUrl,
1318
+ }
1319
+ }).catch(() => {}) // popup 可能未打开,忽略错误
1320
+ }
1276
1321
  }
1277
1322
 
1323
+ // 监听被调试页面的导航变化,实时推送到 popup
1324
+ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
1325
+ if (tabId === attachedTabId && (changeInfo.title || changeInfo.url)) {
1326
+ if (state.connected) broadcastStatus()
1327
+ }
1328
+ })
1329
+
1330
+ // 监听用户切换标签页(Active Tab 发生变化),让调试器自动跟随到新标签页
1331
+ chrome.tabs.onActivated.addListener(async (activeInfo) => {
1332
+ if (state.enabled && state.connected) {
1333
+ try {
1334
+ // 这里的 ensureAttached() 会自动处理从旧 Tab detach 并 attach 到新 Tab
1335
+ await ensureAttached()
1336
+ broadcastStatus()
1337
+ } catch (e) {
1338
+ log(`自动跟随切换 Tab 失败:${e.message}`)
1339
+ }
1340
+ }
1341
+ })
1342
+
1343
+
1278
1344
  // ========== 消息监听 ==========
1279
1345
 
1280
1346
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
@@ -1335,19 +1401,41 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1335
1401
  status = 'disconnected'
1336
1402
  } else if (state.connected) {
1337
1403
  status = 'connected'
1338
- } else if (state.scanRound >= 2) {
1404
+ } else if (state.scanRound >= 4) {
1339
1405
  status = 'not_found'
1340
1406
  } else {
1341
1407
  status = 'scanning'
1342
1408
  }
1343
- sendResponse({
1344
- status,
1345
- enabled: state.enabled,
1346
- port: state.port,
1347
- currentPort: state.currentPort,
1348
- basePort: CONFIG.basePort,
1349
- scanRound: state.scanRound,
1350
- })
1409
+
1410
+ let tabUrl = ''
1411
+ let tabTitle = ''
1412
+ if (attachedTabId) {
1413
+ chrome.tabs.get(attachedTabId).then(t => {
1414
+ tabUrl = t.url
1415
+ tabTitle = t.title
1416
+ sendStatusResponse()
1417
+ }).catch(() => {
1418
+ sendStatusResponse()
1419
+ })
1420
+ } else {
1421
+ sendStatusResponse()
1422
+ }
1423
+
1424
+ function sendStatusResponse() {
1425
+ const actualErrors = lastErrors.filter(e => e.severity === 'error')
1426
+ sendResponse({
1427
+ status,
1428
+ enabled: state.enabled,
1429
+ port: state.port,
1430
+ currentPort: state.currentPort,
1431
+ basePort: CONFIG.basePort,
1432
+ scanRound: state.scanRound,
1433
+ errorCount: actualErrors.length,
1434
+ recentErrors: actualErrors.slice(0, 5),
1435
+ tabTitle,
1436
+ tabUrl,
1437
+ })
1438
+ }
1351
1439
  return true
1352
1440
  }
1353
1441
 
@@ -1383,8 +1471,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1383
1471
  setBadgeState('off')
1384
1472
  detachAllTargets().catch(() => {})
1385
1473
 
1386
- // 通知 offscreen 断开
1474
+ // 通知 offscreen 断开 (WebSocket 清除)
1387
1475
  chrome.runtime.sendMessage({ type: 'disconnect' }).catch(() => {})
1476
+
1477
+ // 关键修复:显式销毁 offscreen document 防止内存泄漏
1478
+ closeOffscreenDocument().catch(() => {})
1388
1479
 
1389
1480
  sendResponse({ ok: true })
1390
1481
  return true
Binary file
Binary file
Binary file
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Ghost Bridge",
4
- "version": "0.3.0",
4
+ "version": "0.5.0",
5
5
  "description": "Zero-restart Chrome debugger bridge for Claude MCP, optimized for no-sourcemap production debugging.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -171,7 +171,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
171
171
  return true
172
172
  }
173
173
 
174
- if (message.type === 'getStatus') {
174
+ if (message.type === 'getOffscreenStatus') {
175
175
  sendResponse({
176
176
  connected: ws && ws.readyState === WebSocket.OPEN,
177
177
  })