dsh-site-bridge 0.8.0 → 0.8.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/docs/limitations.md +2 -1
- package/extension/manifest.json +3 -2
- package/extension/sw.js +35 -6
- 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` (отказ) и на странице поиска Google (зависание), при этом `tabs.get` вкладку отдаёт, а `chrome.debugger.getTargets` показывает обычный `page` с https-адресом. В чистом профиле с одним нашим расширением те же адреса (`accounts.google.com`, `google.com/sorry`) подключаются без ошибок — значит на вкладке есть фрейм другого расширения либо отладку перехватывает стороннее ПО. Лечится профилем без посторонних расширений |
|
|
12
13
|
|
|
13
14
|
Ниже — исходный разбор каждого ограничения; для снятых пунктов оставлено описание того, что
|
|
14
15
|
именно было изменено и какие границы остались.
|
package/extension/manifest.json
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"default_locale": "en",
|
|
4
4
|
"name": "__MSG_appName__",
|
|
5
|
-
"version": "0.8.
|
|
5
|
+
"version": "0.8.2",
|
|
6
6
|
"description": "__MSG_appDesc__",
|
|
7
7
|
"minimum_chrome_version": "116",
|
|
8
8
|
"permissions": [
|
|
9
9
|
"debugger",
|
|
10
10
|
"tabs",
|
|
11
|
-
"storage"
|
|
11
|
+
"storage",
|
|
12
|
+
"webNavigation"
|
|
12
13
|
],
|
|
13
14
|
"host_permissions": [
|
|
14
15
|
"http://127.0.0.1/*",
|
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) => ({
|
|
@@ -1592,6 +1613,7 @@ async function dispatch(op, args) {
|
|
|
1592
1613
|
const windows = await chrome.windows.getAll({}).catch(() => [])
|
|
1593
1614
|
const window = windows.find((item) => item.id === info?.windowId) ?? null
|
|
1594
1615
|
const targets = await chrome.debugger.getTargets().catch(() => [])
|
|
1616
|
+
const frames = await chrome.webNavigation.getAllFrames({ tabId: id }).catch(() => null)
|
|
1595
1617
|
const mine = targets
|
|
1596
1618
|
.filter((target) => target.tabId === id)
|
|
1597
1619
|
.map((target) => ({
|
|
@@ -1617,6 +1639,13 @@ async function dispatch(op, args) {
|
|
|
1617
1639
|
: { error: info?.error },
|
|
1618
1640
|
window: window ? { id: window.id, type: window.type, state: window.state } : null,
|
|
1619
1641
|
targets: mine,
|
|
1642
|
+
frames: Array.isArray(frames)
|
|
1643
|
+
? frames.map((frame) => ({
|
|
1644
|
+
frameId: frame.frameId,
|
|
1645
|
+
parentFrameId: frame.parentFrameId,
|
|
1646
|
+
url: String(frame.url ?? '').slice(0, 200)
|
|
1647
|
+
}))
|
|
1648
|
+
: null,
|
|
1620
1649
|
targetCount: targets.length
|
|
1621
1650
|
}
|
|
1622
1651
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-site-bridge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
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",
|