dsh-site-bridge 0.7.0 → 0.7.4
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/extension/_locales/en/messages.json +3 -0
- package/extension/_locales/ru/messages.json +3 -0
- package/extension/_locales/zh_CN/messages.json +3 -0
- package/extension/manifest.json +1 -1
- package/extension/popup.js +49 -4
- package/extension/sw.js +39 -13
- package/lib/index.js +4 -4
- package/package.json +1 -1
package/extension/manifest.json
CHANGED
package/extension/popup.js
CHANGED
|
@@ -138,10 +138,17 @@ function render(status) {
|
|
|
138
138
|
|
|
139
139
|
// Nothing to claim once a tab is bound: the action would be a no-op.
|
|
140
140
|
const selfAttached = selfTabId != null && targets.some((item) => item.id === selfTabId)
|
|
141
|
+
// Service pages (chrome://, other extensions, the Web Store) cannot be driven;
|
|
142
|
+
// do not offer a button that Chrome will refuse.
|
|
143
|
+
const claimable = selfTabUrl == null || /^(https?|file):/.test(selfTabUrl)
|
|
141
144
|
claim.hidden = targets.length > 0
|
|
142
|
-
claim.disabled = !connected || targets.length > 0
|
|
145
|
+
claim.disabled = !connected || !claimable || targets.length > 0
|
|
146
|
+
claim.title = claimable ? '' : t('notClaimable')
|
|
143
147
|
claim.textContent = t('connectTab')
|
|
144
|
-
$('claim-more')
|
|
148
|
+
const claimMore = $('claim-more')
|
|
149
|
+
claimMore.hidden = targets.length === 0 || selfAttached
|
|
150
|
+
claimMore.disabled = !connected || !claimable
|
|
151
|
+
claimMore.title = claimable ? t('attachAnother') : t('notClaimable')
|
|
145
152
|
|
|
146
153
|
const pick = $('pick')
|
|
147
154
|
pick.classList.toggle('active', status.picking === true)
|
|
@@ -170,12 +177,14 @@ function render(status) {
|
|
|
170
177
|
// Which tab this popup is open on: attaching that tab again is a no-op, so the
|
|
171
178
|
// attach button is only offered when the current tab is not attached yet.
|
|
172
179
|
let selfTabId = null
|
|
180
|
+
let selfTabUrl = null
|
|
173
181
|
|
|
174
182
|
async function refresh() {
|
|
175
183
|
try {
|
|
176
184
|
if (chrome.tabs?.query) {
|
|
177
|
-
const
|
|
185
|
+
const tab = await currentBrowserTab()
|
|
178
186
|
selfTabId = tab?.id ?? null
|
|
187
|
+
selfTabUrl = tab?.url ?? null
|
|
179
188
|
}
|
|
180
189
|
render(await send({ type: 'status' }))
|
|
181
190
|
} catch {
|
|
@@ -183,6 +192,42 @@ async function refresh() {
|
|
|
183
192
|
}
|
|
184
193
|
}
|
|
185
194
|
|
|
195
|
+
/**
|
|
196
|
+
* The browser tab this popup belongs to. `currentWindow` from a popup can point
|
|
197
|
+
* at the popup's own extension page, and Chrome refuses to read other
|
|
198
|
+
* extensions' pages, so prefer a real http(s)/file tab from the focused window.
|
|
199
|
+
*/
|
|
200
|
+
async function currentBrowserTab() {
|
|
201
|
+
const scheme = (tab) => (/^(https?|file):/.test(String(tab?.url ?? '')) ? 'web' : 'other')
|
|
202
|
+
const scopes = [
|
|
203
|
+
{ active: true, lastFocusedWindow: true, windowType: 'normal' },
|
|
204
|
+
{ active: true, windowType: 'normal' },
|
|
205
|
+
{ active: true, lastFocusedWindow: true },
|
|
206
|
+
{ active: true, currentWindow: true }
|
|
207
|
+
]
|
|
208
|
+
for (const scope of scopes) {
|
|
209
|
+
let tabs = []
|
|
210
|
+
try {
|
|
211
|
+
tabs = await chrome.tabs.query(scope)
|
|
212
|
+
} catch {
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
if (!Array.isArray(tabs) || tabs.length === 0) continue
|
|
216
|
+
const ordered = [...tabs.filter((tab) => scheme(tab) === 'web'), ...tabs.filter((tab) => scheme(tab) !== 'web')]
|
|
217
|
+
for (const tab of ordered) {
|
|
218
|
+
// Ask Chrome whether this tab is readable at all: tabs in app windows of
|
|
219
|
+
// other extensions are listed but cannot be touched.
|
|
220
|
+
try {
|
|
221
|
+
await chrome.tabs.get(tab.id)
|
|
222
|
+
return tab
|
|
223
|
+
} catch {
|
|
224
|
+
// not ours to read — try the next candidate
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return null
|
|
229
|
+
}
|
|
230
|
+
|
|
186
231
|
/* Autosave: a complete pair is stored as soon as it appears, no button. */
|
|
187
232
|
let saveTimer = null
|
|
188
233
|
|
|
@@ -262,7 +307,7 @@ $('import-file').addEventListener('change', async (event) => {
|
|
|
262
307
|
async function attachCurrentTab() {
|
|
263
308
|
let tabId = null
|
|
264
309
|
try {
|
|
265
|
-
const
|
|
310
|
+
const tab = await currentBrowserTab()
|
|
266
311
|
tabId = tab?.id ?? null
|
|
267
312
|
} catch {
|
|
268
313
|
tabId = null
|
package/extension/sw.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* host, which redacts and never writes it to disk.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
const EXT_VERSION =
|
|
18
|
+
const EXT_VERSION = chrome.runtime.getManifest().version
|
|
19
19
|
const NET_CAP = 600
|
|
20
20
|
// Bodies are kept up to this size in the worker, then marked as truncated.
|
|
21
21
|
// Raise it per machine with chrome.storage.local.set({ maxBodyChars }): the
|
|
@@ -284,13 +284,30 @@ async function claimTab(tabId, attachedBy = 'agent') {
|
|
|
284
284
|
// caller that only knows "this tab" cannot silently pass NaN.
|
|
285
285
|
let id = Number(tabId)
|
|
286
286
|
if (!Number.isFinite(id)) {
|
|
287
|
-
const
|
|
287
|
+
const candidates = await chrome.tabs
|
|
288
|
+
.query({ active: true, lastFocusedWindow: true, windowType: 'normal' })
|
|
289
|
+
.catch(() => [])
|
|
290
|
+
const active = candidates.find((tab) => /^(https?|file):/.test(tab.url ?? '')) ?? candidates[0]
|
|
288
291
|
id = active?.id
|
|
289
292
|
}
|
|
290
293
|
if (!Number.isFinite(id)) throw new Error('no tab to claim: pass tabId or open a page first')
|
|
291
|
-
|
|
294
|
+
let tab = null
|
|
295
|
+
try {
|
|
296
|
+
tab = await chrome.tabs.get(id)
|
|
297
|
+
} catch (error) {
|
|
298
|
+
// Chrome refuses to read pages of other extensions and its own service
|
|
299
|
+
// pages; say that in words a person can act on.
|
|
300
|
+
const detail = String(error?.message ?? error)
|
|
301
|
+
const hint = /chrome-extension:\/\//.test(detail)
|
|
302
|
+
? ' Похоже, вкладка открыта в окне-приложении другого расширения — откройте её как обычную вкладку.'
|
|
303
|
+
: ''
|
|
304
|
+
throw new Error(`эту вкладку нельзя подключить: ${detail}${hint}`)
|
|
305
|
+
}
|
|
292
306
|
if (!/^https?:|^file:/.test(tab.url ?? '')) {
|
|
293
|
-
|
|
307
|
+
const scheme = String(tab.url ?? '').split(':')[0] || 'неизвестная схема'
|
|
308
|
+
throw new Error(
|
|
309
|
+
`эту страницу нельзя подключить (${scheme}:): Chrome не даёт доступ к служебным страницам и страницам других расширений. Откройте обычный сайт http(s) или file://`
|
|
310
|
+
)
|
|
294
311
|
}
|
|
295
312
|
return attach(id, attachedBy)
|
|
296
313
|
}
|
|
@@ -1489,17 +1506,26 @@ async function dispatch(op, args) {
|
|
|
1489
1506
|
return { ok: true, version: EXT_VERSION }
|
|
1490
1507
|
case 'tabs.list': {
|
|
1491
1508
|
const tabs = await chrome.tabs.query({})
|
|
1509
|
+
// Window type explains why a tab can be listed but not read: app windows
|
|
1510
|
+
// belong to another extension, and Chrome refuses access to them.
|
|
1511
|
+
const windows = await chrome.windows.getAll({}).catch(() => [])
|
|
1512
|
+
const windowType = new Map(windows.map((window) => [window.id, window.type ?? 'normal']))
|
|
1492
1513
|
const list = tabs
|
|
1493
1514
|
.filter((tab) => /^https?:|^file:/.test(tab.url ?? ''))
|
|
1494
|
-
.map((tab) =>
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1515
|
+
.map((tab) => {
|
|
1516
|
+
const type = windowType.get(tab.windowId) ?? 'unknown'
|
|
1517
|
+
return {
|
|
1518
|
+
id: tab.id,
|
|
1519
|
+
title: tab.title,
|
|
1520
|
+
url: tab.url,
|
|
1521
|
+
active: tab.active,
|
|
1522
|
+
windowId: tab.windowId,
|
|
1523
|
+
windowType: type,
|
|
1524
|
+
readable: type === 'normal',
|
|
1525
|
+
attached: state.sessions.has(tab.id),
|
|
1526
|
+
target: tab.id === state.activeTabId
|
|
1527
|
+
}
|
|
1528
|
+
})
|
|
1503
1529
|
await evt('tabs', { tabs: list })
|
|
1504
1530
|
return { tabs: list }
|
|
1505
1531
|
}
|
package/lib/index.js
CHANGED
|
@@ -332,10 +332,10 @@ export function apply(ctx, config) {
|
|
|
332
332
|
}
|
|
333
333
|
const result = await instance.enqueue('tabs.list', {}, { timeout: 20000 })
|
|
334
334
|
const tabs = Array.isArray(result?.tabs) ? result.tabs : []
|
|
335
|
-
const lines = tabs.map(
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
)
|
|
335
|
+
const lines = tabs.map((tab) => {
|
|
336
|
+
const window = tab.windowType && tab.windowType !== 'normal' ? ` [окно: ${tab.windowType}, недоступно]` : ''
|
|
337
|
+
return `${tab.target ? '*' : tab.attached ? '+' : ' '} [${tab.id}] ${tab.title || '(без названия)'} — ${tab.url}${window}`
|
|
338
|
+
})
|
|
339
339
|
return ok(
|
|
340
340
|
tabs.length === 0
|
|
341
341
|
? 'Нет доступных вкладок (нужны http(s)/file://).'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-site-bridge",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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",
|