@effect-uai/browser 0.9.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Betalyra Sociedade Unipessoal Lda.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ import * as CoreBrowser from "@effect-uai/core/Browser";
2
+ import { Context, Layer } from "effect";
3
+
4
+ //#region src/Connect.d.ts
5
+ declare namespace Connect_d_exports {
6
+ export { CdpBrowser, CdpConnectConfig, layer };
7
+ }
8
+ type CdpConnectConfig = {
9
+ /**
10
+ * Browser-level CDP WebSocket endpoint, e.g.
11
+ * `ws://127.0.0.1:9222/devtools/browser/<uuid>` for local Chromium or
12
+ * obscura, or a hosted provider's connect URL (credentials in the URL).
13
+ */
14
+ readonly endpoint: string;
15
+ };
16
+ declare const CdpBrowser_base: Context.ServiceClass<CdpBrowser, "@betalyra/effect-uai/providers/browser/CdpBrowser", CoreBrowser.BrowserService>;
17
+ /**
18
+ * Provider tag. Identical surface to the core `Browser` service; both tags
19
+ * are registered by {@link layer}.
20
+ */
21
+ declare class CdpBrowser extends CdpBrowser_base {}
22
+ /**
23
+ * Registers the generic CDP adapter against both the provider tag
24
+ * ({@link CdpBrowser}) and the core `Browser` tag. No capability markers:
25
+ * proxy / stealth / recording are vendor features, not part of a bare CDP
26
+ * endpoint.
27
+ */
28
+ declare const layer: (config: CdpConnectConfig) => Layer.Layer<CdpBrowser | CoreBrowser.Browser>;
29
+ //#endregion
30
+ export { CdpBrowser, CdpConnectConfig, layer, Connect_d_exports as t };
31
+ //# sourceMappingURL=Connect.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Connect.d.mts","names":[],"sources":["../src/Connect.ts"],"mappings":";;;;;;;KAyBY,gBAAA;;;;;;WAMD,QAAA;AAAA;AAAA,cACV,eAAA;;;AAMD;;cAAa,UAAA,SAAmB,eAAA;;AAuIhC;;;;;cAAa,KAAA,GAAS,MAAA,EAAQ,gBAAA,KAAmB,KAAA,CAAM,KAAA,CAAM,UAAA,GAAa,WAAA,CAAY,OAAA"}
@@ -0,0 +1,122 @@
1
+ import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
+ import { openCdp } from "./internal/cdp.mjs";
3
+ import { isMethodMissing, makeSession } from "./internal/session.mjs";
4
+ import * as CoreBrowser from "@effect-uai/core/Browser";
5
+ import * as BrowserError from "@effect-uai/core/BrowserError";
6
+ import { Context, Effect, Layer } from "effect";
7
+ //#region src/Connect.ts
8
+ /**
9
+ * Generic CDP connect provider.
10
+ *
11
+ * Point it at any browser-level CDP WebSocket endpoint and it registers
12
+ * the core `Browser` capability over it. This is the shared floor for the
13
+ * whole field: a locally launched Chromium (`--remote-debugging-port`),
14
+ * obscura's partial CDP server, or a hosted vendor's `wss://` connect URL
15
+ * pasted straight in. Vendor-specific packages (session-create REST call,
16
+ * auth handshake) can layer on top and reuse this data plane.
17
+ *
18
+ * Sessions map to CDP targets: `create` opens a fresh target (tab),
19
+ * `attach` re-attaches to an existing one by target id (finalizer detaches
20
+ * without closing, for warm reuse), `destroy` closes the target from
21
+ * anywhere. The `timeout` / `idleTimeout` knobs on the create request are
22
+ * enforced locally by the session's reaper fibers; a plain CDP endpoint
23
+ * has no server-side cap to mirror them to.
24
+ */
25
+ var Connect_exports = /* @__PURE__ */ __exportAll({
26
+ CdpBrowser: () => CdpBrowser,
27
+ layer: () => layer
28
+ });
29
+ const PROVIDER = "cdp";
30
+ /**
31
+ * Provider tag. Identical surface to the core `Browser` service; both tags
32
+ * are registered by {@link layer}.
33
+ */
34
+ var CdpBrowser = class extends Context.Service()("@betalyra/effect-uai/providers/browser/CdpBrowser") {};
35
+ const mapCreate = (e) => new BrowserError.BrowserCreateFailed({
36
+ provider: PROVIDER,
37
+ ...e.reason !== void 0 ? { reason: e.reason } : {},
38
+ raw: e
39
+ });
40
+ const mapLookup = (id) => (e) => e.kind === "reply" && /no target|not found|invalid target|no session/i.test(e.reason ?? "") ? new BrowserError.BrowserSessionNotFound({
41
+ provider: PROVIDER,
42
+ id
43
+ }) : mapCreate(e);
44
+ /**
45
+ * Enable the domains the session verbs rely on. Partial CDP servers that
46
+ * auto-enable (or don't know an `enable` method) are tolerated: a
47
+ * method-missing reply is not an error.
48
+ */
49
+ const enableDomains = (cdp, sessionId) => Effect.forEach([
50
+ "Page.enable",
51
+ "Runtime.enable",
52
+ "Network.enable"
53
+ ], (method) => cdp.send(method, {}, sessionId).pipe(Effect.catchIf(isMethodMissing, () => Effect.succeed({})))).pipe(Effect.asVoid, Effect.mapError(mapCreate));
54
+ const applyViewport = (cdp, sessionId, viewport) => cdp.send("Emulation.setDeviceMetricsOverride", {
55
+ width: viewport.width,
56
+ height: viewport.height,
57
+ deviceScaleFactor: 1,
58
+ mobile: false
59
+ }, sessionId).pipe(Effect.catchIf(isMethodMissing, () => Effect.succeed({})), Effect.asVoid, Effect.mapError(mapCreate));
60
+ const makeService = (config) => ({
61
+ create: (request) => Effect.gen(function* () {
62
+ const cdp = yield* openCdp(config.endpoint);
63
+ const { targetId } = yield* cdp.send("Target.createTarget", { url: "about:blank" }).pipe(Effect.mapError(mapCreate));
64
+ const closeTarget = cdp.send("Target.closeTarget", { targetId }).pipe(Effect.ignore);
65
+ yield* Effect.addFinalizer(() => closeTarget);
66
+ const { sessionId } = yield* cdp.send("Target.attachToTarget", {
67
+ targetId,
68
+ flatten: true
69
+ }).pipe(Effect.mapError(mapCreate));
70
+ yield* enableDomains(cdp, sessionId);
71
+ if (request.viewport !== void 0) yield* applyViewport(cdp, sessionId, request.viewport);
72
+ return yield* makeSession({
73
+ cdp,
74
+ id: CoreBrowser.BrowserSessionId(targetId),
75
+ sessionId,
76
+ provider: PROVIDER,
77
+ request,
78
+ dispose: closeTarget
79
+ });
80
+ }),
81
+ attach: (id) => Effect.gen(function* () {
82
+ const cdp = yield* openCdp(config.endpoint);
83
+ const { sessionId } = yield* cdp.send("Target.attachToTarget", {
84
+ targetId: id,
85
+ flatten: true
86
+ }).pipe(Effect.mapError(mapLookup(id)));
87
+ yield* Effect.addFinalizer(() => cdp.send("Target.detachFromTarget", { sessionId }).pipe(Effect.ignore));
88
+ yield* enableDomains(cdp, sessionId);
89
+ return yield* makeSession({
90
+ cdp,
91
+ id,
92
+ sessionId,
93
+ provider: PROVIDER,
94
+ request: {},
95
+ dispose: Effect.void
96
+ });
97
+ }),
98
+ list: Effect.scoped(Effect.gen(function* () {
99
+ const { targetInfos } = yield* (yield* openCdp(config.endpoint)).send("Target.getTargets").pipe(Effect.mapError(mapCreate));
100
+ return targetInfos.filter((target) => target.type === "page").map((target) => ({
101
+ id: CoreBrowser.BrowserSessionId(target.targetId),
102
+ ...target.url !== "" ? { url: target.url } : {}
103
+ }));
104
+ })),
105
+ destroy: (id) => Effect.scoped(Effect.gen(function* () {
106
+ yield* (yield* openCdp(config.endpoint)).send("Target.closeTarget", { targetId: id }).pipe(Effect.mapError(mapLookup(id)));
107
+ }))
108
+ });
109
+ /**
110
+ * Registers the generic CDP adapter against both the provider tag
111
+ * ({@link CdpBrowser}) and the core `Browser` tag. No capability markers:
112
+ * proxy / stealth / recording are vendor features, not part of a bare CDP
113
+ * endpoint.
114
+ */
115
+ const layer = (config) => {
116
+ const service = makeService(config);
117
+ return Layer.mergeAll(Layer.succeed(CdpBrowser, service), Layer.succeed(CoreBrowser.Browser, service));
118
+ };
119
+ //#endregion
120
+ export { CdpBrowser, layer, Connect_exports as t };
121
+
122
+ //# sourceMappingURL=Connect.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Connect.mjs","names":[],"sources":["../src/Connect.ts"],"sourcesContent":["/**\n * Generic CDP connect provider.\n *\n * Point it at any browser-level CDP WebSocket endpoint and it registers\n * the core `Browser` capability over it. This is the shared floor for the\n * whole field: a locally launched Chromium (`--remote-debugging-port`),\n * obscura's partial CDP server, or a hosted vendor's `wss://` connect URL\n * pasted straight in. Vendor-specific packages (session-create REST call,\n * auth handshake) can layer on top and reuse this data plane.\n *\n * Sessions map to CDP targets: `create` opens a fresh target (tab),\n * `attach` re-attaches to an existing one by target id (finalizer detaches\n * without closing, for warm reuse), `destroy` closes the target from\n * anywhere. The `timeout` / `idleTimeout` knobs on the create request are\n * enforced locally by the session's reaper fibers; a plain CDP endpoint\n * has no server-side cap to mirror them to.\n */\nimport * as CoreBrowser from \"@effect-uai/core/Browser\"\nimport * as BrowserError from \"@effect-uai/core/BrowserError\"\nimport { Context, Effect, Layer } from \"effect\"\nimport { type Cdp, type CdpError, openCdp } from \"./internal/cdp.js\"\nimport { isMethodMissing, makeSession } from \"./internal/session.js\"\n\nconst PROVIDER = \"cdp\"\n\nexport type CdpConnectConfig = {\n /**\n * Browser-level CDP WebSocket endpoint, e.g.\n * `ws://127.0.0.1:9222/devtools/browser/<uuid>` for local Chromium or\n * obscura, or a hosted provider's connect URL (credentials in the URL).\n */\n readonly endpoint: string\n}\n\n/**\n * Provider tag. Identical surface to the core `Browser` service; both tags\n * are registered by {@link layer}.\n */\nexport class CdpBrowser extends Context.Service<CdpBrowser, CoreBrowser.BrowserService>()(\n \"@betalyra/effect-uai/providers/browser/CdpBrowser\",\n) {}\n\n// ---------------------------------------------------------------------------\n// Error mapping (control-plane operations; the session maps its own).\n// ---------------------------------------------------------------------------\n\nconst mapCreate = (e: CdpError): BrowserError.BrowserError =>\n new BrowserError.BrowserCreateFailed({\n provider: PROVIDER,\n ...(e.reason !== undefined ? { reason: e.reason } : {}),\n raw: e,\n })\n\nconst mapLookup =\n (id: string) =>\n (e: CdpError): BrowserError.BrowserError =>\n e.kind === \"reply\" && /no target|not found|invalid target|no session/i.test(e.reason ?? \"\")\n ? new BrowserError.BrowserSessionNotFound({ provider: PROVIDER, id })\n : mapCreate(e)\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\n/**\n * Enable the domains the session verbs rely on. Partial CDP servers that\n * auto-enable (or don't know an `enable` method) are tolerated: a\n * method-missing reply is not an error.\n */\nconst enableDomains = (cdp: Cdp, sessionId: string) =>\n Effect.forEach([\"Page.enable\", \"Runtime.enable\", \"Network.enable\"] as const, (method) =>\n cdp.send(method, {}, sessionId).pipe(Effect.catchIf(isMethodMissing, () => Effect.succeed({}))),\n ).pipe(Effect.asVoid, Effect.mapError(mapCreate))\n\nconst applyViewport = (\n cdp: Cdp,\n sessionId: string,\n viewport: NonNullable<CoreBrowser.CommonSessionRequest[\"viewport\"]>,\n) =>\n cdp\n .send(\n \"Emulation.setDeviceMetricsOverride\",\n { width: viewport.width, height: viewport.height, deviceScaleFactor: 1, mobile: false },\n sessionId,\n )\n .pipe(\n // No Emulation domain: the engine's default viewport stands.\n Effect.catchIf(isMethodMissing, () => Effect.succeed({})),\n Effect.asVoid,\n Effect.mapError(mapCreate),\n )\n\nconst makeService = (config: CdpConnectConfig): CoreBrowser.BrowserService => ({\n create: (request) =>\n Effect.gen(function* () {\n const cdp = yield* openCdp(config.endpoint)\n const { targetId } = yield* cdp\n .send(\"Target.createTarget\", { url: \"about:blank\" })\n .pipe(Effect.mapError(mapCreate))\n const closeTarget = cdp.send(\"Target.closeTarget\", { targetId }).pipe(Effect.ignore)\n yield* Effect.addFinalizer(() => closeTarget)\n const { sessionId } = yield* cdp\n .send(\"Target.attachToTarget\", { targetId, flatten: true })\n .pipe(Effect.mapError(mapCreate))\n yield* enableDomains(cdp, sessionId)\n if (request.viewport !== undefined) {\n yield* applyViewport(cdp, sessionId, request.viewport)\n }\n return yield* makeSession({\n cdp,\n id: CoreBrowser.BrowserSessionId(targetId),\n sessionId,\n provider: PROVIDER,\n request,\n dispose: closeTarget,\n })\n }),\n\n attach: (id) =>\n Effect.gen(function* () {\n const cdp = yield* openCdp(config.endpoint)\n const { sessionId } = yield* cdp\n .send(\"Target.attachToTarget\", { targetId: id, flatten: true })\n .pipe(Effect.mapError(mapLookup(id)))\n // Detach on scope close; the target keeps running for warm reuse.\n yield* Effect.addFinalizer(() =>\n cdp.send(\"Target.detachFromTarget\", { sessionId }).pipe(Effect.ignore),\n )\n yield* enableDomains(cdp, sessionId)\n return yield* makeSession({\n cdp,\n id,\n sessionId,\n provider: PROVIDER,\n request: {},\n dispose: Effect.void,\n })\n }),\n\n list: Effect.scoped(\n Effect.gen(function* () {\n const cdp = yield* openCdp(config.endpoint)\n const { targetInfos } = yield* cdp.send(\"Target.getTargets\").pipe(Effect.mapError(mapCreate))\n return targetInfos\n .filter((target) => target.type === \"page\")\n .map(\n (target): CoreBrowser.BrowserSessionRef => ({\n id: CoreBrowser.BrowserSessionId(target.targetId),\n ...(target.url !== \"\" ? { url: target.url } : {}),\n }),\n )\n }),\n ),\n\n destroy: (id) =>\n Effect.scoped(\n Effect.gen(function* () {\n const cdp = yield* openCdp(config.endpoint)\n yield* cdp.send(\"Target.closeTarget\", { targetId: id }).pipe(Effect.mapError(mapLookup(id)))\n }),\n ),\n})\n\n// ---------------------------------------------------------------------------\n// Layer\n// ---------------------------------------------------------------------------\n\n/**\n * Registers the generic CDP adapter against both the provider tag\n * ({@link CdpBrowser}) and the core `Browser` tag. No capability markers:\n * proxy / stealth / recording are vendor features, not part of a bare CDP\n * endpoint.\n */\nexport const layer = (config: CdpConnectConfig): Layer.Layer<CdpBrowser | CoreBrowser.Browser> => {\n const service = makeService(config)\n return Layer.mergeAll(\n Layer.succeed(CdpBrowser, service),\n Layer.succeed(CoreBrowser.Browser, service),\n )\n}\n"],"mappings":"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"}