dsh-site-bridge 0.7.5 → 0.8.1
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/docs/limitations.md +2 -1
- package/extension/manifest.json +1 -1
- package/extension/sw.js +62 -6
- package/lib/index.js +7 -1
- package/package.json +1 -1
package/docs/limitations.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ограничения: что именно ломается и как это снять
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Пять ограничений, которые чаще всего мешают на реальных сайтах. Для каждого: наблюдаемое
|
|
4
4
|
поведение, причина (со ссылкой на код), что нужно изменить и сколько это стоит.
|
|
5
5
|
|
|
6
6
|
| # | Ограничение | Статус | Что осталось |
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
| 2 | shadow DOM частично | **частично снято** | открытые корни: селекторы-цепочки и поиск работают; закрытые корни не видны |
|
|
10
10
|
| 3 | тело ответа ≤ 400 КБ | **снято** | лимит 5 МБ в воркере, тела > 200 КБ выносятся в `bodies/<id>.json` |
|
|
11
11
|
| 4 | один таб на сессию | **снято** | `Map<tabId, session>`, `tabId` в командах, переключатель в попапе |
|
|
12
|
+
| 5 | Chrome отказывает в `chrome.debugger.attach` отдельной вкладке (`Cannot access a chrome-extension:// URL of different extension`) | открыто | воспроизведено на `mail.google.com`: соседние сайты подключаются, Gmail — нет, при этом `tabs.get` вкладку отдаёт, а в списке целей у неё обычный `page` с https-адресом. Обход не найден; `site_tabs {action:"diagnose"}` показывает, что видит Chrome |
|
|
12
13
|
|
|
13
14
|
Ниже — исходный разбор каждого ограничения; для снятых пунктов оставлено описание того, что
|
|
14
15
|
именно было изменено и какие границы остались.
|
package/extension/manifest.json
CHANGED
package/extension/sw.js
CHANGED
|
@@ -88,18 +88,27 @@ async function evt(kind, payload = {}) {
|
|
|
88
88
|
|
|
89
89
|
let looping = false
|
|
90
90
|
|
|
91
|
+
/** Runs a promise with a deadline, so one stuck Chrome call cannot park the loop. */
|
|
92
|
+
function withDeadline(promise, ms, label) {
|
|
93
|
+
return Promise.race([
|
|
94
|
+
promise,
|
|
95
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} не ответил за ${ms} мс`)), ms))
|
|
96
|
+
])
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
async function ensureLoop() {
|
|
92
100
|
if (looping) return
|
|
93
101
|
looping = true
|
|
94
102
|
let greeted = false
|
|
95
103
|
try {
|
|
96
104
|
for (;;) {
|
|
97
|
-
|
|
98
|
-
if (!cfg.token) {
|
|
99
|
-
await sleep(1500)
|
|
100
|
-
continue
|
|
101
|
-
}
|
|
105
|
+
state.lastTick = Date.now()
|
|
102
106
|
try {
|
|
107
|
+
const cfg = await loadCfg()
|
|
108
|
+
if (!cfg.token) {
|
|
109
|
+
await sleep(1500)
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
103
112
|
const reply = await api(cfg, '/v1/poll?timeout=20000')
|
|
104
113
|
if (!greeted) {
|
|
105
114
|
greeted = true
|
|
@@ -115,7 +124,7 @@ async function ensureLoop() {
|
|
|
115
124
|
state.attempts = 0
|
|
116
125
|
for (const command of reply.commands || []) {
|
|
117
126
|
// Sequential: a command may pin a tab for its whole duration.
|
|
118
|
-
await runCommand(cfg, command)
|
|
127
|
+
await withDeadline(runCommand(cfg, command), 120000, `команда ${command?.op ?? '?'}`)
|
|
119
128
|
}
|
|
120
129
|
} catch (error) {
|
|
121
130
|
greeted = false
|
|
@@ -129,6 +138,16 @@ async function ensureLoop() {
|
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
140
|
|
|
141
|
+
// If the loop parks on a Chrome call that never settles, restart it.
|
|
142
|
+
setInterval(() => {
|
|
143
|
+
if (!looping) return
|
|
144
|
+
if (Date.now() - (state.lastTick ?? 0) > 90000) {
|
|
145
|
+
state.lastError = 'цикл опроса завис, перезапускаю'
|
|
146
|
+
looping = false
|
|
147
|
+
void ensureLoop()
|
|
148
|
+
}
|
|
149
|
+
}, 30000)
|
|
150
|
+
|
|
132
151
|
function sleep(ms) {
|
|
133
152
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
134
153
|
}
|
|
@@ -186,6 +205,8 @@ async function handleMessageInner(message) {
|
|
|
186
205
|
connected: state.lastPollOk > 0 && Date.now() - state.lastPollOk < 45000,
|
|
187
206
|
lastError: state.lastError ?? null,
|
|
188
207
|
attempts: state.attempts || 0,
|
|
208
|
+
loopAlive: looping,
|
|
209
|
+
lastTickAgoMs: state.lastTick ? Date.now() - state.lastTick : null,
|
|
189
210
|
target: active?.target ?? null,
|
|
190
211
|
activeTabId: state.activeTabId,
|
|
191
212
|
targets: [...state.sessions.values()].map((session) => ({
|
|
@@ -1585,6 +1606,41 @@ async function dispatch(op, args) {
|
|
|
1585
1606
|
if (tab.windowId != null) await chrome.windows.update(tab.windowId, { focused: true }).catch(() => {})
|
|
1586
1607
|
return { focused: session.tabId }
|
|
1587
1608
|
}
|
|
1609
|
+
case 'tab.diagnose': {
|
|
1610
|
+
const id = Number(args.tabId)
|
|
1611
|
+
if (!Number.isFinite(id)) throw new Error('нужен tabId')
|
|
1612
|
+
const info = await chrome.tabs.get(id).catch((error) => ({ error: String(error?.message ?? error) }))
|
|
1613
|
+
const windows = await chrome.windows.getAll({}).catch(() => [])
|
|
1614
|
+
const window = windows.find((item) => item.id === info?.windowId) ?? null
|
|
1615
|
+
const targets = await chrome.debugger.getTargets().catch(() => [])
|
|
1616
|
+
const mine = targets
|
|
1617
|
+
.filter((target) => target.tabId === id)
|
|
1618
|
+
.map((target) => ({
|
|
1619
|
+
id: target.id,
|
|
1620
|
+
type: target.type,
|
|
1621
|
+
url: String(target.url ?? '').slice(0, 300),
|
|
1622
|
+
title: String(target.title ?? '').slice(0, 120),
|
|
1623
|
+
attached: target.attached === true
|
|
1624
|
+
}))
|
|
1625
|
+
return {
|
|
1626
|
+
tab: info
|
|
1627
|
+
? {
|
|
1628
|
+
id: info.id,
|
|
1629
|
+
url: String(info.url ?? '').slice(0, 300),
|
|
1630
|
+
pendingUrl: String(info.pendingUrl ?? '').slice(0, 300),
|
|
1631
|
+
title: String(info.title ?? '').slice(0, 120),
|
|
1632
|
+
status: info.status,
|
|
1633
|
+
active: info.active,
|
|
1634
|
+
discarded: info.discarded,
|
|
1635
|
+
windowId: info.windowId,
|
|
1636
|
+
index: info.index
|
|
1637
|
+
}
|
|
1638
|
+
: { error: info?.error },
|
|
1639
|
+
window: window ? { id: window.id, type: window.type, state: window.state } : null,
|
|
1640
|
+
targets: mine,
|
|
1641
|
+
targetCount: targets.length
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1588
1644
|
case 'snapshot.compact': {
|
|
1589
1645
|
const session = requireSession(args.tabId)
|
|
1590
1646
|
const { result } = await cdp('Runtime.evaluate', {
|
package/lib/index.js
CHANGED
|
@@ -263,7 +263,7 @@ export function apply(ctx, config) {
|
|
|
263
263
|
'ПРАВИЛО: подключать вкладку, которую пользователь не подключал сам, можно только с его явного ' +
|
|
264
264
|
'разрешения. Без confirmed:true вызов claim отклоняется — сначала спросите пользователя.',
|
|
265
265
|
{
|
|
266
|
-
action: { type: 'string', description: 'list | claim | use | release | open | close | focus' },
|
|
266
|
+
action: { type: 'string', description: 'list | claim | use | release | open | close | focus | diagnose' },
|
|
267
267
|
tabId: { type: 'number', description: 'id вкладки для claim / use / release / focus / close (из action=list)' },
|
|
268
268
|
url: { type: 'string', description: 'адрес для action=open (по умолчанию about:blank)' },
|
|
269
269
|
confirmed: {
|
|
@@ -306,6 +306,12 @@ export function apply(ctx, config) {
|
|
|
306
306
|
await instance.enqueue('tab.focus', { tabId }, { timeout: 20000 })
|
|
307
307
|
return ok(`Вкладка ${tabId} на переднем плане.`, { tabId })
|
|
308
308
|
}
|
|
309
|
+
if (action === 'diagnose') {
|
|
310
|
+
const tabId = Number(args.tabId ?? instance.state().activeTabId)
|
|
311
|
+
if (!Number.isFinite(tabId)) throw new Error('нужен tabId')
|
|
312
|
+
const info = await instance.enqueue('tab.diagnose', { tabId }, { timeout: 20000 })
|
|
313
|
+
return ok(JSON.stringify(info, null, 2), { diagnose: info })
|
|
314
|
+
}
|
|
309
315
|
if (action === 'claim' || action === 'use' || action === 'release') {
|
|
310
316
|
const tabId = Number(args.tabId)
|
|
311
317
|
if (!Number.isFinite(tabId)) throw new Error(`tabId is required for action=${action}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-site-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Chrome/Edge MV3 bridge for DeepSeek Harness: capture page markup (DOM, styles, selectors) and BFF traffic (xhr/fetch with headers and bodies) into a workspace snapshot bundle, drive the page with trusted CDP input, and verify injected scripts. Authorization never leaves the browser and is never written to disk.",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"type": "module",
|