@effect-agent/platform-cloudflare 0.1.0-beta.13 → 0.1.0-beta.131
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/dist/Alarm.d.mts +244 -0
- package/dist/Alarm.mjs +947 -0
- package/dist/Alarm.mjs.map +1 -0
- package/dist/BrowserCredentials.d.mts +91 -0
- package/dist/BrowserCredentials.mjs +120 -0
- package/dist/BrowserCredentials.mjs.map +1 -0
- package/dist/BrowserRestCapture.d.mts +34 -0
- package/dist/BrowserRestCapture.mjs +238 -0
- package/dist/BrowserRestCapture.mjs.map +1 -0
- package/dist/BrowserRestCrawl.d.mts +16 -0
- package/dist/BrowserRestCrawl.mjs +244 -0
- package/dist/BrowserRestCrawl.mjs.map +1 -0
- package/dist/BrowserSession-DP6b-cpL.d.mts +125 -0
- package/dist/BrowserSession-DWoRC_iM.mjs +505 -0
- package/dist/BrowserSession-DWoRC_iM.mjs.map +1 -0
- package/dist/BrowserSession.d.mts +3 -0
- package/dist/BrowserSession.mjs +4 -0
- package/dist/CloudflareAiGateway.d.mts +64 -0
- package/dist/CloudflareAiGateway.mjs +70 -0
- package/dist/CloudflareAiGateway.mjs.map +1 -0
- package/dist/CloudflareBindings.d.mts +115 -0
- package/dist/CloudflareBindings.mjs +113 -0
- package/dist/CloudflareBindings.mjs.map +1 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs +495 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs.map +1 -0
- package/dist/CloudflareBrowser-Doe1M7R5.d.mts +80 -0
- package/dist/CloudflareBrowser.d.mts +2 -0
- package/dist/CloudflareBrowser.mjs +2 -0
- package/dist/CloudflareCodeMode.d.mts +44 -0
- package/dist/CloudflareCodeMode.mjs +616 -0
- package/dist/CloudflareCodeMode.mjs.map +1 -0
- package/dist/CloudflareConfig-DKXGo8L3.d.mts +106 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +128 -0
- package/dist/CloudflareConfig.mjs.map +1 -0
- package/dist/CloudflareMemory.d.mts +142 -0
- package/dist/CloudflareMemory.mjs +202 -0
- package/dist/CloudflareMemory.mjs.map +1 -0
- package/dist/CloudflareScheduling.d.mts +52 -0
- package/dist/CloudflareScheduling.mjs +396 -0
- package/dist/CloudflareScheduling.mjs.map +1 -0
- package/dist/CloudflareSubscriptions.d.mts +87 -0
- package/dist/CloudflareSubscriptions.mjs +530 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1752 -0
- package/dist/CloudflareThreadClient.mjs +382 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser-BnqY_Y-F.d.mts +228 -0
- package/dist/InteractiveBrowser-CxFT-Nwm.mjs +1923 -0
- package/dist/InteractiveBrowser-CxFT-Nwm.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +2 -0
- package/dist/InteractiveBrowser.mjs +2 -0
- package/dist/ThreadObject-CVhZC-7l.mjs +849 -0
- package/dist/ThreadObject-CVhZC-7l.mjs.map +1 -0
- package/dist/ThreadObject-D8GzJxJB.d.mts +839 -0
- package/dist/ThreadObject.d.mts +2 -0
- package/dist/ThreadObject.mjs +3 -0
- package/dist/WakeScheduler.d.mts +23 -0
- package/dist/WakeScheduler.mjs +57 -0
- package/dist/WakeScheduler.mjs.map +1 -0
- package/dist/boundary-BguazVkh.mjs +42 -0
- package/dist/boundary-BguazVkh.mjs.map +1 -0
- package/dist/index.d.mts +15 -1550
- package/dist/index.mjs +15 -1716
- package/dist/prepared-admission-DU0_T-ev.mjs +73 -0
- package/dist/prepared-admission-DU0_T-ev.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -56
- package/src/Alarm.ts +1979 -0
- package/src/BrowserCredentials.ts +146 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/BrowserSession.ts +624 -0
- package/src/CloudflareAiGateway.ts +170 -0
- package/src/CloudflareBindings.ts +199 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +308 -283
- package/src/{config.ts → CloudflareConfig.ts} +19 -12
- package/src/CloudflareMemory.ts +420 -0
- package/src/CloudflareScheduling.ts +747 -0
- package/src/CloudflareSubscriptions.ts +1041 -0
- package/src/CloudflareThreadClient.ts +809 -0
- package/src/InteractiveBrowser.ts +3246 -0
- package/src/ThreadObject.ts +1190 -0
- package/src/{wake-scheduler.ts → WakeScheduler.ts} +32 -26
- package/src/index.ts +14 -23
- package/src/internal/boundary.ts +55 -0
- package/src/internal/browser-binding.ts +322 -0
- package/src/internal/browser-credentials.ts +362 -0
- package/src/internal/browser-failure.ts +92 -0
- package/src/internal/browser-file-selection.ts +126 -0
- package/src/internal/browser-quick-action.ts +857 -0
- package/src/internal/browser-readonly-live-view.ts +111 -0
- package/src/internal/browser-session-lifecycle.ts +193 -0
- package/src/internal/layers.ts +773 -0
- package/src/internal/message-delivery.ts +131 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +44 -0
- package/dist/index.mjs.map +0 -1
- package/src/alarm.ts +0 -334
- package/src/bindings.ts +0 -145
- package/src/client.ts +0 -646
- package/src/conversation-object.ts +0 -768
- package/src/layers.ts +0 -376
- package/src/transport.ts +0 -38
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { BrowserCredentialAccess, CredentialFillError, CredentialFillResult, FillCredentialRequest } from "./BrowserCredentials.mjs";
|
|
2
|
+
import { A as BrowserRunSessionLifecycle, O as BrowserRunCleanupError, _ as BrowserRunLiveViewRequest, i as BrowserRunHandoffState, k as BrowserRunLifecycleOptions, n as BrowserRunHandoffRequest, r as BrowserRunHandoffResult, v as BrowserRunLiveViewResult } from "./InteractiveBrowser-BnqY_Y-F.mjs";
|
|
3
|
+
import { Cause, Context, Effect, ErrorReporter, Layer, Redacted, Schema, Scope } from "effect";
|
|
4
|
+
import { HttpClient } from "effect/unstable/http";
|
|
5
|
+
import { Browser, Page } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
|
|
6
|
+
//#region src/internal/browser-failure.d.ts
|
|
7
|
+
declare const BrowserRunFailure_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Cause.YieldableError & {
|
|
8
|
+
readonly _tag: "BrowserRunFailure";
|
|
9
|
+
} & Readonly<A>;
|
|
10
|
+
/** Source-authored stages and classifications only; never retain SDK text or session capabilities. */
|
|
11
|
+
declare class BrowserRunFailure extends BrowserRunFailure_base<{
|
|
12
|
+
readonly operation: string;
|
|
13
|
+
readonly reason: "provider" | "timeout" | "malformed" | "configuration" | "authorization" | "rate-limited" | "pending";
|
|
14
|
+
readonly status?: number;
|
|
15
|
+
readonly cause?: BrowserRunFailure | {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
};
|
|
18
|
+
}> {
|
|
19
|
+
readonly [ErrorReporter.severity]: "Error";
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/internal/browser-binding.d.ts
|
|
23
|
+
/** Private attachment ownership is available before asynchronous SDK initialization completes. */
|
|
24
|
+
interface BrowserRunAttachment {
|
|
25
|
+
readonly browser: Promise<Browser>;
|
|
26
|
+
/** Fence SDK dispatch/callbacks and await local socket closure; never terminate the provider. */
|
|
27
|
+
readonly retire: Effect.Effect<void, BrowserRunFailure>;
|
|
28
|
+
}
|
|
29
|
+
declare const BrowserRunBinding_base: Context.ServiceClass<BrowserRunBinding, "@effect-agent/platform-cloudflare/internal/BrowserRunBinding", {
|
|
30
|
+
readonly acquire: (keepAliveMillis: number, operation: "interactive.acquire" | "session.acquire") => Promise<string>;
|
|
31
|
+
readonly connect: (sessionId: string, operation: string, signal?: AbortSignal) => BrowserRunAttachment;
|
|
32
|
+
readonly keepAlive: (sessionId: string) => Effect.Effect<void, BrowserRunFailure>;
|
|
33
|
+
}>;
|
|
34
|
+
/** Native transport port shared by both scoped adapters; the Layer owns the foreign binding. */
|
|
35
|
+
declare class BrowserRunBinding extends BrowserRunBinding_base {
|
|
36
|
+
static layer(browser: Pick<BrowserRun, "fetch">): Layer.Layer<BrowserRunBinding, never, never>;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/internal/browser-readonly-live-view.d.ts
|
|
40
|
+
declare const BrowserRunReadonlyLiveView_base: Context.ServiceClass<BrowserRunReadonlyLiveView, "@effect-agent/platform-cloudflare/internal/BrowserRunReadonlyLiveView", {
|
|
41
|
+
readonly mint: (sessionId: Redacted.Redacted<string>, targetId: Redacted.Redacted<string>, request: BrowserRunLiveViewRequest) => Effect.Effect<BrowserRunLiveViewResult, BrowserRunFailure>;
|
|
42
|
+
}>;
|
|
43
|
+
/** Only the REST endpoint can mint provider-enforced read-only connections. */
|
|
44
|
+
declare class BrowserRunReadonlyLiveView extends BrowserRunReadonlyLiveView_base {
|
|
45
|
+
static layer(options: BrowserRunLifecycleOptions): Layer.Layer<BrowserRunReadonlyLiveView, never, HttpClient.HttpClient>;
|
|
46
|
+
}
|
|
47
|
+
declare namespace BrowserSession_d_exports {
|
|
48
|
+
export { BrowserRunHandoffRequest, BrowserRunHandoffResult, BrowserRunHandoffState, BrowserRunLiveViewRequest, BrowserRunLiveViewResult, BrowserSession, BrowserSessionError, BrowserSessionOptions, BrowserSessionReference, BrowserSessions, BrowserSessionsOptions };
|
|
49
|
+
}
|
|
50
|
+
declare const BrowserSessionReference_base: Schema.Class<BrowserSessionReference, Schema.Struct<{
|
|
51
|
+
readonly version: Schema.Literal<1>;
|
|
52
|
+
readonly sessionId: Schema.Redacted<Schema.String>;
|
|
53
|
+
readonly contextId: Schema.Redacted<Schema.NonEmptyString>;
|
|
54
|
+
readonly targetId: Schema.Redacted<Schema.NonEmptyString>;
|
|
55
|
+
readonly expiresAt: Schema.Int;
|
|
56
|
+
readonly commandTimeoutMillis: Schema.Int;
|
|
57
|
+
}>, {}>;
|
|
58
|
+
/** Private host record, not a model capability. Persist together with the owner's controller state. */
|
|
59
|
+
declare class BrowserSessionReference extends BrowserSessionReference_base {}
|
|
60
|
+
declare const BrowserSessionOptions_base: Schema.Class<BrowserSessionOptions, Schema.Struct<{
|
|
61
|
+
readonly maxElapsedMillis: Schema.Int;
|
|
62
|
+
readonly keepAliveMillis: Schema.optionalKey<Schema.Int>;
|
|
63
|
+
readonly commandTimeoutMillis: Schema.optionalKey<Schema.Int>;
|
|
64
|
+
}>, {}>;
|
|
65
|
+
declare class BrowserSessionOptions extends BrowserSessionOptions_base {}
|
|
66
|
+
declare const BrowserSessionError_base: Schema.Class<BrowserSessionError, Schema.TaggedStruct<"BrowserSessionError", {
|
|
67
|
+
readonly reason: Schema.Literals<readonly ["invalid", "expired", "missing-page", "busy", "closed", "provider", "timeout", "cleanup"]>;
|
|
68
|
+
readonly dispatch: Schema.Literals<readonly ["not-dispatched", "possibly-dispatched"]>;
|
|
69
|
+
readonly cleanup: Schema.Literals<readonly ["not-requested", "confirmed", "unconfirmed"]>;
|
|
70
|
+
}>, Cause.YieldableError>;
|
|
71
|
+
/** No SDK text, selectors, page data, or credential values are retained in public failures. */
|
|
72
|
+
declare class BrowserSessionError extends BrowserSessionError_base {}
|
|
73
|
+
/**
|
|
74
|
+
* One local attachment to an application-owned browser. Scope release disconnects; it never
|
|
75
|
+
* transfers ownership or closes a healthy remote session. The owner serializes attachments,
|
|
76
|
+
* fences old attempts, authorizes human access, and calls BrowserSessions.close on expiry/stop.
|
|
77
|
+
* Native callbacks are trusted host code: never retain SDK handles or start unawaited work.
|
|
78
|
+
*/
|
|
79
|
+
interface BrowserSession {
|
|
80
|
+
readonly reference: BrowserSessionReference;
|
|
81
|
+
/**
|
|
82
|
+
* Check current authority under the lock before native dispatch. A settled SDK rejection leaves
|
|
83
|
+
* the session available for inspection, but may have changed the website; never retry blindly.
|
|
84
|
+
* Timeout/interruption fences outstanding SDK work and terminates the exact browser.
|
|
85
|
+
*/
|
|
86
|
+
readonly run: <A, E, R>(authorize: Effect.Effect<void, E, R>, action: (page: Page) => Promise<A>) => Effect.Effect<A, E | BrowserSessionError, R>;
|
|
87
|
+
/**
|
|
88
|
+
* Resolves host-owned material under fresh credential grants. Never submits or retries a write.
|
|
89
|
+
* Credential timeouts retain acknowledged writes and dispatch evidence alongside cleanup.
|
|
90
|
+
*/
|
|
91
|
+
readonly fillCredential: (request: FillCredentialRequest) => Effect.Effect<CredentialFillResult, CredentialFillError | BrowserSessionError, BrowserCredentialAccess>;
|
|
92
|
+
readonly handoff: <E, R>(authorize: Effect.Effect<void, E, R>, request: BrowserRunHandoffRequest) => Effect.Effect<BrowserRunHandoffResult, E | BrowserSessionError, R>;
|
|
93
|
+
readonly getLiveView: <E, R>(authorize: Effect.Effect<void, E, R>, request: BrowserRunLiveViewRequest) => Effect.Effect<BrowserRunLiveViewResult, E | BrowserSessionError, R>;
|
|
94
|
+
/**
|
|
95
|
+
* Mint a provider-enforced read-only connection to this exact page. Requires REST credentials;
|
|
96
|
+
* missing read-only confirmation fails closed, never falling back to an interactive URL.
|
|
97
|
+
* Expiry limits new connections, not established connections or access to visible page data.
|
|
98
|
+
*/
|
|
99
|
+
readonly getReadOnlyLiveView: <E, R>(authorize: Effect.Effect<void, E, R>, request: BrowserRunLiveViewRequest) => Effect.Effect<BrowserRunLiveViewResult, E | BrowserSessionError, R>;
|
|
100
|
+
readonly getHandoffState: <E, R>(authorize: Effect.Effect<void, E, R>) => Effect.Effect<BrowserRunHandoffState, E | BrowserSessionError, R>;
|
|
101
|
+
}
|
|
102
|
+
interface BrowserSessionsOptions extends BrowserRunLifecycleOptions {
|
|
103
|
+
readonly browser: Pick<BrowserRun, "fetch">;
|
|
104
|
+
}
|
|
105
|
+
declare const BrowserSessions_base: Context.ServiceClass<BrowserSessions, "@effect-agent/platform-cloudflare/BrowserSessions", {
|
|
106
|
+
/**
|
|
107
|
+
* Allocate once and commit its private reference in retain. Failed/uncommitted creation closes
|
|
108
|
+
* the allocation. After retain succeeds the application owns remote cleanup, independent of
|
|
109
|
+
* attempt Scopes. A lost allocation reply can remain indeterminate until provider idle expiry.
|
|
110
|
+
*/
|
|
111
|
+
readonly create: <E, R>(options: BrowserSessionOptions, retain: (reference: BrowserSessionReference) => Effect.Effect<void, E, R>) => Effect.Effect<BrowserSessionReference, E | BrowserSessionError, R>;
|
|
112
|
+
/** Attach only the exact saved context/page. Never recreate missing/expired state. */
|
|
113
|
+
readonly attach: (reference: BrowserSessionReference) => Effect.Effect<BrowserSession, BrowserSessionError, Scope.Scope>;
|
|
114
|
+
/** Refresh provider inactivity only; the owner must still enforce its expiresAt. */
|
|
115
|
+
readonly keepAlive: (sessionId: Redacted.Redacted<string>) => Effect.Effect<void, BrowserSessionError>;
|
|
116
|
+
readonly close: (sessionId: Redacted.Redacted<string>) => Effect.Effect<void, BrowserSessionError>;
|
|
117
|
+
}>;
|
|
118
|
+
/** Native Cloudflare sessions; no framework browser workflow, checkpoint transfer, or observation policy. */
|
|
119
|
+
declare class BrowserSessions extends BrowserSessions_base {
|
|
120
|
+
static readonly layerNoDeps: Layer.Layer<BrowserSessions, never, BrowserRunBinding | BrowserRunReadonlyLiveView | BrowserRunSessionLifecycle>;
|
|
121
|
+
static layer(options: BrowserSessionsOptions): Layer.Layer<BrowserSessions, BrowserRunCleanupError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { BrowserSession_d_exports as a, BrowserSessionReference as i, BrowserSessionError as n, BrowserSessions as o, BrowserSessionOptions as r, BrowserSessionsOptions as s, BrowserSession as t };
|
|
125
|
+
//# sourceMappingURL=BrowserSession-DP6b-cpL.d.mts.map
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { BrowserCredentialAccess, CardCredential, CredentialFillError, CredentialFillResult, CredentialOrigin, CredentialTarget, FillCredentialRequest, LoginCredential } from "./BrowserCredentials.mjs";
|
|
3
|
+
import { C as reportedBrowserError, S as reportBrowserCause, b as BrowserRunFailure, c as BrowserRunLiveViewResult, n as BrowserRunHandoffResult, r as BrowserRunHandoffState, s as BrowserRunLiveViewRequest, t as BrowserRunHandoffRequest, v as BrowserRunSessionLifecycle, x as browserFailure, y as BrowserRunBinding } from "./InteractiveBrowser-CxFT-Nwm.mjs";
|
|
4
|
+
import { Cause, Clock, Context, Effect, Exit, Layer, Redacted, Schema, Scope, Semaphore, Stream } from "effect";
|
|
5
|
+
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
6
|
+
//#region src/internal/browser-credentials.ts
|
|
7
|
+
/** Supplied only under the attachment lock, with the remaining command/session budget. */
|
|
8
|
+
var BrowserSessionPage = class extends Context.Service()("@effect-agent/platform-cloudflare/internal/BrowserSessionPage") {};
|
|
9
|
+
const TargetReply = Schema.Union([Schema.Struct({ action: Schema.String.check(Schema.isMaxLength(2048)) }), Schema.Struct({ reason: Schema.Literals(["stale-target", "unsupported"]) })]);
|
|
10
|
+
const WriteReply = Schema.Literals([
|
|
11
|
+
"filled",
|
|
12
|
+
"stale-target",
|
|
13
|
+
"unsupported-before-write",
|
|
14
|
+
"unsupported"
|
|
15
|
+
]);
|
|
16
|
+
const prepareFields = `(() => {
|
|
17
|
+
const doc = document;
|
|
18
|
+
let fields = [];
|
|
19
|
+
let form;
|
|
20
|
+
let snapshots = [];
|
|
21
|
+
const describe = el => {
|
|
22
|
+
if (!el || !el.isConnected || el.ownerDocument !== doc || doc !== document) return null;
|
|
23
|
+
const native = el instanceof HTMLTextAreaElement ||
|
|
24
|
+
(el instanceof HTMLSelectElement && !el.multiple) ||
|
|
25
|
+
(el instanceof HTMLInputElement && ['text','email','password','tel','number','month','search','url','date','time','week','datetime-local'].includes(el.type));
|
|
26
|
+
if (!native || el.matches(':disabled') || el.readOnly || !el.form) return null;
|
|
27
|
+
const values = [el.form.action, el.form.method, el.form.enctype, el.name, el.type, el.autocomplete ?? ''];
|
|
28
|
+
if (values.some(value => value.length > 2048)) return null;
|
|
29
|
+
return JSON.stringify(values);
|
|
30
|
+
};
|
|
31
|
+
const validate = () => fields.length > 0 && fields.every((el, index) =>
|
|
32
|
+
el.form === form && describe(el) !== null && describe(el) === snapshots[index]);
|
|
33
|
+
return {
|
|
34
|
+
prepare(selectors) {
|
|
35
|
+
try {
|
|
36
|
+
fields = selectors.map(selector => {
|
|
37
|
+
const matches = doc.querySelectorAll(selector);
|
|
38
|
+
return matches.length === 1 ? matches[0] : null;
|
|
39
|
+
});
|
|
40
|
+
} catch { return {reason: 'stale-target'}; }
|
|
41
|
+
if (fields.some(el => !el) || new Set(fields).size !== fields.length) return {reason: 'stale-target'};
|
|
42
|
+
snapshots = fields.map(describe);
|
|
43
|
+
form = fields[0].form;
|
|
44
|
+
if (!form || snapshots.some(value => value === null) || fields.some(el => el.form !== form)) return {reason: 'unsupported'};
|
|
45
|
+
return {action: form.action};
|
|
46
|
+
},
|
|
47
|
+
target() { return validate() ? {action: form.action} : {reason: 'stale-target'}; },
|
|
48
|
+
fill(index, value) {
|
|
49
|
+
if (!validate()) return 'stale-target';
|
|
50
|
+
const el = fields[index];
|
|
51
|
+
if (el instanceof HTMLSelectElement) {
|
|
52
|
+
const options = [...el.options].filter(option => option.value === value);
|
|
53
|
+
if (options.length !== 1 || options[0].matches(':disabled')) return 'unsupported-before-write';
|
|
54
|
+
}
|
|
55
|
+
let prototype = Object.getPrototypeOf(el);
|
|
56
|
+
let setter;
|
|
57
|
+
while (prototype && !setter) { setter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set; prototype = Object.getPrototypeOf(prototype); }
|
|
58
|
+
if (!setter) return 'unsupported-before-write';
|
|
59
|
+
setter.call(el, value);
|
|
60
|
+
if (el.value !== value) return 'unsupported';
|
|
61
|
+
el.dispatchEvent(new Event('input', {bubbles: true}));
|
|
62
|
+
el.dispatchEvent(new Event('change', {bubbles: true}));
|
|
63
|
+
return 'filled';
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
})()`;
|
|
67
|
+
const secretFor = (material, role) => {
|
|
68
|
+
if (material._tag === "LoginCredential") return role === "username" ? material.username : role === "password" ? material.password : void 0;
|
|
69
|
+
switch (role) {
|
|
70
|
+
case "card-name": return material.name;
|
|
71
|
+
case "card-number": return material.number;
|
|
72
|
+
case "card-expiry": return material.expiry;
|
|
73
|
+
case "card-expiry-month": return material.expiryMonth;
|
|
74
|
+
case "card-expiry-year": return material.expiryYear;
|
|
75
|
+
case "card-security-code": return material.securityCode;
|
|
76
|
+
case "username":
|
|
77
|
+
case "password": return;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Fill explicit native fields on an existing page without submitting. The owning browser session
|
|
82
|
+
* serializes operations and closes on timeout, interruption or uncertain dispatch; this function releases
|
|
83
|
+
* only its local handles. Ordinary native observations remain available, including credential echoes.
|
|
84
|
+
*/
|
|
85
|
+
const fillCredential = Effect.fnUntraced(function* (input) {
|
|
86
|
+
const { page, commandTimeoutMillis } = yield* BrowserSessionPage;
|
|
87
|
+
let dispatch = "not-dispatched";
|
|
88
|
+
let filled = 0;
|
|
89
|
+
const fail = (reason) => new CredentialFillError({
|
|
90
|
+
reason,
|
|
91
|
+
dispatch,
|
|
92
|
+
filled,
|
|
93
|
+
cleanup: "not-requested"
|
|
94
|
+
});
|
|
95
|
+
const remote = (run) => Effect.tryPromise({
|
|
96
|
+
try: async (signal) => {
|
|
97
|
+
try {
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
value: await run(signal)
|
|
101
|
+
};
|
|
102
|
+
} catch {
|
|
103
|
+
return { ok: false };
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
catch: () => fail("provider")
|
|
107
|
+
}).pipe(Effect.flatMap((result) => result.ok ? Effect.succeed(result.value) : Effect.fail(fail("provider"))));
|
|
108
|
+
const release = (handles) => Effect.promise(async () => {
|
|
109
|
+
await Promise.all(handles.map((handle) => handle.dispose().catch(() => {})));
|
|
110
|
+
}).pipe(Effect.interruptible, Effect.timeoutOption("1 second"), Effect.asVoid);
|
|
111
|
+
const acquire = (run) => Effect.acquireRelease(remote(async (signal) => {
|
|
112
|
+
const handles = await run();
|
|
113
|
+
if (signal.aborted) {
|
|
114
|
+
await Promise.all(handles.map((handle) => handle.dispose().catch(() => {})));
|
|
115
|
+
throw fail("provider");
|
|
116
|
+
}
|
|
117
|
+
return handles;
|
|
118
|
+
}).pipe(Effect.interruptible), release);
|
|
119
|
+
const origin = (value) => Effect.try({
|
|
120
|
+
try: () => {
|
|
121
|
+
const url = new URL(value);
|
|
122
|
+
if (url.username || url.password) throw fail("denied");
|
|
123
|
+
return url.origin;
|
|
124
|
+
},
|
|
125
|
+
catch: () => fail("denied")
|
|
126
|
+
}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(CredentialOrigin)), Effect.mapError(() => fail("denied")));
|
|
127
|
+
return yield* Effect.gen(function* () {
|
|
128
|
+
const request = yield* Schema.decodeEffect(FillCredentialRequest, { onExcessProperty: "error" })(input).pipe(Effect.mapError(() => fail("denied")));
|
|
129
|
+
const roles = request.fields.map((field) => field.role);
|
|
130
|
+
if (new Set(roles).size !== roles.length || roles.some((role) => request.kind === "login" !== (role === "username" || role === "password"))) return yield* fail("denied");
|
|
131
|
+
const access = yield* BrowserCredentialAccess;
|
|
132
|
+
let frame = page.mainFrame();
|
|
133
|
+
for (const selector of request.frame ?? []) {
|
|
134
|
+
const matches = yield* acquire(() => frame.$$(selector));
|
|
135
|
+
const element = matches[0];
|
|
136
|
+
if (matches.length !== 1 || element === void 0) return yield* fail("stale-target");
|
|
137
|
+
const child = yield* remote(() => element.contentFrame());
|
|
138
|
+
if (child === null) return yield* fail("unsupported");
|
|
139
|
+
frame = child;
|
|
140
|
+
}
|
|
141
|
+
const held = (yield* acquire(async () => [await frame.isolatedRealm().evaluateHandle(prepareFields)]))[0];
|
|
142
|
+
if (held === void 0) return yield* fail("provider");
|
|
143
|
+
const inspect = (raw) => Schema.decodeUnknownEffect(TargetReply)(raw).pipe(Effect.mapError(() => fail("provider")), Effect.flatMap((reply) => "reason" in reply ? Effect.fail(fail(reply.reason)) : Effect.succeed(reply.action)));
|
|
144
|
+
const action = yield* remote(() => held.evaluate((state, selectors) => {
|
|
145
|
+
if (typeof state !== "object" || state === null) return null;
|
|
146
|
+
return Reflect.apply(Reflect.get(state, "prepare"), state, [selectors]);
|
|
147
|
+
}, request.fields.map((field) => field.selector))).pipe(Effect.flatMap(inspect));
|
|
148
|
+
const target = CredentialTarget.make({
|
|
149
|
+
topOrigin: yield* origin(page.url()),
|
|
150
|
+
frameOrigin: yield* origin(frame.url()),
|
|
151
|
+
recipientOrigin: yield* origin(action)
|
|
152
|
+
});
|
|
153
|
+
const authorization = {
|
|
154
|
+
credential: request.credential,
|
|
155
|
+
kind: request.kind,
|
|
156
|
+
target,
|
|
157
|
+
roles
|
|
158
|
+
};
|
|
159
|
+
const authorize = Effect.suspend(() => access.authorize(authorization)).pipe(Effect.mapError((error) => fail(error.reason)));
|
|
160
|
+
const validate = Effect.gen(function* () {
|
|
161
|
+
if (frame.detached || (yield* origin(page.url())) !== target.topOrigin || (yield* origin(frame.url())) !== target.frameOrigin) return yield* fail("stale-target");
|
|
162
|
+
const current = yield* remote(() => held.evaluate((state) => {
|
|
163
|
+
if (typeof state !== "object" || state === null) return null;
|
|
164
|
+
return Reflect.apply(Reflect.get(state, "target"), state, []);
|
|
165
|
+
})).pipe(Effect.flatMap(inspect));
|
|
166
|
+
if ((yield* origin(current)) !== target.recipientOrigin) return yield* fail("stale-target");
|
|
167
|
+
});
|
|
168
|
+
yield* authorize;
|
|
169
|
+
yield* validate;
|
|
170
|
+
const raw = yield* access.resolve(authorization).pipe(Effect.mapError((error) => fail(error.reason)));
|
|
171
|
+
const material = yield* Schema.decodeEffect(request.kind === "login" ? LoginCredential : CardCredential)(raw).pipe(Effect.mapError(() => fail("resolver")));
|
|
172
|
+
for (const role of roles) if (secretFor(material, role) === void 0) return yield* fail("missing-credential");
|
|
173
|
+
for (const [index, role] of roles.entries()) {
|
|
174
|
+
yield* authorize;
|
|
175
|
+
yield* validate;
|
|
176
|
+
const value = secretFor(material, role);
|
|
177
|
+
if (value === void 0) return yield* fail("missing-credential");
|
|
178
|
+
const previous = dispatch;
|
|
179
|
+
const result = yield* remote((signal) => {
|
|
180
|
+
if (signal.aborted) return Promise.reject(fail("provider"));
|
|
181
|
+
dispatch = "possibly-dispatched";
|
|
182
|
+
return held.evaluate((state, field, secret) => {
|
|
183
|
+
if (typeof state !== "object" || state === null) return "stale-target";
|
|
184
|
+
return Reflect.apply(Reflect.get(state, "fill"), state, [field, secret]);
|
|
185
|
+
}, index, Redacted.value(value));
|
|
186
|
+
}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(WriteReply)), Effect.mapError(() => fail("provider")));
|
|
187
|
+
if (result === "stale-target" || result === "unsupported-before-write") {
|
|
188
|
+
dispatch = previous;
|
|
189
|
+
return yield* fail(result === "stale-target" ? "stale-target" : "unsupported");
|
|
190
|
+
}
|
|
191
|
+
if (result === "unsupported") return yield* fail("unsupported");
|
|
192
|
+
dispatch = "dispatched";
|
|
193
|
+
filled++;
|
|
194
|
+
}
|
|
195
|
+
return CredentialFillResult.make({
|
|
196
|
+
dispatch: "dispatched",
|
|
197
|
+
filled
|
|
198
|
+
});
|
|
199
|
+
}).pipe(Effect.scoped, Effect.timeoutOrElse({
|
|
200
|
+
duration: commandTimeoutMillis,
|
|
201
|
+
orElse: () => Effect.fail(fail("timeout"))
|
|
202
|
+
}), Effect.catchCause((cause) => {
|
|
203
|
+
if (Cause.hasInterrupts(cause)) return Effect.interrupt;
|
|
204
|
+
if (cause.reasons.some((reason) => reason._tag === "Die" || reason._tag === "Fail" && !Schema.is(CredentialFillError)(reason.error))) return Effect.fail(fail("provider"));
|
|
205
|
+
const expected = cause.reasons.find((reason) => reason._tag === "Fail" && Schema.is(CredentialFillError)(reason.error));
|
|
206
|
+
return Effect.fail(fail(expected?._tag === "Fail" && Schema.is(CredentialFillError)(expected.error) ? expected.error.reason : "provider"));
|
|
207
|
+
}), Effect.withTracerEnabled(false));
|
|
208
|
+
});
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/internal/browser-readonly-live-view.ts
|
|
211
|
+
const Identity = Schema.Struct({
|
|
212
|
+
sessionId: Schema.String.check(Schema.isUUID()),
|
|
213
|
+
targetId: Schema.NonEmptyString.check(Schema.isMaxLength(256))
|
|
214
|
+
});
|
|
215
|
+
const Reply = Schema.fromJsonString(Schema.Struct({
|
|
216
|
+
id: Identity.fields.targetId,
|
|
217
|
+
options: Schema.Struct({
|
|
218
|
+
mode: Schema.Literal("tab"),
|
|
219
|
+
guardrails: Schema.Struct({ mode: Schema.Literal("readonly") })
|
|
220
|
+
}),
|
|
221
|
+
devtoolsFrontendUrl: Schema.String
|
|
222
|
+
}));
|
|
223
|
+
const operation = "session.readonlyLiveView";
|
|
224
|
+
/** Only the REST endpoint can mint provider-enforced read-only connections. */
|
|
225
|
+
var BrowserRunReadonlyLiveView = class extends Context.Service()("@effect-agent/platform-cloudflare/internal/BrowserRunReadonlyLiveView") {
|
|
226
|
+
static layer(options) {
|
|
227
|
+
return Layer.effect(this, Effect.gen(function* () {
|
|
228
|
+
const client = yield* HttpClient.HttpClient;
|
|
229
|
+
return { mint: Effect.fnUntraced(function* (sessionId, targetId, request) {
|
|
230
|
+
const identity = yield* Schema.decodeEffect(Identity)({
|
|
231
|
+
sessionId: Redacted.value(sessionId),
|
|
232
|
+
targetId: Redacted.value(targetId)
|
|
233
|
+
});
|
|
234
|
+
const input = yield* Schema.decodeEffect(BrowserRunLiveViewRequest)(request);
|
|
235
|
+
const response = yield* client.execute(HttpClientRequest.post(`https://api.cloudflare.com/client/v4/accounts/${options.accountId}/browser-rendering/devtools/browser/${identity.sessionId}/live_view`).pipe(HttpClientRequest.bearerToken(options.apiToken), HttpClientRequest.bodyJsonUnsafe({
|
|
236
|
+
mode: input.mode,
|
|
237
|
+
expiresInMs: input.expiresInMs,
|
|
238
|
+
targetId: identity.targetId,
|
|
239
|
+
guardrails: { mode: "readonly" }
|
|
240
|
+
}))).pipe(Effect.provideService(FetchHttpClient.RequestInit, { redirect: "manual" }));
|
|
241
|
+
if (response.status !== 200) return yield* new BrowserRunFailure({
|
|
242
|
+
operation,
|
|
243
|
+
reason: "provider",
|
|
244
|
+
status: response.status
|
|
245
|
+
});
|
|
246
|
+
const bytes = yield* Stream.runFoldEffect(response.stream, () => /* @__PURE__ */ new Uint8Array(), (body, chunk) => {
|
|
247
|
+
if (body.byteLength + chunk.byteLength > 16384) return Effect.fail(new BrowserRunFailure({
|
|
248
|
+
operation,
|
|
249
|
+
reason: "malformed"
|
|
250
|
+
}));
|
|
251
|
+
const combined = new Uint8Array(body.byteLength + chunk.byteLength);
|
|
252
|
+
combined.set(body);
|
|
253
|
+
combined.set(chunk, body.byteLength);
|
|
254
|
+
return Effect.succeed(combined);
|
|
255
|
+
});
|
|
256
|
+
const reply = yield* Schema.decodeEffect(Reply)(new TextDecoder().decode(bytes));
|
|
257
|
+
if (reply.id !== identity.targetId) return yield* new BrowserRunFailure({
|
|
258
|
+
operation,
|
|
259
|
+
reason: "malformed"
|
|
260
|
+
});
|
|
261
|
+
return yield* Schema.decodeEffect(BrowserRunLiveViewResult)({ devtoolsFrontendUrl: Redacted.make(reply.devtoolsFrontendUrl) });
|
|
262
|
+
}, Effect.mapError((cause) => browserFailure(operation, cause)), Effect.withTracerEnabled(false)) };
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/BrowserSession.ts
|
|
268
|
+
var BrowserSession_exports = /* @__PURE__ */ __exportAll({
|
|
269
|
+
BrowserRunHandoffRequest: () => BrowserRunHandoffRequest,
|
|
270
|
+
BrowserRunHandoffResult: () => BrowserRunHandoffResult,
|
|
271
|
+
BrowserRunHandoffState: () => BrowserRunHandoffState,
|
|
272
|
+
BrowserRunLiveViewRequest: () => BrowserRunLiveViewRequest,
|
|
273
|
+
BrowserRunLiveViewResult: () => BrowserRunLiveViewResult,
|
|
274
|
+
BrowserSessionError: () => BrowserSessionError,
|
|
275
|
+
BrowserSessionOptions: () => BrowserSessionOptions,
|
|
276
|
+
BrowserSessionReference: () => BrowserSessionReference,
|
|
277
|
+
BrowserSessions: () => BrowserSessions
|
|
278
|
+
});
|
|
279
|
+
const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
|
|
280
|
+
const SessionId = Schema.Redacted(Schema.String.check(Schema.isUUID()));
|
|
281
|
+
const ProviderId = Schema.Redacted(Schema.NonEmptyString.check(Schema.isMaxLength(256)));
|
|
282
|
+
/** Private host record, not a model capability. Persist together with the owner's controller state. */
|
|
283
|
+
var BrowserSessionReference = class extends Schema.Class("BrowserSessionReference")({
|
|
284
|
+
version: Schema.Literal(1),
|
|
285
|
+
sessionId: SessionId,
|
|
286
|
+
contextId: ProviderId,
|
|
287
|
+
targetId: ProviderId,
|
|
288
|
+
expiresAt: PositiveMillis,
|
|
289
|
+
commandTimeoutMillis: PositiveMillis
|
|
290
|
+
}) {};
|
|
291
|
+
var BrowserSessionOptions = class extends Schema.Class("BrowserSessionOptions")({
|
|
292
|
+
maxElapsedMillis: PositiveMillis,
|
|
293
|
+
keepAliveMillis: Schema.optionalKey(PositiveMillis.check(Schema.isLessThanOrEqualTo(6e5))),
|
|
294
|
+
commandTimeoutMillis: Schema.optionalKey(PositiveMillis)
|
|
295
|
+
}) {};
|
|
296
|
+
/** No SDK text, selectors, page data, or credential values are retained in public failures. */
|
|
297
|
+
var BrowserSessionError = class extends Schema.TaggedError()("BrowserSessionError", {
|
|
298
|
+
reason: Schema.Literals([
|
|
299
|
+
"invalid",
|
|
300
|
+
"expired",
|
|
301
|
+
"missing-page",
|
|
302
|
+
"busy",
|
|
303
|
+
"closed",
|
|
304
|
+
"provider",
|
|
305
|
+
"timeout",
|
|
306
|
+
"cleanup"
|
|
307
|
+
]),
|
|
308
|
+
dispatch: Schema.Literals(["not-dispatched", "possibly-dispatched"]),
|
|
309
|
+
cleanup: Schema.Literals([
|
|
310
|
+
"not-requested",
|
|
311
|
+
"confirmed",
|
|
312
|
+
"unconfirmed"
|
|
313
|
+
])
|
|
314
|
+
}) {};
|
|
315
|
+
const failure = (reason, dispatch = "not-dispatched", cleanup = "not-requested") => new BrowserSessionError({
|
|
316
|
+
reason,
|
|
317
|
+
dispatch,
|
|
318
|
+
cleanup
|
|
319
|
+
});
|
|
320
|
+
const decode = (schema, input) => Schema.decodeUnknownEffect(schema, { onExcessProperty: "error" })(input).pipe(Effect.mapError(() => failure("invalid")));
|
|
321
|
+
const native = Effect.fnUntraced(function* (operation, action) {
|
|
322
|
+
const runReport = Effect.runPromiseWith(yield* Effect.context());
|
|
323
|
+
return yield* Effect.tryPromise({
|
|
324
|
+
try: async (signal) => {
|
|
325
|
+
try {
|
|
326
|
+
return {
|
|
327
|
+
ok: true,
|
|
328
|
+
value: await action(signal)
|
|
329
|
+
};
|
|
330
|
+
} catch (cause) {
|
|
331
|
+
if (!signal.aborted || cause !== signal.reason) await runReport(reportBrowserCause(operation, Cause.fail(cause)));
|
|
332
|
+
return { ok: false };
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
catch: () => failure("provider")
|
|
336
|
+
}).pipe(Effect.flatMap((result) => result.ok ? Effect.succeed(result.value) : Effect.fail(reportedBrowserError(failure("provider")))), Effect.withTracerEnabled(false));
|
|
337
|
+
});
|
|
338
|
+
const targetId = (page) => native("session.target", async () => {
|
|
339
|
+
const cdp = await page.createCDPSession();
|
|
340
|
+
try {
|
|
341
|
+
return (await cdp.send("Target.getTargetInfo")).targetInfo.targetId;
|
|
342
|
+
} finally {
|
|
343
|
+
await cdp.detach();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
/** Native Cloudflare sessions; no framework browser workflow, checkpoint transfer, or observation policy. */
|
|
347
|
+
var BrowserSessions = class BrowserSessions extends Context.Service()("@effect-agent/platform-cloudflare/BrowserSessions") {
|
|
348
|
+
static layerNoDeps = Layer.effect(this, Effect.gen(function* () {
|
|
349
|
+
const binding = yield* BrowserRunBinding;
|
|
350
|
+
const lifecycle = yield* BrowserRunSessionLifecycle;
|
|
351
|
+
const readonlyLiveView = yield* BrowserRunReadonlyLiveView;
|
|
352
|
+
const close = (sessionId) => decode(SessionId, sessionId).pipe(Effect.flatMap((id) => lifecycle.close(id).pipe(Effect.mapError(() => failure("cleanup", "not-dispatched", "unconfirmed")))));
|
|
353
|
+
const cleanup = (sessionId) => close(sessionId).pipe(Effect.as("confirmed"), Effect.catchCause((cause) => reportBrowserCause("session.close", cause).pipe(Effect.as("unconfirmed"))));
|
|
354
|
+
const connect = Effect.fnUntraced(function* (sessionId) {
|
|
355
|
+
let attachment;
|
|
356
|
+
const retire = Effect.suspend(() => attachment === void 0 ? Effect.void : attachment.retire.pipe(Effect.timeoutOrElse({
|
|
357
|
+
duration: "10 seconds",
|
|
358
|
+
orElse: () => Effect.fail(failure("cleanup"))
|
|
359
|
+
}), Effect.catchCause((cause) => reportBrowserCause("session.disconnect", cause).pipe(Effect.andThen(Effect.die(failure("cleanup", "not-dispatched", "unconfirmed")))))));
|
|
360
|
+
yield* Effect.addFinalizer(() => retire);
|
|
361
|
+
return {
|
|
362
|
+
browser: yield* native("session.connect", (signal) => {
|
|
363
|
+
attachment = binding.connect(Redacted.value(sessionId), "session.connect", signal);
|
|
364
|
+
return attachment.browser;
|
|
365
|
+
}),
|
|
366
|
+
retire
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
const create = (input, retain) => Effect.gen(function* () {
|
|
370
|
+
const options = yield* decode(BrowserSessionOptions, input);
|
|
371
|
+
const expiresAt = (yield* Clock.currentTimeMillis) + options.maxElapsedMillis;
|
|
372
|
+
if (!Number.isSafeInteger(expiresAt)) return yield* failure("invalid");
|
|
373
|
+
let sessionId;
|
|
374
|
+
let retained = false;
|
|
375
|
+
const runCleanup = Effect.runPromiseWith(yield* Effect.context());
|
|
376
|
+
yield* Effect.addFinalizer(() => sessionId === void 0 || retained ? Effect.void : cleanup(sessionId));
|
|
377
|
+
sessionId = yield* native("session.acquire", async (signal) => {
|
|
378
|
+
const id = Redacted.make(await binding.acquire(options.keepAliveMillis ?? 6e5, "session.acquire"));
|
|
379
|
+
if (signal.aborted) {
|
|
380
|
+
await runCleanup(cleanup(id));
|
|
381
|
+
signal.throwIfAborted();
|
|
382
|
+
}
|
|
383
|
+
return id;
|
|
384
|
+
});
|
|
385
|
+
const { browser } = yield* connect(sessionId);
|
|
386
|
+
const page = yield* native("session.page", async () => {
|
|
387
|
+
return await (await browser.createBrowserContext()).newPage();
|
|
388
|
+
});
|
|
389
|
+
const reference = yield* decode(BrowserSessionReference, {
|
|
390
|
+
version: 1,
|
|
391
|
+
sessionId,
|
|
392
|
+
contextId: Redacted.make(page.browserContext().id ?? ""),
|
|
393
|
+
targetId: Redacted.make(yield* targetId(page)),
|
|
394
|
+
expiresAt,
|
|
395
|
+
commandTimeoutMillis: options.commandTimeoutMillis ?? 3e4
|
|
396
|
+
});
|
|
397
|
+
yield* Effect.uninterruptibleMask((restore) => restore(retain(reference)).pipe(Effect.tap(() => Effect.sync(() => {
|
|
398
|
+
retained = true;
|
|
399
|
+
}))));
|
|
400
|
+
return reference;
|
|
401
|
+
}).pipe(Effect.scoped, Effect.timeoutOrElse({
|
|
402
|
+
duration: "30 seconds",
|
|
403
|
+
orElse: () => Effect.fail(failure("timeout"))
|
|
404
|
+
}), Effect.withTracerEnabled(false));
|
|
405
|
+
const attachPage = Effect.fnUntraced(function* (input) {
|
|
406
|
+
const reference = yield* decode(BrowserSessionReference, input);
|
|
407
|
+
if (reference.expiresAt <= (yield* Clock.currentTimeMillis)) return yield* failure("expired");
|
|
408
|
+
const scope = yield* Effect.scope;
|
|
409
|
+
const { browser, retire } = yield* connect(reference.sessionId);
|
|
410
|
+
const context = browser.browserContexts().find((value) => value.id === Redacted.value(reference.contextId));
|
|
411
|
+
if (context === void 0) return yield* failure("missing-page");
|
|
412
|
+
let page;
|
|
413
|
+
for (const candidate of yield* native("session.pages", () => context.pages())) if ((yield* targetId(candidate)) === Redacted.value(reference.targetId)) {
|
|
414
|
+
page = candidate;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
if (page === void 0) return yield* failure("missing-page");
|
|
418
|
+
const currentPage = page;
|
|
419
|
+
const lock = yield* Semaphore.make(1);
|
|
420
|
+
let invalid = false;
|
|
421
|
+
const terminate = Effect.suspend(() => {
|
|
422
|
+
invalid = true;
|
|
423
|
+
return retire.pipe(Effect.catchCause((cause) => reportBrowserCause("session.disconnect", cause)), Effect.andThen(cleanup(reference.sessionId)));
|
|
424
|
+
});
|
|
425
|
+
const runEffect = (action) => lock.withPermitsIfAvailable(1)(Effect.gen(function* () {
|
|
426
|
+
if (invalid || scope.state._tag === "Closed" || !browser.isConnected()) return yield* failure("closed");
|
|
427
|
+
const remaining = reference.expiresAt - (yield* Clock.currentTimeMillis);
|
|
428
|
+
if (remaining <= 0) return yield* failure("expired", "not-dispatched", yield* terminate);
|
|
429
|
+
return yield* action(Math.min(remaining, reference.commandTimeoutMillis)).pipe(Effect.catchDefect((defect) => reportBrowserCause("session.command", Cause.die(defect)).pipe(Effect.andThen(terminate), Effect.flatMap((closed) => Effect.fail(failure("provider", "possibly-dispatched", closed))))), Effect.onInterrupt(() => terminate));
|
|
430
|
+
})).pipe(Effect.flatMap(Effect.fromOption(() => failure("busy"))), Effect.withTracerEnabled(false));
|
|
431
|
+
const run = (authorize, action, terminateOnError = false) => runEffect((commandTimeoutMillis) => authorize.pipe(Effect.andThen(() => native("session.command", () => action(currentPage)).pipe(Effect.catch((error) => Effect.gen(function* () {
|
|
432
|
+
const closed = terminateOnError ? yield* terminate : "not-requested";
|
|
433
|
+
return yield* reportedBrowserError(failure(error.reason, "possibly-dispatched", closed));
|
|
434
|
+
})))), Effect.timeoutOrElse({
|
|
435
|
+
duration: commandTimeoutMillis,
|
|
436
|
+
orElse: () => terminate.pipe(Effect.flatMap((closed) => Effect.fail(failure("timeout", "possibly-dispatched", closed))))
|
|
437
|
+
})));
|
|
438
|
+
const command = (authorize, method, parameters, schema) => run(authorize, async (page) => {
|
|
439
|
+
const cdp = await page.createCDPSession();
|
|
440
|
+
try {
|
|
441
|
+
const result = await Reflect.apply(cdp.send, cdp, [method, parameters]);
|
|
442
|
+
return Schema.decodeUnknownSync(schema)(result);
|
|
443
|
+
} finally {
|
|
444
|
+
await cdp.detach();
|
|
445
|
+
}
|
|
446
|
+
}, method === "Cloudflare.handoff");
|
|
447
|
+
return {
|
|
448
|
+
reference,
|
|
449
|
+
run,
|
|
450
|
+
fillCredential: (request) => runEffect((commandTimeoutMillis) => fillCredential(request).pipe(Effect.provideService(BrowserSessionPage, {
|
|
451
|
+
page: currentPage,
|
|
452
|
+
commandTimeoutMillis
|
|
453
|
+
}), Effect.catchIf((error) => error.reason === "timeout" || error.dispatch === "possibly-dispatched", (error) => terminate.pipe(Effect.flatMap((cleanup) => Effect.fail(CredentialFillError.make({
|
|
454
|
+
...error,
|
|
455
|
+
cleanup
|
|
456
|
+
}))))))),
|
|
457
|
+
handoff: (authorize, request) => decode(BrowserRunHandoffRequest, request).pipe(Effect.flatMap((value) => command(authorize, "Cloudflare.handoff", {
|
|
458
|
+
instructions: value.instructions,
|
|
459
|
+
timeout: value.timeout
|
|
460
|
+
}, Schema.Struct({ handoffId: Schema.NonEmptyString.check(Schema.isMaxLength(1024)) }))), Effect.map((value) => BrowserRunHandoffResult.make({ handoffId: Redacted.make(value.handoffId) }))),
|
|
461
|
+
getLiveView: (authorize, request) => decode(BrowserRunLiveViewRequest, request).pipe(Effect.flatMap((value) => command(authorize, "Cloudflare.getLiveView", {
|
|
462
|
+
mode: value.mode,
|
|
463
|
+
expiresInMs: value.expiresInMs
|
|
464
|
+
}, Schema.Struct({ devtoolsFrontendUrl: Schema.String }))), Effect.flatMap((value) => decode(BrowserRunLiveViewResult, { devtoolsFrontendUrl: Redacted.make(value.devtoolsFrontendUrl) }))),
|
|
465
|
+
getReadOnlyLiveView: (authorize, request) => decode(BrowserRunLiveViewRequest, request).pipe(Effect.flatMap((value) => runEffect((commandTimeoutMillis) => authorize.pipe(Effect.andThen(() => readonlyLiveView.mint(reference.sessionId, reference.targetId, value).pipe(Effect.tapCause((cause) => reportBrowserCause("session.readonlyLiveView", cause)), Effect.mapError(() => reportedBrowserError(failure("provider"))))), Effect.timeoutOrElse({
|
|
466
|
+
duration: commandTimeoutMillis,
|
|
467
|
+
orElse: () => Effect.fail(failure("timeout"))
|
|
468
|
+
}))))),
|
|
469
|
+
getHandoffState: (authorize) => command(authorize, "Cloudflare.getHandoffState", {}, Schema.Struct({
|
|
470
|
+
active: Schema.Boolean,
|
|
471
|
+
handoffId: Schema.optionalKey(Schema.NonEmptyString.check(Schema.isMaxLength(1024))),
|
|
472
|
+
durationMs: Schema.optionalKey(Schema.Natural)
|
|
473
|
+
})).pipe(Effect.map((value) => BrowserRunHandoffState.make({
|
|
474
|
+
active: value.active,
|
|
475
|
+
...value.handoffId === void 0 ? {} : { handoffId: Redacted.make(value.handoffId) },
|
|
476
|
+
...value.durationMs === void 0 ? {} : { durationMs: value.durationMs }
|
|
477
|
+
})))
|
|
478
|
+
};
|
|
479
|
+
});
|
|
480
|
+
const attach = Effect.fnUntraced(function* (input) {
|
|
481
|
+
const reference = yield* decode(BrowserSessionReference, input);
|
|
482
|
+
const scope = yield* Scope.fork(yield* Effect.scope);
|
|
483
|
+
return yield* attachPage(reference).pipe(Effect.provideService(Scope.Scope, scope), Effect.timeoutOrElse({
|
|
484
|
+
duration: reference.commandTimeoutMillis,
|
|
485
|
+
orElse: () => reportBrowserCause("session.connect", Cause.fail(failure("timeout"))).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("timeout")))))
|
|
486
|
+
}), Effect.onExit((exit) => Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void));
|
|
487
|
+
});
|
|
488
|
+
return BrowserSessions.of({
|
|
489
|
+
create,
|
|
490
|
+
attach,
|
|
491
|
+
close,
|
|
492
|
+
keepAlive: (sessionId) => decode(SessionId, sessionId).pipe(Effect.flatMap((id) => binding.keepAlive(Redacted.value(id)).pipe(Effect.timeoutOrElse({
|
|
493
|
+
duration: "10 seconds",
|
|
494
|
+
orElse: () => Effect.fail(failure("timeout"))
|
|
495
|
+
}), Effect.catch((error) => reportBrowserCause("session.keepAlive", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure(error.reason === "timeout" ? "timeout" : "provider")))))))), Effect.withTracerEnabled(false))
|
|
496
|
+
});
|
|
497
|
+
}));
|
|
498
|
+
static layer(options) {
|
|
499
|
+
return this.layerNoDeps.pipe(Layer.provide(BrowserRunBinding.layer(options.browser)), Layer.provide(BrowserRunSessionLifecycle.layer(options)), Layer.provide(BrowserRunReadonlyLiveView.layer(options)));
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
//#endregion
|
|
503
|
+
export { BrowserSessions as a, BrowserSession_exports as i, BrowserSessionOptions as n, BrowserSessionReference as r, BrowserSessionError as t };
|
|
504
|
+
|
|
505
|
+
//# sourceMappingURL=BrowserSession-DWoRC_iM.mjs.map
|