dsh-builtin-browser 0.4.9 → 0.4.10
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/CHANGELOG.md +173 -0
- package/desktop-bridge/install.mjs +77 -19
- package/desktop-bridge/plugin-browser-bridge.js +49 -3
- package/docs/limitations.md +8 -0
- package/lib/browser/types.d.ts +8 -2
- package/lib/browser-electron/desktop-bridge-host.d.ts +49 -0
- package/lib/browser-electron/desktop-bridge-host.js +116 -2
- package/lib/browser-electron/entry.js +13 -0
- package/lib/browser-electron/provider.d.ts +41 -0
- package/lib/browser-electron/provider.js +36 -3
- package/lib/browser-electron/system-browser.d.ts +40 -0
- package/lib/browser-electron/system-browser.js +74 -2
- package/package.json +1 -1
- package/src/browser/types.ts +9 -2
- package/src/browser-electron/desktop-bridge-host.ts +116 -2
- package/src/browser-electron/entry.ts +15 -0
- package/src/browser-electron/provider.ts +79 -3
- package/src/browser-electron/system-browser.ts +79 -2
|
@@ -82,6 +82,7 @@ const defaultLauncher: BrowserLauncher = (path, args) =>
|
|
|
82
82
|
import { spawn, type ChildProcess } from 'node:child_process'
|
|
83
83
|
import { randomUUID } from 'node:crypto'
|
|
84
84
|
import type { ElectronBrowserViewHost, ElectronViewHandle } from './provider.js'
|
|
85
|
+
import type { CarrierPage } from '../browser/types.js'
|
|
85
86
|
|
|
86
87
|
/** Which browser the user asked for. */
|
|
87
88
|
export type BrowserChannel = 'bundled' | 'chrome' | 'edge' | 'auto' | 'sidebar'
|
|
@@ -187,6 +188,19 @@ const INSTALL_LOCATIONS: Record<DetectedBrowser['kind'], Partial<Record<NodeJS.P
|
|
|
187
188
|
* @param env - the environment to resolve against.
|
|
188
189
|
* @returns the path with %NAME% references replaced, or the entry unchanged when unknown.
|
|
189
190
|
*/
|
|
191
|
+
/**
|
|
192
|
+
* Whether a browser's self-reported product name is one this carrier could have launched.
|
|
193
|
+
*
|
|
194
|
+
* `edg`, not `edge`: Edge reports itself as `Edg/155.0.4283.45`, both over HTTP and over the
|
|
195
|
+
* protocol. Testing for the full word rejected a live Edge that had just been started, so the
|
|
196
|
+
* call failed with the hand-off error this check was added to prevent — while the browser was
|
|
197
|
+
* running the whole time, answering on its own port.
|
|
198
|
+
* @param product - the product string a browser reports about itself.
|
|
199
|
+
*/
|
|
200
|
+
function isBrowserProduct(product: unknown): boolean {
|
|
201
|
+
return typeof product === 'string' && /chrome|chromium|edg/i.test(product)
|
|
202
|
+
}
|
|
203
|
+
|
|
190
204
|
/**
|
|
191
205
|
* Whether a target URL is one of the blanks a browser opens by itself.
|
|
192
206
|
*
|
|
@@ -619,13 +633,13 @@ export class SystemBrowserViewHost implements ElectronBrowserViewHost {
|
|
|
619
633
|
if (typeof version.webSocketDebuggerUrl !== 'string') return undefined
|
|
620
634
|
// The port number alone proves nothing — it can be reused once the browser that owned it
|
|
621
635
|
// exits. The endpoint's own answer is the evidence, and it has to name a browser.
|
|
622
|
-
if (
|
|
636
|
+
if (!isBrowserProduct(version.Browser)) return undefined
|
|
623
637
|
const client = new CdpClient(version.webSocketDebuggerUrl)
|
|
624
638
|
await client.whenReady()
|
|
625
639
|
// Asked through the protocol too, so a websocket that merely accepts connections is not
|
|
626
640
|
// mistaken for a browser that answers.
|
|
627
641
|
const info = await client.send('Browser.getVersion', {}) as { product?: string }
|
|
628
|
-
if (
|
|
642
|
+
if (!isBrowserProduct(info?.product)) {
|
|
629
643
|
client.close()
|
|
630
644
|
return undefined
|
|
631
645
|
}
|
|
@@ -867,6 +881,69 @@ export class SystemBrowserViewHost implements ElectronBrowserViewHost {
|
|
|
867
881
|
return !this.disposed
|
|
868
882
|
}
|
|
869
883
|
|
|
884
|
+
/**
|
|
885
|
+
* Every page the browser currently holds, including ones this plugin did not create.
|
|
886
|
+
*
|
|
887
|
+
* Chrome and Edge own their own page creation. A click on a link that opens a new page — or any
|
|
888
|
+
* `window.open` — produces a page the provider never asked for, and nothing reports it back.
|
|
889
|
+
* Those pages are real: they hold memory and stay until something closes them. Measured on a real
|
|
890
|
+
* window: four pages present while `dsh_browser_list_tabs` reported one.
|
|
891
|
+
*
|
|
892
|
+
* Discovery is already on at connection time (see the blank-page normalisation), so
|
|
893
|
+
* `Target.getTargets` also lists pages that existed before this client connected.
|
|
894
|
+
* @returns the browser's pages, or an empty list when it cannot answer.
|
|
895
|
+
*/
|
|
896
|
+
/**
|
|
897
|
+
* Every page the browser holds that THIS HOST did not open.
|
|
898
|
+
*
|
|
899
|
+
* Chrome and Edge own their own page creation. A click on a link that opens a new page — or any
|
|
900
|
+
* `window.open` — produces a page the provider never asked for, and nothing reports it back.
|
|
901
|
+
* Those pages are real: they hold memory and stay until something closes them. Measured on a real
|
|
902
|
+
* window: four pages present while `dsh_browser_list_tabs` reported one.
|
|
903
|
+
*
|
|
904
|
+
* Pages this host opened are EXCLUDED here rather than deduplicated by the caller, because the
|
|
905
|
+
* caller cannot do it: the provider knows a tab by its own id, the carrier knows a page by its
|
|
906
|
+
* CDP target id, and the two never match. The first version returned everything and relied on the
|
|
907
|
+
* caller to filter, which reported the same page twice.
|
|
908
|
+
*
|
|
909
|
+
* Discovery is already on at connection time (see the blank-page normalisation), so
|
|
910
|
+
* `Target.getTargets` also lists pages that existed before this client connected.
|
|
911
|
+
* @returns the browser's pages that are not ours, or an empty list when it cannot answer.
|
|
912
|
+
*/
|
|
913
|
+
async listPages(): Promise<readonly CarrierPage[]> {
|
|
914
|
+
try {
|
|
915
|
+
const client = await this.ensureClient()
|
|
916
|
+
const listed = await client.send('Target.getTargets', {}) as {
|
|
917
|
+
targetInfos?: Array<{ targetId?: string; type?: string; url?: string; title?: string }>
|
|
918
|
+
}
|
|
919
|
+
const ours = new Set(this.sessions.values())
|
|
920
|
+
return (listed?.targetInfos ?? [])
|
|
921
|
+
.filter(entry => entry.type === 'page' && typeof entry.targetId === 'string')
|
|
922
|
+
.filter(entry => !ours.has(entry.targetId as string))
|
|
923
|
+
.map(entry => ({
|
|
924
|
+
id: entry.targetId as string,
|
|
925
|
+
url: typeof entry.url === 'string' ? entry.url : '',
|
|
926
|
+
...typeof entry.title === 'string' && entry.title !== '' ? { title: entry.title } : {},
|
|
927
|
+
}))
|
|
928
|
+
} catch {
|
|
929
|
+
// A listing must not fail harder than driving does; the next command reports a real problem.
|
|
930
|
+
return []
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Close one page by the target id {@link listPages} reported.
|
|
936
|
+
*
|
|
937
|
+
* `Target.closeTarget` is the browser's own close, so it works for pages this plugin never
|
|
938
|
+
* created. The page may already be gone if the human closed it in the meantime — the caller
|
|
939
|
+
* asked for it to be closed and it is, so that is not an error.
|
|
940
|
+
* @param id - the CDP target id.
|
|
941
|
+
*/
|
|
942
|
+
async closePage(id: string): Promise<void> {
|
|
943
|
+
const client = await this.ensureClient()
|
|
944
|
+
await client.send('Target.closeTarget', { targetId: id }).catch(() => undefined)
|
|
945
|
+
}
|
|
946
|
+
|
|
870
947
|
createView(): ElectronViewHandle {
|
|
871
948
|
const viewId = randomUUID()
|
|
872
949
|
return {
|