@memberjunction/remote-browser-selfhost 0.0.1 → 5.41.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 +92 -43
- package/dist/chrome-container-runner.d.ts +87 -0
- package/dist/chrome-container-runner.d.ts.map +1 -0
- package/dist/chrome-container-runner.js +22 -0
- package/dist/chrome-container-runner.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/local-chrome-container-runner.d.ts +79 -0
- package/dist/local-chrome-container-runner.d.ts.map +1 -0
- package/dist/local-chrome-container-runner.js +170 -0
- package/dist/local-chrome-container-runner.js.map +1 -0
- package/dist/selfhost-remote-browser.d.ts +89 -0
- package/dist/selfhost-remote-browser.d.ts.map +1 -0
- package/dist/selfhost-remote-browser.js +135 -0
- package/dist/selfhost-remote-browser.js.map +1 -0
- package/dist/selfhost-session-backend.d.ts +102 -0
- package/dist/selfhost-session-backend.d.ts.map +1 -0
- package/dist/selfhost-session-backend.js +127 -0
- package/dist/selfhost-session-backend.js.map +1 -0
- package/package.json +42 -7
package/README.md
CHANGED
|
@@ -1,45 +1,94 @@
|
|
|
1
1
|
# @memberjunction/remote-browser-selfhost
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
3
|
+
The **Self-Hosted Chrome** backend driver for MemberJunction's Remote Browser channel. Instead of a
|
|
4
|
+
browser-as-a-service, this backend has MJ orchestrate a **lightweight headless-Chrome container** (just
|
|
5
|
+
Chrome started with a `--remote-debugging-port`) and connect to it over the Chrome DevTools Protocol.
|
|
6
|
+
The shared CDP control kit does all the actual page driving, so this driver answers only one
|
|
7
|
+
backend-specific question — *how do I obtain a CDP endpoint (and its live-view / release hooks) for a
|
|
8
|
+
self-hosted Chrome container?* — behind an **injectable container-runner seam**, so it builds and
|
|
9
|
+
unit-tests with **no container, no network, and no real browser**.
|
|
10
|
+
|
|
11
|
+
See the [Realtime Bridges Guide](../../../../../guides/REALTIME_BRIDGES_GUIDE.md) and
|
|
12
|
+
`/plans/realtime/realtime-bridges-architecture.md` (§4d-i, the Remote Browser channel) for the full
|
|
13
|
+
architecture.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @memberjunction/remote-browser-selfhost
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## What it provides
|
|
22
|
+
|
|
23
|
+
- **`SelfHostRemoteBrowser`** — `@RegisterClass(BaseRemoteBrowserProvider, 'SelfHostRemoteBrowser')`.
|
|
24
|
+
A `MJ: AI Remote Browser Providers` row with `DriverClass = 'SelfHostRemoteBrowser'` resolves to this
|
|
25
|
+
driver via the `ClassFactory`. It extends `BaseCdpRemoteBrowserProvider` and implements only
|
|
26
|
+
`AcquireSession`; everything else (action mapping, capability gating, screencast, human takeover,
|
|
27
|
+
`Connect` / `Disconnect`) is inherited from `@memberjunction/remote-browser-cdp`.
|
|
28
|
+
- **`IChromeContainerRunner`** — the **injectable orchestration seam** the driver depends on instead of a
|
|
29
|
+
real container orchestrator: `Acquire(opts)` → `{ CdpEndpoint, ViewerUrl, Release() }`.
|
|
30
|
+
- **`SelfHostSessionBackend`** — the `ICdpSessionBackend` for a live session: `GetLiveViewUrl()` returns
|
|
31
|
+
the MJ-hosted viewer URL (backed by the inherited screencast), `InvokeNativeAIControl()` throws (no
|
|
32
|
+
native harness), and `Release()` tears down the container (idempotently).
|
|
33
|
+
|
|
34
|
+
## Capability coverage (the Self-Hosted Chrome seed row)
|
|
35
|
+
|
|
36
|
+
Seed row name **`Self-Hosted Chrome`**, `DriverClass = 'SelfHostRemoteBrowser'`.
|
|
37
|
+
|
|
38
|
+
| Capability | Status |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `RawCdpControl` | ✅ universal CDP substrate |
|
|
41
|
+
| `LiveView` | ✅ MJ-hosted viewer URL backed by the inherited CDP screencast |
|
|
42
|
+
| `HumanTakeover` | ✅ inherited grab-the-wheel input routing |
|
|
43
|
+
| `ScreenStreaming` | ✅ |
|
|
44
|
+
| `PersistentContext` | ✅ |
|
|
45
|
+
| `MultiTab` | ✅ |
|
|
46
|
+
| `FileDownloads` | ✅ |
|
|
47
|
+
| `NativeAIControl` | ➖ self-host has no first-party AI-control harness — `InvokeNativeAIControl` throws `RemoteBrowserCapabilityNotSupportedError`; MJ's own computer-use loop drives the page |
|
|
48
|
+
|
|
49
|
+
Unlike a browser-as-a-service, Self-Hosted Chrome has **no provider-hosted live view of its own**.
|
|
50
|
+
`LiveView` is still supported because `GetLiveViewUrl()` returns an **MJ-hosted** viewer URL whose page
|
|
51
|
+
renders the inherited CDP screencast frames — so it returns a URL rather than throwing.
|
|
52
|
+
|
|
53
|
+
## Binding the real Chrome container runner (production)
|
|
54
|
+
|
|
55
|
+
This package ships **without** a real container orchestrator — that is a deployment concern, so no Docker
|
|
56
|
+
/ Kubernetes / Chrome-image wiring is hard-coded here. At startup, bind a factory that builds an
|
|
57
|
+
`IChromeContainerRunner` which spins up a headless-Chrome container and returns its CDP endpoint plus an
|
|
58
|
+
MJ-hosted viewer URL backed by the screencast:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { SelfHostRemoteBrowser, IChromeContainerRunner } from '@memberjunction/remote-browser-selfhost';
|
|
62
|
+
|
|
63
|
+
SelfHostRemoteBrowser.SetContainerRunnerFactory(() => {
|
|
64
|
+
// Build a thin adapter over your real container orchestrator (Docker/K8s/…).
|
|
65
|
+
// Region/image/proxy arrive already resolved in the acquire options; never inline secrets.
|
|
66
|
+
const runner: IChromeContainerRunner = {
|
|
67
|
+
async Acquire(opts) {
|
|
68
|
+
// …start a headless-Chrome container with --remote-debugging-port,
|
|
69
|
+
// stand up the MJ viewer page backed by the screencast,
|
|
70
|
+
// and return the handle:
|
|
71
|
+
return {
|
|
72
|
+
CdpEndpoint, // ws://…/devtools/browser/…
|
|
73
|
+
ViewerUrl, // MJ-hosted viewer backed by the inherited screencast
|
|
74
|
+
async Release() {
|
|
75
|
+
// …stop + tear down the container
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
return runner;
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Until a factory is bound, `AcquireSession` throws an explicit *"bind a real Chrome container runner via
|
|
85
|
+
SetContainerRunnerFactory"* error.
|
|
86
|
+
|
|
87
|
+
## Testing
|
|
88
|
+
|
|
89
|
+
Tests inject a `FakeChromeContainerRunner` via `SelfHostRemoteBrowser.SetContainerRunnerFactory(...)` —
|
|
90
|
+
no container, no network, no real browser. Run them with:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run test
|
|
94
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The injectable **Chrome-container-runner seam** — the orchestration boundary the
|
|
3
|
+
* {@link import('./selfhost-remote-browser.js').SelfHostRemoteBrowser} driver depends on to obtain a
|
|
4
|
+
* running headless-Chrome container exposing a CDP endpoint, plus the MJ-hosted live-view URL backed by
|
|
5
|
+
* the inherited screencast.
|
|
6
|
+
*
|
|
7
|
+
* It is declared as an interface (mirroring the bridge subsystem's `IZoomMeetingSdk` seam) so the driver
|
|
8
|
+
* builds and unit-tests against an in-memory **Fake runner** with **no container, no network, and no
|
|
9
|
+
* real browser**. Binding a real runner in production is a thin adapter that implements this interface;
|
|
10
|
+
* the driver and its tests do not change, and none of the orchestration SDK types leak into this package.
|
|
11
|
+
*
|
|
12
|
+
* ## Production binding (seam — bound at deployment)
|
|
13
|
+
* In production the runner spins up a lightweight headless-Chrome container (just Chrome started with a
|
|
14
|
+
* `--remote-debugging-port`), returns its CDP endpoint for the shared adapter to attach to, and returns
|
|
15
|
+
* an MJ-hosted viewer URL whose page is backed by the inherited CDP screencast frames. `Release()` stops
|
|
16
|
+
* and tears down that container.
|
|
17
|
+
*
|
|
18
|
+
* @see `base-cdp-remote-browser-provider.ts` (`@memberjunction/remote-browser-cdp`) — `AcquireSession`
|
|
19
|
+
* returns the CDP endpoint this runner provides.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The options handed to {@link IChromeContainerRunner.Acquire} for one session — the connection
|
|
23
|
+
* configuration the runner needs to size and launch its Chrome container. All fields are optional so a
|
|
24
|
+
* production runner can fall back to sensible defaults; the values flow from the provider context's
|
|
25
|
+
* opaque `Configuration` (credential references already resolved upstream; never inline secrets).
|
|
26
|
+
*/
|
|
27
|
+
export interface ChromeContainerAcquireOptions {
|
|
28
|
+
/** Optional viewport width hint for the launched Chrome, in pixels. */
|
|
29
|
+
ViewportWidth?: number;
|
|
30
|
+
/** Optional viewport height hint for the launched Chrome, in pixels. */
|
|
31
|
+
ViewportHeight?: number;
|
|
32
|
+
/**
|
|
33
|
+
* The full, opaque backend configuration record from the provider context (region, Chrome image,
|
|
34
|
+
* proxy settings, …). Typed as a record of unknown values rather than `any` so the boundary stays
|
|
35
|
+
* inspectable; a production runner narrows the fields it understands.
|
|
36
|
+
*/
|
|
37
|
+
Configuration?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The handle a {@link IChromeContainerRunner.Acquire} call returns: the CDP endpoint the shared adapter
|
|
41
|
+
* attaches to, the MJ-hosted viewer URL for human live-view, and the teardown hook for the container
|
|
42
|
+
* behind them.
|
|
43
|
+
*/
|
|
44
|
+
export interface ChromeContainerHandle {
|
|
45
|
+
/**
|
|
46
|
+
* The Chrome DevTools Protocol connect endpoint of the launched container (e.g. a
|
|
47
|
+
* `ws://…/devtools/browser/…` URL). The shared provider attaches to this over CDP.
|
|
48
|
+
*/
|
|
49
|
+
CdpEndpoint: string;
|
|
50
|
+
/**
|
|
51
|
+
* The MJ-hosted, embeddable live-view URL whose page renders the inherited CDP screencast frames.
|
|
52
|
+
* Self-host has no first-party hosted live view; this is MJ's own viewer backed by the screencast.
|
|
53
|
+
*/
|
|
54
|
+
ViewerUrl: string;
|
|
55
|
+
/**
|
|
56
|
+
* Tears down the Chrome container behind {@link CdpEndpoint} / {@link ViewerUrl}. Should be
|
|
57
|
+
* idempotent and non-throwing for an already-released container so teardown is safe to run more than
|
|
58
|
+
* once.
|
|
59
|
+
*
|
|
60
|
+
* @returns A promise that resolves once the container has been torn down.
|
|
61
|
+
*/
|
|
62
|
+
Release(): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The minimal Chrome-container orchestration surface the
|
|
66
|
+
* {@link import('./selfhost-remote-browser.js').SelfHostRemoteBrowser} driver depends on. Production binds
|
|
67
|
+
* this to a real container orchestrator; tests inject a `FakeChromeContainerRunner`.
|
|
68
|
+
*/
|
|
69
|
+
export interface IChromeContainerRunner {
|
|
70
|
+
/**
|
|
71
|
+
* Launches (or leases) a headless-Chrome container for one session and returns its CDP endpoint, the
|
|
72
|
+
* MJ-hosted viewer URL, and the teardown hook.
|
|
73
|
+
*
|
|
74
|
+
* @param opts The connection configuration for this session (viewport hints + opaque backend config).
|
|
75
|
+
* @returns A promise resolving to the running container's handle.
|
|
76
|
+
*/
|
|
77
|
+
Acquire(opts: ChromeContainerAcquireOptions): Promise<ChromeContainerHandle>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A factory that constructs an {@link IChromeContainerRunner} — the creation seam (mirroring the bridge
|
|
81
|
+
* subsystem's SDK-factory pattern). Production supplies a factory that builds the real container-runner
|
|
82
|
+
* adapter; tests supply one that returns a `FakeChromeContainerRunner`.
|
|
83
|
+
*
|
|
84
|
+
* @returns The Chrome-container runner the driver acquires sessions through.
|
|
85
|
+
*/
|
|
86
|
+
export type ChromeContainerRunnerFactory = () => IChromeContainerRunner;
|
|
87
|
+
//# sourceMappingURL=chrome-container-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-container-runner.d.ts","sourceRoot":"","sources":["../src/chrome-container-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,6BAA6B;IAC1C,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;;OAMG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,EAAE,6BAA6B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAChF;AAED;;;;;;GAMG;AACH,MAAM,MAAM,4BAA4B,GAAG,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The injectable **Chrome-container-runner seam** — the orchestration boundary the
|
|
3
|
+
* {@link import('./selfhost-remote-browser.js').SelfHostRemoteBrowser} driver depends on to obtain a
|
|
4
|
+
* running headless-Chrome container exposing a CDP endpoint, plus the MJ-hosted live-view URL backed by
|
|
5
|
+
* the inherited screencast.
|
|
6
|
+
*
|
|
7
|
+
* It is declared as an interface (mirroring the bridge subsystem's `IZoomMeetingSdk` seam) so the driver
|
|
8
|
+
* builds and unit-tests against an in-memory **Fake runner** with **no container, no network, and no
|
|
9
|
+
* real browser**. Binding a real runner in production is a thin adapter that implements this interface;
|
|
10
|
+
* the driver and its tests do not change, and none of the orchestration SDK types leak into this package.
|
|
11
|
+
*
|
|
12
|
+
* ## Production binding (seam — bound at deployment)
|
|
13
|
+
* In production the runner spins up a lightweight headless-Chrome container (just Chrome started with a
|
|
14
|
+
* `--remote-debugging-port`), returns its CDP endpoint for the shared adapter to attach to, and returns
|
|
15
|
+
* an MJ-hosted viewer URL whose page is backed by the inherited CDP screencast frames. `Release()` stops
|
|
16
|
+
* and tears down that container.
|
|
17
|
+
*
|
|
18
|
+
* @see `base-cdp-remote-browser-provider.ts` (`@memberjunction/remote-browser-cdp`) — `AcquireSession`
|
|
19
|
+
* returns the CDP endpoint this runner provides.
|
|
20
|
+
*/
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=chrome-container-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-container-runner.js","sourceRoot":"","sources":["../src/chrome-container-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@memberjunction/remote-browser-selfhost` — the Self-Hosted Chrome backend driver for the Remote
|
|
3
|
+
* Browser channel.
|
|
4
|
+
*
|
|
5
|
+
* Exports the driver, its injectable Chrome-container-runner seam, and the session backend, plus a
|
|
6
|
+
* static tree-shaking-prevention loader so the `ClassFactory` can resolve the
|
|
7
|
+
* `'SelfHostRemoteBrowser'` registration.
|
|
8
|
+
*/
|
|
9
|
+
export * from './chrome-container-runner.js';
|
|
10
|
+
export * from './local-chrome-container-runner.js';
|
|
11
|
+
export * from './selfhost-session-backend.js';
|
|
12
|
+
export * from './selfhost-remote-browser.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@memberjunction/remote-browser-selfhost` — the Self-Hosted Chrome backend driver for the Remote
|
|
3
|
+
* Browser channel.
|
|
4
|
+
*
|
|
5
|
+
* Exports the driver, its injectable Chrome-container-runner seam, and the session backend, plus a
|
|
6
|
+
* static tree-shaking-prevention loader so the `ClassFactory` can resolve the
|
|
7
|
+
* `'SelfHostRemoteBrowser'` registration.
|
|
8
|
+
*/
|
|
9
|
+
export * from './chrome-container-runner.js';
|
|
10
|
+
export * from './local-chrome-container-runner.js';
|
|
11
|
+
export * from './selfhost-session-backend.js';
|
|
12
|
+
export * from './selfhost-remote-browser.js';
|
|
13
|
+
import { LoadSelfHostRemoteBrowser } from './selfhost-remote-browser.js';
|
|
14
|
+
// Static reference so bundlers cannot tree-shake the
|
|
15
|
+
// @RegisterClass(BaseRemoteBrowserProvider, 'SelfHostRemoteBrowser') registration. Calling the no-op
|
|
16
|
+
// here keeps the driver resolvable by the engine's ClassFactory.
|
|
17
|
+
LoadSelfHostRemoteBrowser();
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAE1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,qDAAqD;AACrD,qGAAqG;AACrG,iEAAiE;AACjE,yBAAyB,EAAE,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The DEFAULT, zero-config {@link IChromeContainerRunner} for Self-Hosted Chrome: a LOCAL
|
|
3
|
+
* headless Chromium launched on this host (no Docker, no container orchestrator, no cloud account).
|
|
4
|
+
*
|
|
5
|
+
* It launches the Chromium that Playwright manages with a `--remote-debugging-port`, then returns the
|
|
6
|
+
* DevTools **CDP HTTP endpoint** (`http://127.0.0.1:<port>`) for the shared CDP adapter to attach to via
|
|
7
|
+
* `connectOverCDP`. `Release()` closes that Chromium. This is what makes
|
|
8
|
+
* `@memberjunction/remote-browser-selfhost` work out of the box — it is bound as the default factory at
|
|
9
|
+
* module load, so a deployment needs NO `SetContainerRunnerFactory` call and NO external service to drive
|
|
10
|
+
* a real browser. The seam stays open: a deployment that wants a real container backend still overrides it
|
|
11
|
+
* via `SetContainerRunnerFactory`.
|
|
12
|
+
*
|
|
13
|
+
* ## Why the CDP HTTP endpoint (not `browser.wsEndpoint()`)
|
|
14
|
+
* For a `chromium.launch()` browser, `browser.wsEndpoint()` is the **Playwright-protocol** endpoint, which
|
|
15
|
+
* `connectOverCDP` cannot attach to. The shared `BaseCdpRemoteBrowserProvider` always attaches via CDP, so
|
|
16
|
+
* the runner launches Chromium with `--remote-debugging-port=<port>` and hands back the raw DevTools HTTP
|
|
17
|
+
* endpoint, which `connectOverCDP` accepts directly.
|
|
18
|
+
*
|
|
19
|
+
* Playwright is a (peer) dependency only because of this default runner; it is imported lazily so a
|
|
20
|
+
* deployment that overrides the runner — or never starts a self-host session — pays nothing for it.
|
|
21
|
+
*
|
|
22
|
+
* @module @memberjunction/remote-browser-selfhost
|
|
23
|
+
* @author MemberJunction.com
|
|
24
|
+
*/
|
|
25
|
+
import { ChromeContainerAcquireOptions, ChromeContainerHandle, IChromeContainerRunner } from './chrome-container-runner.js';
|
|
26
|
+
/**
|
|
27
|
+
* The default local-Chrome runner — launches a local headless Chromium via Playwright and exposes its
|
|
28
|
+
* real CDP HTTP endpoint. Stateless; one instance is shared process-wide (each {@link Acquire} launches
|
|
29
|
+
* its own browser and its {@link ChromeContainerHandle.Release} closes exactly that one).
|
|
30
|
+
*/
|
|
31
|
+
export declare class LocalChromeContainerRunner implements IChromeContainerRunner {
|
|
32
|
+
/**
|
|
33
|
+
* Launches a local headless Chromium and returns its CDP HTTP endpoint + teardown hook.
|
|
34
|
+
*
|
|
35
|
+
* @param opts Per-session viewport hints (and the opaque backend configuration, unused here).
|
|
36
|
+
* @returns A handle carrying the launched browser's CDP HTTP endpoint and a `Release` that closes it.
|
|
37
|
+
* @throws When Playwright is not installed, or the browser fails to launch / expose a CDP endpoint.
|
|
38
|
+
*/
|
|
39
|
+
Acquire(opts: ChromeContainerAcquireOptions): Promise<ChromeContainerHandle>;
|
|
40
|
+
/**
|
|
41
|
+
* Lazily imports Playwright's `chromium` launcher. Kept dynamic (the sanctioned optional-peer case) so
|
|
42
|
+
* the package builds and a non-self-host deployment pays nothing — but the error is explicit when a
|
|
43
|
+
* self-host session is actually started without Playwright installed.
|
|
44
|
+
*
|
|
45
|
+
* @returns Playwright's `chromium` browser type.
|
|
46
|
+
* @throws When the optional `playwright` peer dependency is not installed.
|
|
47
|
+
*/
|
|
48
|
+
private loadChromium;
|
|
49
|
+
/**
|
|
50
|
+
* Builds the Chromium launch args: the chosen remote-debugging port (exposing a real CDP endpoint),
|
|
51
|
+
* bound to loopback, plus the resolved window size.
|
|
52
|
+
*
|
|
53
|
+
* @param opts The acquire options carrying optional viewport hints.
|
|
54
|
+
* @param port The remote-debugging port to expose CDP on.
|
|
55
|
+
* @returns The Chromium command-line args.
|
|
56
|
+
*/
|
|
57
|
+
private launchArgs;
|
|
58
|
+
/**
|
|
59
|
+
* Polls Chromium's DevTools `/json/version` document until it is reachable (the browser has fully
|
|
60
|
+
* started its CDP server) or the readiness timeout elapses.
|
|
61
|
+
*
|
|
62
|
+
* @param cdpEndpoint The CDP HTTP base endpoint (`http://127.0.0.1:<port>`).
|
|
63
|
+
* @throws When the DevTools endpoint is not reachable within {@link DEVTOOLS_READY_TIMEOUT_MS}.
|
|
64
|
+
*/
|
|
65
|
+
private waitForDevTools;
|
|
66
|
+
/**
|
|
67
|
+
* Reserves a free TCP port by binding an ephemeral server to loopback and reading the assigned port.
|
|
68
|
+
* There is an unavoidable (tiny) race between releasing the port and Chromium binding it; on a single
|
|
69
|
+
* host this is acceptable for the default runner.
|
|
70
|
+
*
|
|
71
|
+
* @returns A free TCP port on loopback.
|
|
72
|
+
*/
|
|
73
|
+
private findFreePort;
|
|
74
|
+
/** Closes a browser, swallowing any teardown error so `Release` (and failure cleanup) never throws. */
|
|
75
|
+
private closeQuietly;
|
|
76
|
+
/** A cancellable-free delay used between DevTools readiness polls. */
|
|
77
|
+
private delay;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=local-chrome-container-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-chrome-container-runner.d.ts","sourceRoot":"","sources":["../src/local-chrome-container-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EACH,6BAA6B,EAC7B,qBAAqB,EACrB,sBAAsB,EACzB,MAAM,2BAA2B,CAAC;AAYnC;;;;GAIG;AACH,qBAAa,0BAA2B,YAAW,sBAAsB;IACrE;;;;;;OAMG;IACU,OAAO,CAAC,IAAI,EAAE,6BAA6B,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAyBzF;;;;;;;OAOG;YACW,YAAY;IAa1B;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAUlB;;;;;;OAMG;YACW,eAAe;IAoB7B;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAgBpB,uGAAuG;YACzF,YAAY;IAQ1B,sEAAsE;IACtE,OAAO,CAAC,KAAK;CAGhB"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The DEFAULT, zero-config {@link IChromeContainerRunner} for Self-Hosted Chrome: a LOCAL
|
|
3
|
+
* headless Chromium launched on this host (no Docker, no container orchestrator, no cloud account).
|
|
4
|
+
*
|
|
5
|
+
* It launches the Chromium that Playwright manages with a `--remote-debugging-port`, then returns the
|
|
6
|
+
* DevTools **CDP HTTP endpoint** (`http://127.0.0.1:<port>`) for the shared CDP adapter to attach to via
|
|
7
|
+
* `connectOverCDP`. `Release()` closes that Chromium. This is what makes
|
|
8
|
+
* `@memberjunction/remote-browser-selfhost` work out of the box — it is bound as the default factory at
|
|
9
|
+
* module load, so a deployment needs NO `SetContainerRunnerFactory` call and NO external service to drive
|
|
10
|
+
* a real browser. The seam stays open: a deployment that wants a real container backend still overrides it
|
|
11
|
+
* via `SetContainerRunnerFactory`.
|
|
12
|
+
*
|
|
13
|
+
* ## Why the CDP HTTP endpoint (not `browser.wsEndpoint()`)
|
|
14
|
+
* For a `chromium.launch()` browser, `browser.wsEndpoint()` is the **Playwright-protocol** endpoint, which
|
|
15
|
+
* `connectOverCDP` cannot attach to. The shared `BaseCdpRemoteBrowserProvider` always attaches via CDP, so
|
|
16
|
+
* the runner launches Chromium with `--remote-debugging-port=<port>` and hands back the raw DevTools HTTP
|
|
17
|
+
* endpoint, which `connectOverCDP` accepts directly.
|
|
18
|
+
*
|
|
19
|
+
* Playwright is a (peer) dependency only because of this default runner; it is imported lazily so a
|
|
20
|
+
* deployment that overrides the runner — or never starts a self-host session — pays nothing for it.
|
|
21
|
+
*
|
|
22
|
+
* @module @memberjunction/remote-browser-selfhost
|
|
23
|
+
* @author MemberJunction.com
|
|
24
|
+
*/
|
|
25
|
+
import { createServer } from 'node:net';
|
|
26
|
+
/** Default viewport when the acquire options carry no hint. */
|
|
27
|
+
const DEFAULT_VIEWPORT_WIDTH = 1280;
|
|
28
|
+
/** Default viewport when the acquire options carry no hint. */
|
|
29
|
+
const DEFAULT_VIEWPORT_HEIGHT = 800;
|
|
30
|
+
/** How long to wait for Chromium's DevTools endpoint to become reachable, in ms. */
|
|
31
|
+
const DEVTOOLS_READY_TIMEOUT_MS = 10_000;
|
|
32
|
+
/** Poll interval while waiting for the DevTools endpoint, in ms. */
|
|
33
|
+
const DEVTOOLS_POLL_INTERVAL_MS = 100;
|
|
34
|
+
/**
|
|
35
|
+
* The default local-Chrome runner — launches a local headless Chromium via Playwright and exposes its
|
|
36
|
+
* real CDP HTTP endpoint. Stateless; one instance is shared process-wide (each {@link Acquire} launches
|
|
37
|
+
* its own browser and its {@link ChromeContainerHandle.Release} closes exactly that one).
|
|
38
|
+
*/
|
|
39
|
+
export class LocalChromeContainerRunner {
|
|
40
|
+
/**
|
|
41
|
+
* Launches a local headless Chromium and returns its CDP HTTP endpoint + teardown hook.
|
|
42
|
+
*
|
|
43
|
+
* @param opts Per-session viewport hints (and the opaque backend configuration, unused here).
|
|
44
|
+
* @returns A handle carrying the launched browser's CDP HTTP endpoint and a `Release` that closes it.
|
|
45
|
+
* @throws When Playwright is not installed, or the browser fails to launch / expose a CDP endpoint.
|
|
46
|
+
*/
|
|
47
|
+
async Acquire(opts) {
|
|
48
|
+
const chromium = await this.loadChromium();
|
|
49
|
+
const port = await this.findFreePort();
|
|
50
|
+
const browser = await chromium.launch({
|
|
51
|
+
headless: true,
|
|
52
|
+
args: this.launchArgs(opts, port),
|
|
53
|
+
});
|
|
54
|
+
const cdpEndpoint = `http://127.0.0.1:${port}`;
|
|
55
|
+
try {
|
|
56
|
+
await this.waitForDevTools(cdpEndpoint);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
await this.closeQuietly(browser);
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
CdpEndpoint: cdpEndpoint,
|
|
64
|
+
// Self-host's local runner has no first-party hosted viewer; the MJ live view is backed by the
|
|
65
|
+
// inherited CDP screencast. Surface the CDP endpoint as the (non-navigable) viewer reference.
|
|
66
|
+
ViewerUrl: cdpEndpoint,
|
|
67
|
+
Release: async () => {
|
|
68
|
+
await this.closeQuietly(browser);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Lazily imports Playwright's `chromium` launcher. Kept dynamic (the sanctioned optional-peer case) so
|
|
74
|
+
* the package builds and a non-self-host deployment pays nothing — but the error is explicit when a
|
|
75
|
+
* self-host session is actually started without Playwright installed.
|
|
76
|
+
*
|
|
77
|
+
* @returns Playwright's `chromium` browser type.
|
|
78
|
+
* @throws When the optional `playwright` peer dependency is not installed.
|
|
79
|
+
*/
|
|
80
|
+
async loadChromium() {
|
|
81
|
+
try {
|
|
82
|
+
const { chromium } = await import('playwright');
|
|
83
|
+
return chromium;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new Error('The default Self-Hosted Chrome runner needs Playwright, but it is not installed. ' +
|
|
87
|
+
"Install it with 'npm install playwright' (and run 'npx playwright install chromium'), " +
|
|
88
|
+
'or bind a real container runner via SelfHostRemoteBrowser.SetContainerRunnerFactory(...).');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Builds the Chromium launch args: the chosen remote-debugging port (exposing a real CDP endpoint),
|
|
93
|
+
* bound to loopback, plus the resolved window size.
|
|
94
|
+
*
|
|
95
|
+
* @param opts The acquire options carrying optional viewport hints.
|
|
96
|
+
* @param port The remote-debugging port to expose CDP on.
|
|
97
|
+
* @returns The Chromium command-line args.
|
|
98
|
+
*/
|
|
99
|
+
launchArgs(opts, port) {
|
|
100
|
+
const width = opts.ViewportWidth ?? DEFAULT_VIEWPORT_WIDTH;
|
|
101
|
+
const height = opts.ViewportHeight ?? DEFAULT_VIEWPORT_HEIGHT;
|
|
102
|
+
return [
|
|
103
|
+
`--remote-debugging-port=${port}`,
|
|
104
|
+
'--remote-debugging-address=127.0.0.1',
|
|
105
|
+
`--window-size=${width},${height}`,
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Polls Chromium's DevTools `/json/version` document until it is reachable (the browser has fully
|
|
110
|
+
* started its CDP server) or the readiness timeout elapses.
|
|
111
|
+
*
|
|
112
|
+
* @param cdpEndpoint The CDP HTTP base endpoint (`http://127.0.0.1:<port>`).
|
|
113
|
+
* @throws When the DevTools endpoint is not reachable within {@link DEVTOOLS_READY_TIMEOUT_MS}.
|
|
114
|
+
*/
|
|
115
|
+
async waitForDevTools(cdpEndpoint) {
|
|
116
|
+
const deadline = Date.now() + DEVTOOLS_READY_TIMEOUT_MS;
|
|
117
|
+
let lastError;
|
|
118
|
+
while (Date.now() < deadline) {
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch(`${cdpEndpoint}/json/version`);
|
|
121
|
+
if (response.ok) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
lastError = err;
|
|
127
|
+
}
|
|
128
|
+
await this.delay(DEVTOOLS_POLL_INTERVAL_MS);
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`Local Chromium did not expose a CDP endpoint at ${cdpEndpoint} within ${DEVTOOLS_READY_TIMEOUT_MS}ms` +
|
|
131
|
+
(lastError instanceof Error ? `: ${lastError.message}` : '.'));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Reserves a free TCP port by binding an ephemeral server to loopback and reading the assigned port.
|
|
135
|
+
* There is an unavoidable (tiny) race between releasing the port and Chromium binding it; on a single
|
|
136
|
+
* host this is acceptable for the default runner.
|
|
137
|
+
*
|
|
138
|
+
* @returns A free TCP port on loopback.
|
|
139
|
+
*/
|
|
140
|
+
findFreePort() {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const server = createServer();
|
|
143
|
+
server.once('error', reject);
|
|
144
|
+
server.listen(0, '127.0.0.1', () => {
|
|
145
|
+
const address = server.address();
|
|
146
|
+
if (address && typeof address === 'object') {
|
|
147
|
+
const { port } = address;
|
|
148
|
+
server.close(() => resolve(port));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
server.close(() => reject(new Error('Failed to reserve a free port for local Chromium.')));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/** Closes a browser, swallowing any teardown error so `Release` (and failure cleanup) never throws. */
|
|
157
|
+
async closeQuietly(browser) {
|
|
158
|
+
try {
|
|
159
|
+
await browser.close();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// best-effort teardown — a browser that already exited is a benign no-op
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** A cancellable-free delay used between DevTools readiness polls. */
|
|
166
|
+
delay(ms) {
|
|
167
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=local-chrome-container-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-chrome-container-runner.js","sourceRoot":"","sources":["../src/local-chrome-container-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `SelfHostRemoteBrowser` — the Self-Hosted Chrome backend driver for the Remote Browser
|
|
3
|
+
* channel. It connects the shared CDP control kit
|
|
4
|
+
* ({@link BaseCdpRemoteBrowserProvider}) to a lightweight, MJ-orchestrated headless-Chrome container.
|
|
5
|
+
*
|
|
6
|
+
* The driver itself is intentionally trivial: the shared kit implements the entire generic path
|
|
7
|
+
* (Connect/Disconnect, adapter-attach over CDP, action mapping, screencast, human takeover, capability
|
|
8
|
+
* gating). This driver fills the ONE backend-specific hook — {@link AcquireSession} — by leasing a Chrome
|
|
9
|
+
* container through an injectable {@link IChromeContainerRunner} seam and wrapping its handle in a
|
|
10
|
+
* {@link SelfHostSessionBackend}.
|
|
11
|
+
*
|
|
12
|
+
* Capability coverage (per the `MJ: AI Remote Browser Providers` seed row "Self-Hosted Chrome"):
|
|
13
|
+
* RawCdpControl, LiveView, HumanTakeover, ScreenStreaming, PersistentContext, MultiTab, FileDownloads —
|
|
14
|
+
* and explicitly NOT NativeAIControl (self-host has no first-party AI harness).
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/remote-browser-selfhost
|
|
17
|
+
* @author MemberJunction.com
|
|
18
|
+
*/
|
|
19
|
+
import { RemoteBrowserProviderContext } from '@memberjunction/remote-browser-base';
|
|
20
|
+
import { AcquiredCdpSession, BaseCdpRemoteBrowserProvider } from '@memberjunction/remote-browser-cdp';
|
|
21
|
+
import { ChromeContainerRunnerFactory } from './chrome-container-runner.js';
|
|
22
|
+
/**
|
|
23
|
+
* The `DriverClass` key {@link SelfHostRemoteBrowser} registers under. A `MJ: AI Remote Browser Providers`
|
|
24
|
+
* row with `DriverClass = 'SelfHostRemoteBrowser'` resolves to this driver via the `ClassFactory`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SELF_HOST_REMOTE_BROWSER_DRIVER_CLASS = "SelfHostRemoteBrowser";
|
|
27
|
+
/**
|
|
28
|
+
* The Self-Hosted Chrome Remote Browser driver. Subclasses {@link BaseCdpRemoteBrowserProvider} and
|
|
29
|
+
* implements only {@link AcquireSession}; all connect/disconnect orchestration, action mapping, and
|
|
30
|
+
* capability gating are inherited.
|
|
31
|
+
*
|
|
32
|
+
* Construct via the default constructor (the engine's `ClassFactory` path). By default it uses a
|
|
33
|
+
* {@link LocalChromeContainerRunner} (local headless Chromium) so it works with no external service. The
|
|
34
|
+
* container-runner seam is bound process-wide via the static {@link SetContainerRunnerFactory} — mirroring
|
|
35
|
+
* how bridge drivers bind their real SDK — so a deployment that wants a real container backend overrides
|
|
36
|
+
* the default once at startup and every resolved driver instance shares it.
|
|
37
|
+
*
|
|
38
|
+
* Registered via `@RegisterClass(BaseRemoteBrowserProvider, 'SelfHostRemoteBrowser')`.
|
|
39
|
+
*/
|
|
40
|
+
export declare class SelfHostRemoteBrowser extends BaseCdpRemoteBrowserProvider {
|
|
41
|
+
/**
|
|
42
|
+
* The process-wide Chrome-container-runner factory. Defaults to {@link defaultContainerRunnerFactory}
|
|
43
|
+
* (a local headless Chromium); a deployment overrides it via {@link SetContainerRunnerFactory}.
|
|
44
|
+
*/
|
|
45
|
+
private static containerRunnerFactory;
|
|
46
|
+
/**
|
|
47
|
+
* Overrides the {@link ChromeContainerRunnerFactory} every {@link SelfHostRemoteBrowser} instance uses
|
|
48
|
+
* to acquire its Chrome container — the override seam, mirroring how bridge drivers bind their real
|
|
49
|
+
* SDK. The default is a local-Chromium runner, so this is only needed to point self-host at a real
|
|
50
|
+
* container backend; tests call it with a factory that returns a fake runner.
|
|
51
|
+
*
|
|
52
|
+
* @param factory The factory that constructs the Chrome-container runner.
|
|
53
|
+
*/
|
|
54
|
+
static SetContainerRunnerFactory(factory: ChromeContainerRunnerFactory): void;
|
|
55
|
+
/**
|
|
56
|
+
* Acquires a CDP endpoint by leasing a headless-Chrome container through the bound runner, then wraps
|
|
57
|
+
* the container handle in a {@link SelfHostSessionBackend}. The shared base attaches its adapter to
|
|
58
|
+
* the returned endpoint and delegates the backend-specific concerns (live view / native-AI / release)
|
|
59
|
+
* to the backend.
|
|
60
|
+
*
|
|
61
|
+
* @param ctx The provider context (features, configuration, control mode, context user).
|
|
62
|
+
* @returns A promise resolving to the acquired CDP endpoint + Self-Hosted Chrome backend hooks.
|
|
63
|
+
*/
|
|
64
|
+
protected AcquireSession(ctx: RemoteBrowserProviderContext): Promise<AcquiredCdpSession>;
|
|
65
|
+
/**
|
|
66
|
+
* Builds the {@link ChromeContainerAcquireOptions} for a session from the provider context — carrying
|
|
67
|
+
* the optional viewport hints and the full opaque backend configuration through to the runner.
|
|
68
|
+
*
|
|
69
|
+
* @param ctx The provider context whose `Configuration` may carry viewport + backend hints.
|
|
70
|
+
* @returns The acquire options to hand the Chrome-container runner.
|
|
71
|
+
*/
|
|
72
|
+
private buildAcquireOptions;
|
|
73
|
+
/**
|
|
74
|
+
* Reads an optional numeric value from the opaque, backend-specific `Configuration` record without
|
|
75
|
+
* resorting to `any`. Returns `undefined` when the key is absent or not a finite number.
|
|
76
|
+
*
|
|
77
|
+
* @param configuration The opaque configuration record (or undefined).
|
|
78
|
+
* @param key The key to read.
|
|
79
|
+
* @returns The numeric value, or `undefined` when absent/non-numeric.
|
|
80
|
+
*/
|
|
81
|
+
private readNumberOption;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Tree-shaking-prevention loader. Modern bundlers cannot see the `@RegisterClass` dynamic registration of
|
|
85
|
+
* {@link SelfHostRemoteBrowser} and may eliminate it. Import and call this no-op from a static code path
|
|
86
|
+
* (the package entry point does) so the `ClassFactory` can resolve `'SelfHostRemoteBrowser'`.
|
|
87
|
+
*/
|
|
88
|
+
export declare function LoadSelfHostRemoteBrowser(): void;
|
|
89
|
+
//# sourceMappingURL=selfhost-remote-browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selfhost-remote-browser.d.ts","sourceRoot":"","sources":["../src/selfhost-remote-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EAA6B,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,oCAAoC,CAAC;AACtG,OAAO,EAEH,4BAA4B,EAE/B,MAAM,2BAA2B,CAAC;AAInC;;;GAGG;AACH,eAAO,MAAM,qCAAqC,0BAA0B,CAAC;AAa7E;;;;;;;;;;;;GAYG;AACH,qBACa,qBAAsB,SAAQ,4BAA4B;IACnE;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,sBAAsB,CAA+D;IAEpG;;;;;;;OAOG;WACW,yBAAyB,CAAC,OAAO,EAAE,4BAA4B,GAAG,IAAI;IAIpF;;;;;;;;OAQG;cACa,cAAc,CAAC,GAAG,EAAE,4BAA4B,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAa9F;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;CAO3B;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `SelfHostRemoteBrowser` — the Self-Hosted Chrome backend driver for the Remote Browser
|
|
3
|
+
* channel. It connects the shared CDP control kit
|
|
4
|
+
* ({@link BaseCdpRemoteBrowserProvider}) to a lightweight, MJ-orchestrated headless-Chrome container.
|
|
5
|
+
*
|
|
6
|
+
* The driver itself is intentionally trivial: the shared kit implements the entire generic path
|
|
7
|
+
* (Connect/Disconnect, adapter-attach over CDP, action mapping, screencast, human takeover, capability
|
|
8
|
+
* gating). This driver fills the ONE backend-specific hook — {@link AcquireSession} — by leasing a Chrome
|
|
9
|
+
* container through an injectable {@link IChromeContainerRunner} seam and wrapping its handle in a
|
|
10
|
+
* {@link SelfHostSessionBackend}.
|
|
11
|
+
*
|
|
12
|
+
* Capability coverage (per the `MJ: AI Remote Browser Providers` seed row "Self-Hosted Chrome"):
|
|
13
|
+
* RawCdpControl, LiveView, HumanTakeover, ScreenStreaming, PersistentContext, MultiTab, FileDownloads —
|
|
14
|
+
* and explicitly NOT NativeAIControl (self-host has no first-party AI harness).
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/remote-browser-selfhost
|
|
17
|
+
* @author MemberJunction.com
|
|
18
|
+
*/
|
|
19
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
20
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
21
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
22
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
23
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
24
|
+
};
|
|
25
|
+
var SelfHostRemoteBrowser_1;
|
|
26
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
27
|
+
import { BaseRemoteBrowserProvider } from '@memberjunction/remote-browser-base';
|
|
28
|
+
import { BaseCdpRemoteBrowserProvider } from '@memberjunction/remote-browser-cdp';
|
|
29
|
+
import { LocalChromeContainerRunner } from './local-chrome-container-runner.js';
|
|
30
|
+
import { SelfHostSessionBackend } from './selfhost-session-backend.js';
|
|
31
|
+
/**
|
|
32
|
+
* The `DriverClass` key {@link SelfHostRemoteBrowser} registers under. A `MJ: AI Remote Browser Providers`
|
|
33
|
+
* row with `DriverClass = 'SelfHostRemoteBrowser'` resolves to this driver via the `ClassFactory`.
|
|
34
|
+
*/
|
|
35
|
+
export const SELF_HOST_REMOTE_BROWSER_DRIVER_CLASS = 'SelfHostRemoteBrowser';
|
|
36
|
+
/**
|
|
37
|
+
* The DEFAULT Chrome-container-runner factory: a {@link LocalChromeContainerRunner} that launches a local
|
|
38
|
+
* headless Chromium via Playwright (no Docker, no container orchestrator, no cloud account). This makes
|
|
39
|
+
* Self-Hosted Chrome work OUT OF THE BOX — a deployment needs no `SetContainerRunnerFactory` call and no
|
|
40
|
+
* external service to drive a real browser. A deployment that wants a real container backend overrides it
|
|
41
|
+
* via {@link SelfHostRemoteBrowser.SetContainerRunnerFactory}; tests inject a `FakeChromeContainerRunner`.
|
|
42
|
+
*
|
|
43
|
+
* @returns A {@link LocalChromeContainerRunner}.
|
|
44
|
+
*/
|
|
45
|
+
const defaultContainerRunnerFactory = () => new LocalChromeContainerRunner();
|
|
46
|
+
/**
|
|
47
|
+
* The Self-Hosted Chrome Remote Browser driver. Subclasses {@link BaseCdpRemoteBrowserProvider} and
|
|
48
|
+
* implements only {@link AcquireSession}; all connect/disconnect orchestration, action mapping, and
|
|
49
|
+
* capability gating are inherited.
|
|
50
|
+
*
|
|
51
|
+
* Construct via the default constructor (the engine's `ClassFactory` path). By default it uses a
|
|
52
|
+
* {@link LocalChromeContainerRunner} (local headless Chromium) so it works with no external service. The
|
|
53
|
+
* container-runner seam is bound process-wide via the static {@link SetContainerRunnerFactory} — mirroring
|
|
54
|
+
* how bridge drivers bind their real SDK — so a deployment that wants a real container backend overrides
|
|
55
|
+
* the default once at startup and every resolved driver instance shares it.
|
|
56
|
+
*
|
|
57
|
+
* Registered via `@RegisterClass(BaseRemoteBrowserProvider, 'SelfHostRemoteBrowser')`.
|
|
58
|
+
*/
|
|
59
|
+
let SelfHostRemoteBrowser = class SelfHostRemoteBrowser extends BaseCdpRemoteBrowserProvider {
|
|
60
|
+
static { SelfHostRemoteBrowser_1 = this; }
|
|
61
|
+
/**
|
|
62
|
+
* The process-wide Chrome-container-runner factory. Defaults to {@link defaultContainerRunnerFactory}
|
|
63
|
+
* (a local headless Chromium); a deployment overrides it via {@link SetContainerRunnerFactory}.
|
|
64
|
+
*/
|
|
65
|
+
static { this.containerRunnerFactory = defaultContainerRunnerFactory; }
|
|
66
|
+
/**
|
|
67
|
+
* Overrides the {@link ChromeContainerRunnerFactory} every {@link SelfHostRemoteBrowser} instance uses
|
|
68
|
+
* to acquire its Chrome container — the override seam, mirroring how bridge drivers bind their real
|
|
69
|
+
* SDK. The default is a local-Chromium runner, so this is only needed to point self-host at a real
|
|
70
|
+
* container backend; tests call it with a factory that returns a fake runner.
|
|
71
|
+
*
|
|
72
|
+
* @param factory The factory that constructs the Chrome-container runner.
|
|
73
|
+
*/
|
|
74
|
+
static SetContainerRunnerFactory(factory) {
|
|
75
|
+
SelfHostRemoteBrowser_1.containerRunnerFactory = factory;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Acquires a CDP endpoint by leasing a headless-Chrome container through the bound runner, then wraps
|
|
79
|
+
* the container handle in a {@link SelfHostSessionBackend}. The shared base attaches its adapter to
|
|
80
|
+
* the returned endpoint and delegates the backend-specific concerns (live view / native-AI / release)
|
|
81
|
+
* to the backend.
|
|
82
|
+
*
|
|
83
|
+
* @param ctx The provider context (features, configuration, control mode, context user).
|
|
84
|
+
* @returns A promise resolving to the acquired CDP endpoint + Self-Hosted Chrome backend hooks.
|
|
85
|
+
*/
|
|
86
|
+
async AcquireSession(ctx) {
|
|
87
|
+
this.applyContext(ctx);
|
|
88
|
+
const runner = SelfHostRemoteBrowser_1.containerRunnerFactory();
|
|
89
|
+
const options = this.buildAcquireOptions(ctx);
|
|
90
|
+
const handle = await runner.Acquire(options);
|
|
91
|
+
return {
|
|
92
|
+
CdpEndpoint: handle.CdpEndpoint,
|
|
93
|
+
Backend: new SelfHostSessionBackend(handle),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Builds the {@link ChromeContainerAcquireOptions} for a session from the provider context — carrying
|
|
98
|
+
* the optional viewport hints and the full opaque backend configuration through to the runner.
|
|
99
|
+
*
|
|
100
|
+
* @param ctx The provider context whose `Configuration` may carry viewport + backend hints.
|
|
101
|
+
* @returns The acquire options to hand the Chrome-container runner.
|
|
102
|
+
*/
|
|
103
|
+
buildAcquireOptions(ctx) {
|
|
104
|
+
return {
|
|
105
|
+
ViewportWidth: this.readNumberOption(ctx.Configuration, 'ViewportWidth'),
|
|
106
|
+
ViewportHeight: this.readNumberOption(ctx.Configuration, 'ViewportHeight'),
|
|
107
|
+
Configuration: ctx.Configuration,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Reads an optional numeric value from the opaque, backend-specific `Configuration` record without
|
|
112
|
+
* resorting to `any`. Returns `undefined` when the key is absent or not a finite number.
|
|
113
|
+
*
|
|
114
|
+
* @param configuration The opaque configuration record (or undefined).
|
|
115
|
+
* @param key The key to read.
|
|
116
|
+
* @returns The numeric value, or `undefined` when absent/non-numeric.
|
|
117
|
+
*/
|
|
118
|
+
readNumberOption(configuration, key) {
|
|
119
|
+
const value = configuration?.[key];
|
|
120
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
SelfHostRemoteBrowser = SelfHostRemoteBrowser_1 = __decorate([
|
|
124
|
+
RegisterClass(BaseRemoteBrowserProvider, SELF_HOST_REMOTE_BROWSER_DRIVER_CLASS)
|
|
125
|
+
], SelfHostRemoteBrowser);
|
|
126
|
+
export { SelfHostRemoteBrowser };
|
|
127
|
+
/**
|
|
128
|
+
* Tree-shaking-prevention loader. Modern bundlers cannot see the `@RegisterClass` dynamic registration of
|
|
129
|
+
* {@link SelfHostRemoteBrowser} and may eliminate it. Import and call this no-op from a static code path
|
|
130
|
+
* (the package entry point does) so the `ClassFactory` can resolve `'SelfHostRemoteBrowser'`.
|
|
131
|
+
*/
|
|
132
|
+
export function LoadSelfHostRemoteBrowser() {
|
|
133
|
+
// Intentionally empty — referencing the module is what prevents tree-shaking.
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=selfhost-remote-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selfhost-remote-browser.js","sourceRoot":"","sources":["../src/selfhost-remote-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;;;;;;;;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAgC,MAAM,qCAAqC,CAAC;AAC9G,OAAO,EAAsB,4BAA4B,EAAE,MAAM,oCAAoC,CAAC;AAMtG,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;GAGG;AACH,MAAM,CAAC,MAAM,qCAAqC,GAAG,uBAAuB,CAAC;AAE7E;;;;;;;;GAQG;AACH,MAAM,6BAA6B,GAAiC,GAAG,EAAE,CAAC,IAAI,0BAA0B,EAAE,CAAC;AAE3G;;;;;;;;;;;;GAYG;AAEI,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,4BAA4B;;IACnE;;;OAGG;aACY,2BAAsB,GAAiC,6BAA6B,AAA9D,CAA+D;IAEpG;;;;;;;OAOG;IACI,MAAM,CAAC,yBAAyB,CAAC,OAAqC;QACzE,uBAAqB,CAAC,sBAAsB,GAAG,OAAO,CAAC;IAC3D,CAAC;IAED;;;;;;;;OAQG;IACO,KAAK,CAAC,cAAc,CAAC,GAAiC;QAC5D,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAEvB,MAAM,MAAM,GAA2B,uBAAqB,CAAC,sBAAsB,EAAE,CAAC;QACtF,MAAM,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAE7C,OAAO;YACH,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,OAAO,EAAE,IAAI,sBAAsB,CAAC,MAAM,CAAC;SAC9C,CAAC;IACN,CAAC;IAED;;;;;;OAMG;IACK,mBAAmB,CAAC,GAAiC;QACzD,OAAO;YACH,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,EAAE,eAAe,CAAC;YACxE,cAAc,EAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,EAAE,gBAAgB,CAAC;YAC1E,aAAa,EAAE,GAAG,CAAC,aAAa;SACnC,CAAC;IACN,CAAC;IAED;;;;;;;OAOG;IACK,gBAAgB,CACpB,aAAkD,EAClD,GAAW;QAEX,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC;QACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,CAAC;;AAtEQ,qBAAqB;IADjC,aAAa,CAAC,yBAAyB,EAAE,qCAAqC,CAAC;GACnE,qBAAqB,CAuEjC;;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB;IACrC,8EAA8E;AAClF,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SelfHostSessionBackend` — the Self-Hosted Chrome implementation of the {@link ICdpSessionBackend} hook
|
|
3
|
+
* surface the shared CDP session delegates its three backend-specific concerns to (hosted live-view URL,
|
|
4
|
+
* native-AI delegation, backend release).
|
|
5
|
+
*
|
|
6
|
+
* It wraps the {@link ChromeContainerHandle} returned by the
|
|
7
|
+
* {@link import('./chrome-container-runner.js').IChromeContainerRunner} and answers exactly the
|
|
8
|
+
* Self-Hosted Chrome capability profile (per the `MJ: AI Remote Browser Providers` seed row
|
|
9
|
+
* "Self-Hosted Chrome" / `DriverClass = 'SelfHostRemoteBrowser'`):
|
|
10
|
+
* - **LiveView** is supported → {@link GetLiveViewUrl} returns the runner's MJ-hosted viewer URL (backed
|
|
11
|
+
* by the inherited CDP screencast). It does NOT throw — the seed marks LiveView supported.
|
|
12
|
+
* - **NativeAIControl** is NOT supported → {@link InvokeNativeAIControl} throws
|
|
13
|
+
* {@link RemoteBrowserCapabilityNotSupportedError}; self-host has no first-party AI harness, so MJ's own
|
|
14
|
+
* computer-use loop drives the page.
|
|
15
|
+
* - **Release** always tears down the underlying container handle.
|
|
16
|
+
*
|
|
17
|
+
* @module @memberjunction/remote-browser-selfhost
|
|
18
|
+
* @author MemberJunction.com
|
|
19
|
+
*/
|
|
20
|
+
import { RemoteBrowserActionResult, RemoteBrowserAudioChunk } from '@memberjunction/remote-browser-base';
|
|
21
|
+
import { ICdpAudioCaptureHandle, ICdpSessionBackend } from '@memberjunction/remote-browser-cdp';
|
|
22
|
+
import { PlaywrightBrowserAdapter } from '@memberjunction/computer-use';
|
|
23
|
+
import { ChromeContainerHandle } from './chrome-container-runner.js';
|
|
24
|
+
/**
|
|
25
|
+
* The backend display name carried in capability-error messages from this backend, matching the seed
|
|
26
|
+
* row's `Name`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const SELF_HOST_PROVIDER_NAME = "Self-Hosted Chrome";
|
|
29
|
+
/**
|
|
30
|
+
* Self-Hosted Chrome's {@link ICdpSessionBackend}. Constructed by
|
|
31
|
+
* {@link import('./selfhost-remote-browser.js').SelfHostRemoteBrowser.AcquireSession} around the running
|
|
32
|
+
* container handle.
|
|
33
|
+
*/
|
|
34
|
+
export declare class SelfHostSessionBackend implements ICdpSessionBackend {
|
|
35
|
+
/**
|
|
36
|
+
* The running Chrome container handle this backend wraps — the source of the live-view URL and the
|
|
37
|
+
* teardown hook.
|
|
38
|
+
*/
|
|
39
|
+
private readonly containerHandle;
|
|
40
|
+
/** Guards {@link Release} so the container is torn down at most once even if called repeatedly. */
|
|
41
|
+
private released;
|
|
42
|
+
/**
|
|
43
|
+
* Constructs a {@link SelfHostSessionBackend} around a running container handle.
|
|
44
|
+
*
|
|
45
|
+
* @param containerHandle The handle returned by the Chrome-container runner's `Acquire`.
|
|
46
|
+
*/
|
|
47
|
+
constructor(containerHandle: ChromeContainerHandle);
|
|
48
|
+
/**
|
|
49
|
+
* Begins capturing the browser's tab audio (Self-Hosted Chrome supports it) by delegating to the
|
|
50
|
+
* shared adapter's in-page `MediaRecorder` capture mechanism, mapping each computer-use
|
|
51
|
+
* {@link AudioCaptureChunk} to the Base {@link RemoteBrowserAudioChunk}. The returned handle's
|
|
52
|
+
* `Stop` stops the adapter capture.
|
|
53
|
+
*
|
|
54
|
+
* Implementing this method is what advertises the audio capability for this backend (v1 gates audio
|
|
55
|
+
* by backend implementation, not a metadata flag) — so {@link CdpRemoteBrowserSession.StartAudioStream}
|
|
56
|
+
* starts rather than throws for Self-Hosted Chrome.
|
|
57
|
+
*
|
|
58
|
+
* @param adapter The connected Playwright/CDP adapter driving this session.
|
|
59
|
+
* @param onChunk Callback invoked with each captured audio chunk (Base shape).
|
|
60
|
+
* @returns A promise resolving to a handle whose `Stop` tears the capture down.
|
|
61
|
+
*/
|
|
62
|
+
StartAudioCapture(adapter: PlaywrightBrowserAdapter, onChunk: (chunk: RemoteBrowserAudioChunk) => void): Promise<ICdpAudioCaptureHandle>;
|
|
63
|
+
/**
|
|
64
|
+
* Maps a computer-use {@link AudioCaptureChunk} to the Base {@link RemoteBrowserAudioChunk}. The two
|
|
65
|
+
* shapes are field-identical; this keeps the Base/CDP layers free of any computer-use dependency on the
|
|
66
|
+
* audio path.
|
|
67
|
+
*
|
|
68
|
+
* @param chunk The computer-use audio chunk.
|
|
69
|
+
* @returns The equivalent Base audio chunk.
|
|
70
|
+
*/
|
|
71
|
+
private mapAudioChunk;
|
|
72
|
+
/**
|
|
73
|
+
* Returns the MJ-hosted, embeddable live-view URL — the runner's viewer page, whose frames come from
|
|
74
|
+
* the inherited CDP screencast. Self-host has no provider-hosted live view of its own, so this is
|
|
75
|
+
* MJ's own viewer backed by the screencast (the documented production binding seam).
|
|
76
|
+
*
|
|
77
|
+
* Does NOT throw: the seed row marks LiveView supported for Self-Hosted Chrome.
|
|
78
|
+
*
|
|
79
|
+
* @returns A promise resolving to the MJ-hosted viewer URL.
|
|
80
|
+
*/
|
|
81
|
+
GetLiveViewUrl(): Promise<string>;
|
|
82
|
+
/**
|
|
83
|
+
* Always throws {@link RemoteBrowserCapabilityNotSupportedError}: Self-Hosted Chrome has no
|
|
84
|
+
* first-party AI-control harness, so high-level intents are driven by MJ's own computer-use loop
|
|
85
|
+
* rather than delegated to the backend. Matches the seed row, which does not enable
|
|
86
|
+
* `NativeAIControl`.
|
|
87
|
+
*
|
|
88
|
+
* @param _intent The natural-language intent — ignored; the call is unconditionally unsupported.
|
|
89
|
+
* @returns Never returns; always rejects.
|
|
90
|
+
* @throws {RemoteBrowserCapabilityNotSupportedError} unconditionally.
|
|
91
|
+
*/
|
|
92
|
+
InvokeNativeAIControl(_intent: string): Promise<RemoteBrowserActionResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Tears down the Chrome container behind this session by delegating to the container handle's
|
|
95
|
+
* `Release`. Idempotent — a second call after a successful release is a no-op, so teardown is safe to
|
|
96
|
+
* run from both the session's `Close()` and the provider's `Disconnect()`.
|
|
97
|
+
*
|
|
98
|
+
* @returns A promise that resolves once the container has been released.
|
|
99
|
+
*/
|
|
100
|
+
Release(): Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=selfhost-session-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selfhost-session-backend.d.ts","sourceRoot":"","sources":["../src/selfhost-session-backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACH,yBAAyB,EACzB,uBAAuB,EAE1B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAqB,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,uBAAuB,uBAAuB,CAAC;AAQ5D;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,kBAAkB;IAC7D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAwB;IAExD,mGAAmG;IACnG,OAAO,CAAC,QAAQ,CAAkB;IAElC;;;;OAIG;gBACS,eAAe,EAAE,qBAAqB;IAIlD;;;;;;;;;;;;;OAaG;IACU,iBAAiB,CAC1B,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAClD,OAAO,CAAC,sBAAsB,CAAC;IASlC;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAWrB;;;;;;;;OAQG;IACU,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAI9C;;;;;;;;;OASG;IACU,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAOvF;;;;;;OAMG;IACU,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAOxC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SelfHostSessionBackend` — the Self-Hosted Chrome implementation of the {@link ICdpSessionBackend} hook
|
|
3
|
+
* surface the shared CDP session delegates its three backend-specific concerns to (hosted live-view URL,
|
|
4
|
+
* native-AI delegation, backend release).
|
|
5
|
+
*
|
|
6
|
+
* It wraps the {@link ChromeContainerHandle} returned by the
|
|
7
|
+
* {@link import('./chrome-container-runner.js').IChromeContainerRunner} and answers exactly the
|
|
8
|
+
* Self-Hosted Chrome capability profile (per the `MJ: AI Remote Browser Providers` seed row
|
|
9
|
+
* "Self-Hosted Chrome" / `DriverClass = 'SelfHostRemoteBrowser'`):
|
|
10
|
+
* - **LiveView** is supported → {@link GetLiveViewUrl} returns the runner's MJ-hosted viewer URL (backed
|
|
11
|
+
* by the inherited CDP screencast). It does NOT throw — the seed marks LiveView supported.
|
|
12
|
+
* - **NativeAIControl** is NOT supported → {@link InvokeNativeAIControl} throws
|
|
13
|
+
* {@link RemoteBrowserCapabilityNotSupportedError}; self-host has no first-party AI harness, so MJ's own
|
|
14
|
+
* computer-use loop drives the page.
|
|
15
|
+
* - **Release** always tears down the underlying container handle.
|
|
16
|
+
*
|
|
17
|
+
* @module @memberjunction/remote-browser-selfhost
|
|
18
|
+
* @author MemberJunction.com
|
|
19
|
+
*/
|
|
20
|
+
import { RemoteBrowserCapabilityNotSupportedError, } from '@memberjunction/remote-browser-base';
|
|
21
|
+
/**
|
|
22
|
+
* The backend display name carried in capability-error messages from this backend, matching the seed
|
|
23
|
+
* row's `Name`.
|
|
24
|
+
*/
|
|
25
|
+
export const SELF_HOST_PROVIDER_NAME = 'Self-Hosted Chrome';
|
|
26
|
+
/**
|
|
27
|
+
* The capability key reported when {@link SelfHostSessionBackend.InvokeNativeAIControl} is invoked — the
|
|
28
|
+
* one feature Self-Hosted Chrome deliberately does not support.
|
|
29
|
+
*/
|
|
30
|
+
const NATIVE_AI_CONTROL_FEATURE = 'NativeAIControl';
|
|
31
|
+
/**
|
|
32
|
+
* Self-Hosted Chrome's {@link ICdpSessionBackend}. Constructed by
|
|
33
|
+
* {@link import('./selfhost-remote-browser.js').SelfHostRemoteBrowser.AcquireSession} around the running
|
|
34
|
+
* container handle.
|
|
35
|
+
*/
|
|
36
|
+
export class SelfHostSessionBackend {
|
|
37
|
+
/**
|
|
38
|
+
* Constructs a {@link SelfHostSessionBackend} around a running container handle.
|
|
39
|
+
*
|
|
40
|
+
* @param containerHandle The handle returned by the Chrome-container runner's `Acquire`.
|
|
41
|
+
*/
|
|
42
|
+
constructor(containerHandle) {
|
|
43
|
+
/** Guards {@link Release} so the container is torn down at most once even if called repeatedly. */
|
|
44
|
+
this.released = false;
|
|
45
|
+
this.containerHandle = containerHandle;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Begins capturing the browser's tab audio (Self-Hosted Chrome supports it) by delegating to the
|
|
49
|
+
* shared adapter's in-page `MediaRecorder` capture mechanism, mapping each computer-use
|
|
50
|
+
* {@link AudioCaptureChunk} to the Base {@link RemoteBrowserAudioChunk}. The returned handle's
|
|
51
|
+
* `Stop` stops the adapter capture.
|
|
52
|
+
*
|
|
53
|
+
* Implementing this method is what advertises the audio capability for this backend (v1 gates audio
|
|
54
|
+
* by backend implementation, not a metadata flag) — so {@link CdpRemoteBrowserSession.StartAudioStream}
|
|
55
|
+
* starts rather than throws for Self-Hosted Chrome.
|
|
56
|
+
*
|
|
57
|
+
* @param adapter The connected Playwright/CDP adapter driving this session.
|
|
58
|
+
* @param onChunk Callback invoked with each captured audio chunk (Base shape).
|
|
59
|
+
* @returns A promise resolving to a handle whose `Stop` tears the capture down.
|
|
60
|
+
*/
|
|
61
|
+
async StartAudioCapture(adapter, onChunk) {
|
|
62
|
+
await adapter.StartAudioCapture((chunk) => onChunk(this.mapAudioChunk(chunk)));
|
|
63
|
+
return {
|
|
64
|
+
Stop: async () => {
|
|
65
|
+
await adapter.StopAudioCapture();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Maps a computer-use {@link AudioCaptureChunk} to the Base {@link RemoteBrowserAudioChunk}. The two
|
|
71
|
+
* shapes are field-identical; this keeps the Base/CDP layers free of any computer-use dependency on the
|
|
72
|
+
* audio path.
|
|
73
|
+
*
|
|
74
|
+
* @param chunk The computer-use audio chunk.
|
|
75
|
+
* @returns The equivalent Base audio chunk.
|
|
76
|
+
*/
|
|
77
|
+
mapAudioChunk(chunk) {
|
|
78
|
+
return {
|
|
79
|
+
DataBase64: chunk.DataBase64,
|
|
80
|
+
Codec: chunk.Codec,
|
|
81
|
+
SampleRate: chunk.SampleRate,
|
|
82
|
+
Channels: chunk.Channels,
|
|
83
|
+
SequenceNumber: chunk.SequenceNumber,
|
|
84
|
+
DurationMs: chunk.DurationMs,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the MJ-hosted, embeddable live-view URL — the runner's viewer page, whose frames come from
|
|
89
|
+
* the inherited CDP screencast. Self-host has no provider-hosted live view of its own, so this is
|
|
90
|
+
* MJ's own viewer backed by the screencast (the documented production binding seam).
|
|
91
|
+
*
|
|
92
|
+
* Does NOT throw: the seed row marks LiveView supported for Self-Hosted Chrome.
|
|
93
|
+
*
|
|
94
|
+
* @returns A promise resolving to the MJ-hosted viewer URL.
|
|
95
|
+
*/
|
|
96
|
+
async GetLiveViewUrl() {
|
|
97
|
+
return this.containerHandle.ViewerUrl;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Always throws {@link RemoteBrowserCapabilityNotSupportedError}: Self-Hosted Chrome has no
|
|
101
|
+
* first-party AI-control harness, so high-level intents are driven by MJ's own computer-use loop
|
|
102
|
+
* rather than delegated to the backend. Matches the seed row, which does not enable
|
|
103
|
+
* `NativeAIControl`.
|
|
104
|
+
*
|
|
105
|
+
* @param _intent The natural-language intent — ignored; the call is unconditionally unsupported.
|
|
106
|
+
* @returns Never returns; always rejects.
|
|
107
|
+
* @throws {RemoteBrowserCapabilityNotSupportedError} unconditionally.
|
|
108
|
+
*/
|
|
109
|
+
async InvokeNativeAIControl(_intent) {
|
|
110
|
+
throw new RemoteBrowserCapabilityNotSupportedError(NATIVE_AI_CONTROL_FEATURE, SELF_HOST_PROVIDER_NAME);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Tears down the Chrome container behind this session by delegating to the container handle's
|
|
114
|
+
* `Release`. Idempotent — a second call after a successful release is a no-op, so teardown is safe to
|
|
115
|
+
* run from both the session's `Close()` and the provider's `Disconnect()`.
|
|
116
|
+
*
|
|
117
|
+
* @returns A promise that resolves once the container has been released.
|
|
118
|
+
*/
|
|
119
|
+
async Release() {
|
|
120
|
+
if (this.released) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.released = true;
|
|
124
|
+
await this.containerHandle.Release();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=selfhost-session-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selfhost-session-backend.js","sourceRoot":"","sources":["../src/selfhost-session-backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAGH,wCAAwC,GAC3C,MAAM,qCAAqC,CAAC;AAK7C;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAE5D;;;GAGG;AACH,MAAM,yBAAyB,GAAG,iBAAiB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,OAAO,sBAAsB;IAU/B;;;;OAIG;IACH,YAAY,eAAsC;QARlD,mGAAmG;QAC3F,aAAQ,GAAY,KAAK,CAAC;QAQ9B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IAC3C,CAAC;IAED;;;;;;;;;;;;;OAaG;IACI,KAAK,CAAC,iBAAiB,CAC1B,OAAiC,EACjC,OAAiD;QAEjD,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC,KAAwB,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClG,OAAO;YACH,IAAI,EAAE,KAAK,IAAmB,EAAE;gBAC5B,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAC;YACrC,CAAC;SACJ,CAAC;IACN,CAAC;IAED;;;;;;;OAOG;IACK,aAAa,CAAC,KAAwB;QAC1C,OAAO;YACH,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,UAAU,EAAE,KAAK,CAAC,UAAU;SAC/B,CAAC;IACN,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,cAAc;QACvB,OAAO,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC;IAC1C,CAAC;IAED;;;;;;;;;OASG;IACI,KAAK,CAAC,qBAAqB,CAAC,OAAe;QAC9C,MAAM,IAAI,wCAAwC,CAC9C,yBAAyB,EACzB,uBAAuB,CAC1B,CAAC;IACN,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,OAAO;QAChB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,OAAO;QACX,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;IACzC,CAAC;CACJ"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/remote-browser-selfhost",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "5.41.0",
|
|
5
|
+
"description": "MemberJunction: Self-Hosted Chrome backend driver for the Remote Browser channel. Connects the shared CDP control kit to a lightweight MJ-orchestrated headless-Chrome container (raw CDP control, MJ-hosted live view backed by the screencast, human takeover, screen streaming, persistent context, multi-tab, file downloads — no native AI control) via an injectable Chrome-container-runner seam, so the driver builds and unit-tests with no container, no network, and no real browser.",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"/dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start": "ts-node-dev src/index.ts",
|
|
13
|
+
"build": "tsc && tsc-alias -f",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest"
|
|
16
|
+
},
|
|
17
|
+
"author": "MemberJunction.com",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@memberjunction/computer-use": "5.41.0",
|
|
21
|
+
"@memberjunction/core": "5.41.0",
|
|
22
|
+
"@memberjunction/global": "5.41.0",
|
|
23
|
+
"@memberjunction/remote-browser-base": "5.41.0",
|
|
24
|
+
"@memberjunction/remote-browser-cdp": "5.41.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"playwright": ">=1.40.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"playwright": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "24.10.11",
|
|
36
|
+
"playwright": "^1.40.0",
|
|
37
|
+
"ts-node-dev": "^2.0.0",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"vitest": "^4.0.18"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/MemberJunction/MJ"
|
|
44
|
+
}
|
|
10
45
|
}
|