@mefistofelix/cdp.js 0.3.0 → 0.4.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.
- package/README.md +78 -0
- package/cdp.js +54 -0
- package/cdp_ext/cdp_ext.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ In Bun, use `bun add @mefistofelix/cdp.js` and the ordinary package import above
|
|
|
37
37
|
- Browser proxies and named direct-mode contexts with independent proxies/storage
|
|
38
38
|
and automatic cleanup after their last target closes.
|
|
39
39
|
- Incognito targets in direct and reverse modes.
|
|
40
|
+
- Named, colored tab groups through the reverse Chrome extension.
|
|
40
41
|
- Attachment through WebSocket, HTTP discovery or a debugging port.
|
|
41
42
|
- Optional Chrome extension reverse connections through one native Deno/Bun server,
|
|
42
43
|
without browser remote-debugging arguments.
|
|
@@ -1273,6 +1274,7 @@ await client.call({
|
|
|
1273
1274
|
| `targetId` | `null` | Optional existing physical target ID. Reused if discovered; otherwise create a new target. |
|
|
1274
1275
|
| `context` | unset | Direct-mode context name or first-use configuration object; see named contexts above. Reverse mode rejects this option. |
|
|
1275
1276
|
| `incognito` | `false` | Create/select a private target; direct mode uses the shared `'incognito'` context by default, reverse uses a native incognito window with the user's extension grant. |
|
|
1277
|
+
| `group` | unset | Reverse-only tab group name or first-use `{ name, color }` configuration. False skips grouping. |
|
|
1276
1278
|
| `create_params` | `{}` | Native creation overrides, merged over `url: 'about:blank'` and `background: true`; start from a blank target without requesting foreground activation. |
|
|
1277
1279
|
| `initialize` | `true` | Run the convenience setup sequence; false skips it. |
|
|
1278
1280
|
| `runtime` | `'bootstrap'` | Enable Runtime for setup, then disable reporting. `true` leaves it enabled; `false` skips both operations. |
|
|
@@ -1283,6 +1285,78 @@ await client.call({
|
|
|
1283
1285
|
| `binding` | `'_send_to_cdp'` | Default page-to-host JavaScript binding. String changes its name; false skips installation; true selects the default name. |
|
|
1284
1286
|
| `background_service` | `true` | Clear, observe and record `pushMessaging` background-service events in Chromium. |
|
|
1285
1287
|
|
|
1288
|
+
### Named tab groups
|
|
1289
|
+
|
|
1290
|
+
With `cdp_ext: true` or external `reverse: true`, set `target.group` when first
|
|
1291
|
+
configuring a target:
|
|
1292
|
+
|
|
1293
|
+
```js
|
|
1294
|
+
await client.call({
|
|
1295
|
+
method: 'Page.navigate',
|
|
1296
|
+
params: {
|
|
1297
|
+
browser: {
|
|
1298
|
+
name: 'work',
|
|
1299
|
+
cdp_ext: true,
|
|
1300
|
+
},
|
|
1301
|
+
target: {
|
|
1302
|
+
name: 'search',
|
|
1303
|
+
group: {
|
|
1304
|
+
name: 'Research',
|
|
1305
|
+
color: 'blue',
|
|
1306
|
+
},
|
|
1307
|
+
},
|
|
1308
|
+
url: 'https://example.com/',
|
|
1309
|
+
},
|
|
1310
|
+
})
|
|
1311
|
+
await client.call({
|
|
1312
|
+
method: 'Page.navigate',
|
|
1313
|
+
params: {
|
|
1314
|
+
browser: 'work',
|
|
1315
|
+
target: {
|
|
1316
|
+
name: 'notes',
|
|
1317
|
+
group: 'Research',
|
|
1318
|
+
},
|
|
1319
|
+
url: 'https://example.com/notes',
|
|
1320
|
+
},
|
|
1321
|
+
})
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
| Group option | Default | Meaning |
|
|
1325
|
+
| --- | --- | --- |
|
|
1326
|
+
| `name` | required in an object | Logical name in `record.groups`; also the visible group title. A string reference creates a default group if this name is new. |
|
|
1327
|
+
| `color` | unset | Chrome's native color selection for new groups; otherwise retain the current color. Explicit values: `grey`, `blue`, `red`, `yellow`, `green`, `pink`, `purple`, `cyan`, `orange`. |
|
|
1328
|
+
|
|
1329
|
+
The first use of a group name records its configuration. Concurrent target
|
|
1330
|
+
creation shares that definition and serializes group operations. Names are scoped
|
|
1331
|
+
to the logical browser. Each window has its own native group for that name:
|
|
1332
|
+
tabs stay in their current window, including separate regular/incognito windows.
|
|
1333
|
+
Groups are created by the library, not adopted from unrelated groups that happen
|
|
1334
|
+
to have the same visible title. Incognito still requires the bridge's user grant.
|
|
1335
|
+
|
|
1336
|
+
Grouping happens before optional CDP setup, including with `initialize: false`.
|
|
1337
|
+
It also works with `targetId` selecting an existing tab. As with other target
|
|
1338
|
+
options, passing a new group on a target label that was already configured does
|
|
1339
|
+
not reconfigure that label. Use another label with its physical `targetId`, or
|
|
1340
|
+
edit the public record and reinitialize the target.
|
|
1341
|
+
|
|
1342
|
+
Reconnection reapplies the name/color and rejoins a surviving group. If its last
|
|
1343
|
+
tab was closed or ungrouped, Chrome removes the native group; later target setup
|
|
1344
|
+
creates a new one. `record.groups[name].window_groups[windowId]` stores the last
|
|
1345
|
+
known native group ID. These IDs are checked against Chrome during setup and can
|
|
1346
|
+
remain stale between operations; no extra event subscriptions or persistence are
|
|
1347
|
+
added. In reverse target info, `groupId` is the current native ID, or `-1` when
|
|
1348
|
+
ungrouped. Generic `chrome.tabs` / `chrome.tabGroups` calls remain available for
|
|
1349
|
+
immediate native updates. Native errors, including invalid colors, reject setup
|
|
1350
|
+
with the usual RPC error context and can be retried after correcting the record.
|
|
1351
|
+
|
|
1352
|
+
Direct CDP does not expose browser tab-group commands. A non-false `group` option
|
|
1353
|
+
therefore rejects before connecting or launching in direct mode. The bridge's
|
|
1354
|
+
existing `tabGroups` permission is sufficient; no extra permission is added.
|
|
1355
|
+
See [Chrome tab groups](https://developer.chrome.com/docs/extensions/reference/api/tabGroups)
|
|
1356
|
+
and [grouping tabs](https://developer.chrome.com/docs/extensions/reference/api/tabs#method-group).
|
|
1357
|
+
|
|
1358
|
+
### Target initialization
|
|
1359
|
+
|
|
1286
1360
|
Domain options set to false skip setup commands; they do not actively disable a
|
|
1287
1361
|
domain already enabled by another caller. Likewise, `binding: false` does not
|
|
1288
1362
|
remove a previously installed binding. Use native CDP commands for immediate
|
|
@@ -1554,6 +1628,7 @@ dictionary containers have a null prototype.
|
|
|
1554
1628
|
| `record.targets[label]` | Target options, physical `targetId`, `sessionId`, `connecting` and `setup_errors` after setup. |
|
|
1555
1629
|
| `record.contexts[name]` | Recorded context options, native `browserContextId` or null, and pending `connecting`/`closing` promises. Direct mode only. |
|
|
1556
1630
|
| `record.context_checking` | Pending automatic empty-context cleanup, otherwise unset/null. |
|
|
1631
|
+
| `record.groups[name]` | Recorded tab-group options, per-window native IDs in `window_groups`, and a pending `connecting` operation. Reverse only. |
|
|
1557
1632
|
| `record.target_info[targetId]` | Discovered native target info plus cached `sessionId` when attached. |
|
|
1558
1633
|
| `record.session_targets[sessionId]` | Physical `targetId` for a session. |
|
|
1559
1634
|
| `socket.pending[id]` | Response-wait promise, removed on completion. |
|
|
@@ -1641,6 +1716,7 @@ labels, target labels, custom methods or reconnection policy in this layer.
|
|
|
1641
1716
|
| Failed discovery or local launch | Reject the call establishing the connection. |
|
|
1642
1717
|
| Named context in reverse mode | Reject explicitly; no separate process emulation. |
|
|
1643
1718
|
| Reverse incognito without the extension's user grant | Reject with an instruction to enable Allow in incognito. |
|
|
1719
|
+
| Tab group in direct mode | Reject before connecting; tab groups require the reverse extension APIs. |
|
|
1644
1720
|
| Automatic context cleanup fails | Emit the manager's `error` event with browser label and original error. |
|
|
1645
1721
|
|
|
1646
1722
|
```js
|
|
@@ -1688,6 +1764,8 @@ The shared Deno/Bun reverse suite loads the real extension into two headless Chr
|
|
|
1688
1764
|
server port without remote-debugging arguments. It checks native/custom calls,
|
|
1689
1765
|
Network events, webRequest notifications without debugger attachment, bindings,
|
|
1690
1766
|
child sessions, reconnect, detach/recreate, incognito and isolation.
|
|
1767
|
+
Tab-group checks cover concurrent creation, existing tabs, title/color, per-window
|
|
1768
|
+
incognito separation, reconnection, recreation and native validation/retry.
|
|
1691
1769
|
Node coverage also checks actual browser/context proxy routing, shared private
|
|
1692
1770
|
storage, concurrent context creation, automatic disposal, unselected targets and
|
|
1693
1771
|
context ownership in externally attached browsers.
|
package/cdp.js
CHANGED
|
@@ -684,9 +684,58 @@ export class cdp extends EventTarget {
|
|
|
684
684
|
}
|
|
685
685
|
}
|
|
686
686
|
|
|
687
|
+
async _group(browser, target) {
|
|
688
|
+
const socket = browser.socket
|
|
689
|
+
const group = browser.groups[target.group]
|
|
690
|
+
while (group.connecting) await group.connecting
|
|
691
|
+
return group.connecting = (async () => {
|
|
692
|
+
const tab = await socket.req({
|
|
693
|
+
method: 'chrome.tabs.get',
|
|
694
|
+
params: { args: [Number(target.targetId)] },
|
|
695
|
+
})
|
|
696
|
+
const groups = await socket.req({
|
|
697
|
+
method: 'chrome.tabGroups.query',
|
|
698
|
+
params: { args: [{ windowId: tab.windowId }] },
|
|
699
|
+
})
|
|
700
|
+
const groupId = group.window_groups[tab.windowId]
|
|
701
|
+
const existing = groups.some(value => value.id === groupId)
|
|
702
|
+
const id = await socket.req({
|
|
703
|
+
method: 'chrome.tabs.group',
|
|
704
|
+
params: {
|
|
705
|
+
args: [{
|
|
706
|
+
tabIds: tab.id,
|
|
707
|
+
groupId: existing ? groupId : undefined,
|
|
708
|
+
createProperties: existing ? undefined : { windowId: tab.windowId },
|
|
709
|
+
}],
|
|
710
|
+
},
|
|
711
|
+
})
|
|
712
|
+
group.window_groups[tab.windowId] = id
|
|
713
|
+
await socket.req({
|
|
714
|
+
method: 'chrome.tabGroups.update',
|
|
715
|
+
params: {
|
|
716
|
+
args: [id, {
|
|
717
|
+
title: group.name,
|
|
718
|
+
color: group.color,
|
|
719
|
+
}],
|
|
720
|
+
},
|
|
721
|
+
})
|
|
722
|
+
return id
|
|
723
|
+
})().finally(() => {
|
|
724
|
+
group.connecting = null
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
|
|
687
728
|
async _target(browser, target) {
|
|
688
729
|
const socket = browser.socket
|
|
689
730
|
const reverse = browser.reverse || browser.cdp_ext
|
|
731
|
+
if (target.group != null && target.group !== false) {
|
|
732
|
+
const spec = typeof target.group === 'string' ? { name: target.group } : target.group
|
|
733
|
+
browser.groups[spec.name] ??= {
|
|
734
|
+
...spec,
|
|
735
|
+
window_groups: Object.create(null),
|
|
736
|
+
}
|
|
737
|
+
target.group = spec.name
|
|
738
|
+
}
|
|
690
739
|
if (target.incognito && !reverse && target.context == null && target.create_params?.browserContextId == null) {
|
|
691
740
|
target.context = 'incognito'
|
|
692
741
|
}
|
|
@@ -720,6 +769,7 @@ export class cdp extends EventTarget {
|
|
|
720
769
|
Object.assign(info, targetInfo)
|
|
721
770
|
if (!info.incognito) throw new Error('Existing target is not incognito')
|
|
722
771
|
}
|
|
772
|
+
if (target.group != null && target.group !== false) info.groupId = await this._group(browser, target)
|
|
723
773
|
if (!info.sessionId) {
|
|
724
774
|
info.sessionId = (await socket.req({
|
|
725
775
|
method: 'Target.attachToTarget',
|
|
@@ -796,10 +846,14 @@ export class cdp extends EventTarget {
|
|
|
796
846
|
},
|
|
797
847
|
targets: Object.create(null),
|
|
798
848
|
contexts: Object.create(null),
|
|
849
|
+
groups: Object.create(null),
|
|
799
850
|
}
|
|
800
851
|
if (target?.context != null && (record.reverse || record.cdp_ext)) {
|
|
801
852
|
throw new Error('Named browser contexts are unavailable through the reverse Chrome extension')
|
|
802
853
|
}
|
|
854
|
+
if (target?.group != null && target.group !== false && !record.reverse && !record.cdp_ext) {
|
|
855
|
+
throw new Error('Tab groups require the reverse Chrome extension (cdp_ext or reverse)')
|
|
856
|
+
}
|
|
803
857
|
if (!record.connecting && record.socket?.readyState !== WebSocket.OPEN) {
|
|
804
858
|
record.connecting = this._connect(record).catch(error => {
|
|
805
859
|
record.socket?.close()
|
package/cdp_ext/cdp_ext.js
CHANGED
package/package.json
CHANGED