dsh-site-bridge 0.5.3 → 0.6.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/extension/manifest.json +1 -1
- package/extension/popup.html +20 -9
- package/extension/popup.js +54 -34
- package/extension/sw.js +10 -3
- package/package.json +1 -1
- package/tools/preview-popup.mjs +11 -0
package/extension/manifest.json
CHANGED
package/extension/popup.html
CHANGED
|
@@ -94,6 +94,23 @@
|
|
|
94
94
|
button.link:hover:not(:disabled) { color: #7e98ff; background: none; }
|
|
95
95
|
|
|
96
96
|
.target-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
|
97
|
+
.tabs-list {
|
|
98
|
+
display: flex; flex-direction: column; gap: 6px; max-height: 150px; overflow-y: auto;
|
|
99
|
+
margin-top: 8px; scrollbar-width: thin; scrollbar-color: #3a4150 transparent;
|
|
100
|
+
}
|
|
101
|
+
.tabs-list::-webkit-scrollbar { width: 6px; }
|
|
102
|
+
.tabs-list::-webkit-scrollbar-thumb { background: #3a4150; border-radius: 3px; }
|
|
103
|
+
.tabs-list::-webkit-scrollbar-track { background: transparent; }
|
|
104
|
+
.tab-item {
|
|
105
|
+
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
106
|
+
padding: 6px 8px; background: #15181e; border: 1px solid var(--line);
|
|
107
|
+
border-radius: 7px; min-height: 44px;
|
|
108
|
+
}
|
|
109
|
+
.tab-meta { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); font-size: 10.5px; flex: none; }
|
|
110
|
+
.tab-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); }
|
|
111
|
+
.tab-item-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
|
112
|
+
.tab-item-title { font-size: 12px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
113
|
+
.tab-item-url { color: var(--muted); font-size: 10.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
97
114
|
.capture-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); }
|
|
98
115
|
.capture-row button { padding: 5px 9px; font-size: 11.5px; }
|
|
99
116
|
select {
|
|
@@ -148,12 +165,9 @@
|
|
|
148
165
|
|
|
149
166
|
<div class="target empty" id="target-block">
|
|
150
167
|
<div class="target-text">
|
|
151
|
-
<div class="target-title"
|
|
152
|
-
<div class="target-url"
|
|
168
|
+
<div class="target-title" data-i18n="targetNoneTitle">Вкладка не подключена</div>
|
|
169
|
+
<div class="target-url" data-i18n="targetNoneUrl">Откройте нужную страницу и подключите её</div>
|
|
153
170
|
</div>
|
|
154
|
-
<button class="icon" id="release" hidden data-i18n-title="disconnectTab" title="Отключить вкладку">
|
|
155
|
-
<svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2 L10 10 M10 2 L2 10" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
|
|
156
|
-
</button>
|
|
157
171
|
</div>
|
|
158
172
|
|
|
159
173
|
<button class="primary" id="claim" data-i18n="connectTab">Подключить эту вкладку</button>
|
|
@@ -161,10 +175,7 @@
|
|
|
161
175
|
|
|
162
176
|
<button class="primary" id="claim-more" hidden data-i18n="attachAnother">Подключить ещё вкладку</button>
|
|
163
177
|
|
|
164
|
-
<div class="
|
|
165
|
-
<span class="muted" data-i18n="tabsLabel">Вкладки</span>
|
|
166
|
-
<select id="tabs-select"></select>
|
|
167
|
-
</div>
|
|
178
|
+
<div class="tabs-list" id="tabs-list" hidden></div>
|
|
168
179
|
|
|
169
180
|
<div class="capture-row">
|
|
170
181
|
<span class="muted" id="captured">0</span>
|
package/extension/popup.js
CHANGED
|
@@ -59,7 +59,6 @@ function render(status) {
|
|
|
59
59
|
const dot = $('dot')
|
|
60
60
|
const text = $('connection-text')
|
|
61
61
|
const claim = $('claim')
|
|
62
|
-
const release = $('release')
|
|
63
62
|
const capture = $('capture')
|
|
64
63
|
const target = $('target-block')
|
|
65
64
|
|
|
@@ -93,35 +92,56 @@ function render(status) {
|
|
|
93
92
|
text.textContent = t('statusNotConfigured')
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
$('target-url').textContent = hasTarget ? String(status.target.url || '') : t('targetNoneUrl')
|
|
99
|
-
|
|
100
|
-
// Several attached tabs: a compact switcher, nothing else changes.
|
|
95
|
+
// One block per attached tab: title, address and its own detach cross. The
|
|
96
|
+
// list scrolls after three blocks so the popup keeps its height.
|
|
101
97
|
const targets = Array.isArray(status.targets) ? status.targets : []
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
$('
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
target.classList.toggle('empty', targets.length === 0)
|
|
99
|
+
target.hidden = targets.length > 0
|
|
100
|
+
|
|
101
|
+
const list = $('tabs-list')
|
|
102
|
+
list.hidden = targets.length === 0
|
|
103
|
+
list.textContent = ''
|
|
104
|
+
for (const item of targets) {
|
|
105
|
+
const row = document.createElement('div')
|
|
106
|
+
row.className = 'tab-item'
|
|
107
|
+
row.dataset.id = String(item.id)
|
|
108
|
+
row.title = item.url || ''
|
|
109
|
+
|
|
110
|
+
const body = document.createElement('div')
|
|
111
|
+
body.className = 'tab-item-text'
|
|
112
|
+
const title = document.createElement('div')
|
|
113
|
+
title.className = 'tab-item-title'
|
|
114
|
+
title.textContent = item.title || item.url || `#${item.id}`
|
|
115
|
+
const address = document.createElement('div')
|
|
116
|
+
address.className = 'tab-item-url'
|
|
117
|
+
address.textContent = item.url || ''
|
|
118
|
+
body.append(title, address)
|
|
119
|
+
|
|
120
|
+
const remove = document.createElement('button')
|
|
121
|
+
remove.className = 'icon'
|
|
122
|
+
remove.dataset.remove = String(item.id)
|
|
123
|
+
remove.title = t('disconnectTab')
|
|
124
|
+
remove.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2 L10 10 M10 2 L2 10" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>'
|
|
125
|
+
|
|
126
|
+
// Every tab carries its own numbers: nothing here is "the" tab.
|
|
127
|
+
const meta = document.createElement('span')
|
|
128
|
+
meta.className = 'tab-meta'
|
|
129
|
+
if (item.recording) {
|
|
130
|
+
const dot = document.createElement('i')
|
|
131
|
+
dot.className = 'tab-dot'
|
|
132
|
+
meta.append(dot)
|
|
115
133
|
}
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
meta.append(document.createTextNode(String(item.captured ?? 0)))
|
|
135
|
+
row.append(body, meta, remove)
|
|
136
|
+
list.append(row)
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
// Nothing to claim once a tab is bound: the action would be a no-op.
|
|
121
|
-
|
|
122
|
-
claim.
|
|
123
|
-
claim.
|
|
124
|
-
|
|
140
|
+
const selfAttached = selfTabId != null && targets.some((item) => item.id === selfTabId)
|
|
141
|
+
claim.hidden = targets.length > 0
|
|
142
|
+
claim.disabled = !connected || targets.length > 0
|
|
143
|
+
claim.textContent = t('connectTab')
|
|
144
|
+
$('claim-more').hidden = targets.length === 0 || selfAttached
|
|
125
145
|
|
|
126
146
|
const pick = $('pick')
|
|
127
147
|
pick.classList.toggle('active', status.picking === true)
|
|
@@ -273,9 +293,14 @@ $('capture').addEventListener('click', async () => {
|
|
|
273
293
|
await refresh()
|
|
274
294
|
})
|
|
275
295
|
|
|
276
|
-
$('
|
|
277
|
-
|
|
278
|
-
|
|
296
|
+
$('tabs-list').addEventListener('click', async (event) => {
|
|
297
|
+
const remove = event.target.closest('[data-remove]')
|
|
298
|
+
if (remove) {
|
|
299
|
+
event.stopPropagation()
|
|
300
|
+
await send({ type: 'release', tabId: Number(remove.dataset.remove) })
|
|
301
|
+
await refresh()
|
|
302
|
+
return
|
|
303
|
+
}
|
|
279
304
|
})
|
|
280
305
|
|
|
281
306
|
$('pick').addEventListener('click', async () => {
|
|
@@ -296,11 +321,6 @@ $('pick').addEventListener('click', async () => {
|
|
|
296
321
|
|
|
297
322
|
$('claim-more').addEventListener('click', attachCurrentTab)
|
|
298
323
|
|
|
299
|
-
|
|
300
|
-
const tabId = Number(event.target.value)
|
|
301
|
-
if (!Number.isFinite(tabId)) return
|
|
302
|
-
await send({ type: 'use', tabId })
|
|
303
|
-
await refresh()
|
|
304
|
-
})
|
|
324
|
+
|
|
305
325
|
|
|
306
326
|
void init()
|
package/extension/sw.js
CHANGED
|
@@ -193,8 +193,10 @@ async function handleMessageInner(message) {
|
|
|
193
193
|
recording: session.recording,
|
|
194
194
|
captured: session.entries.length
|
|
195
195
|
})),
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
// Aggregates: the popup shows every attached tab, so "capture on" means
|
|
197
|
+
// "at least one is recording" and the counter is the sum.
|
|
198
|
+
recording: [...state.sessions.values()].some((session) => session.recording),
|
|
199
|
+
captured: [...state.sessions.values()].reduce((sum, session) => sum + session.entries.length, 0),
|
|
198
200
|
picking: active?.picking === true,
|
|
199
201
|
picks: active?.picks?.length ?? 0
|
|
200
202
|
}
|
|
@@ -216,8 +218,13 @@ async function handleMessageInner(message) {
|
|
|
216
218
|
return { ok: true }
|
|
217
219
|
}
|
|
218
220
|
if (message?.type === 'toggle-capture') {
|
|
221
|
+
const on = message.on === true
|
|
222
|
+
if (message.tabId == null || message.tabId === '') {
|
|
223
|
+
for (const session of state.sessions.values()) await setRecording(session, on)
|
|
224
|
+
return { ok: true, recording: on, tabs: state.sessions.size }
|
|
225
|
+
}
|
|
219
226
|
const session = requireSession(message.tabId)
|
|
220
|
-
return { ok: true, recording: await setRecording(session,
|
|
227
|
+
return { ok: true, recording: await setRecording(session, on) }
|
|
221
228
|
}
|
|
222
229
|
return { ok: false, error: `unknown message ${message?.type}` }
|
|
223
230
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-site-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
package/tools/preview-popup.mjs
CHANGED
|
@@ -21,6 +21,17 @@ const chromePath = process.env.CHROME_PATH ?? 'C:\\Program Files\\Google\\Chrome
|
|
|
21
21
|
const sleep = (ms) => new Promise((done) => setTimeout(done, ms))
|
|
22
22
|
|
|
23
23
|
const statuses = {
|
|
24
|
+
many: {
|
|
25
|
+
ok: true, configured: true, connected: true, port: 47321, recording: true, captured: 7, selfTabId: 9,
|
|
26
|
+
activeTabId: 1,
|
|
27
|
+
target: { id: 1, title: 'Example Store — Products', url: 'https://shop.example/products' },
|
|
28
|
+
targets: [
|
|
29
|
+
{ id: 1, title: 'Example Store — Products', url: 'https://shop.example/products', recording: true, captured: 7 },
|
|
30
|
+
{ id: 2, title: 'Example Store — Cart', url: 'https://shop.example/cart', recording: false, captured: 0 },
|
|
31
|
+
{ id: 3, title: 'Example Store — Checkout', url: 'https://shop.example/checkout', recording: false, captured: 0 },
|
|
32
|
+
{ id: 4, title: 'Example Store — Orders', url: 'https://shop.example/orders', recording: false, captured: 0 }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
24
35
|
'connected-one': {
|
|
25
36
|
ok: true, configured: true, connected: true, port: 47321, recording: false, captured: 0, selfTabId: 1,
|
|
26
37
|
activeTabId: 1,
|