@effect-agent/platform-cloudflare 0.1.0-beta.107 → 0.1.0-beta.108
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/CloudflareThreadClient.d.mts +33 -33
- package/dist/{InteractiveBrowser-B3S8if51.d.mts → InteractiveBrowser-CKp6pqmw.d.mts} +2 -1
- package/dist/{InteractiveBrowser-xVNX7ARl.mjs → InteractiveBrowser-l6J4eP6T.mjs} +192 -37
- package/dist/InteractiveBrowser-l6J4eP6T.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +1 -1
- package/dist/InteractiveBrowser.mjs +1 -1
- package/dist/ProtectedBrowser.d.mts +3 -3
- package/dist/ProtectedBrowser.mjs +160 -135
- package/dist/ProtectedBrowser.mjs.map +1 -1
- package/dist/{ThreadObject-CnqCuWlA.d.mts → ThreadObject-D_vvj7Qx.d.mts} +33 -33
- package/dist/ThreadObject.d.mts +1 -1
- package/dist/index.d.mts +1 -1
- package/package.json +1 -1
- package/src/InteractiveBrowser.ts +189 -46
- package/src/internal/browser-binding.ts +82 -0
- package/src/internal/browser-failure.ts +92 -0
- package/src/internal/browser-session-lifecycle.ts +38 -5
- package/src/protected-browser/binding.ts +170 -129
- package/src/protected-browser/host.ts +52 -19
- package/src/protected-browser/native.ts +45 -17
- package/src/protected-browser/policy.ts +75 -39
- package/dist/InteractiveBrowser-xVNX7ARl.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { h as BrowserRunSessionLifecycle, o as BrowserRunLiveViewRequest, r as BrowserRunHandoffState, s as BrowserRunLiveViewResult, t as BrowserRunHandoffRequest } from "./InteractiveBrowser-
|
|
2
|
-
import { Clock, Context, Crypto, Effect, Layer, Redacted, Schema, Semaphore } from "effect";
|
|
3
|
-
import
|
|
1
|
+
import { _ as BrowserRunFailure, b as reportBrowserCause, g as BrowserRunBinding, h as BrowserRunSessionLifecycle, o as BrowserRunLiveViewRequest, r as BrowserRunHandoffState, s as BrowserRunLiveViewResult, t as BrowserRunHandoffRequest, v as browserFailure, x as reportedBrowserError, y as inheritBrowserReport } from "./InteractiveBrowser-l6J4eP6T.mjs";
|
|
2
|
+
import { Cause, Clock, Context, Crypto, Effect, Layer, Redacted, Schema, Semaphore } from "effect";
|
|
3
|
+
import "@cloudflare/puppeteer";
|
|
4
4
|
import { InteractiveBrowserPolicy } from "effect-agent/interactive-browser";
|
|
5
5
|
import { BrowserCredentialAccess, CardCredential, CredentialObservationDecision, CredentialOffer, CredentialOfferMetadata, CredentialOrigin, CredentialTarget, CredentialUseResult, ListCredentialOffers, LoginCredential, ProtectedBrowser, ProtectedBrowserCheckpoint, ProtectedBrowserClick, ProtectedBrowserControl, ProtectedBrowserError, ProtectedBrowserFill, ProtectedBrowserNavigate, ProtectedBrowserObservation, UseCredential } from "effect-agent/protected-browser";
|
|
6
6
|
//#region src/protected-browser/policy.ts
|
|
@@ -91,15 +91,18 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
91
91
|
driver.invalidate();
|
|
92
92
|
cleanup = yield* driver.close.pipe(Effect.interruptible, Effect.timeoutOrElse({
|
|
93
93
|
duration: "10 seconds",
|
|
94
|
-
orElse: () => Effect.
|
|
95
|
-
|
|
94
|
+
orElse: () => Effect.fail(new BrowserRunFailure({
|
|
95
|
+
operation: "protected.close",
|
|
96
|
+
reason: "timeout"
|
|
97
|
+
}))
|
|
98
|
+
}), Effect.catchCause((cause) => reportBrowserCause("protected.close", cause).pipe(Effect.as("unconfirmed"))));
|
|
96
99
|
return cleanup;
|
|
97
100
|
})));
|
|
98
|
-
yield* Effect.addFinalizer(() => detached ? Effect.void : close
|
|
99
|
-
const
|
|
100
|
-
const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.
|
|
101
|
-
const caller = access.caller.pipe(
|
|
102
|
-
const pageContext =
|
|
101
|
+
yield* Effect.addFinalizer(() => detached ? Effect.void : close);
|
|
102
|
+
const publicFailure = (effect) => effect.pipe(Effect.catchCause((cause) => Effect.failCause(Cause.map(cause, (error) => inheritBrowserReport(error, fail(error.reason))))));
|
|
103
|
+
const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.catch((error) => reportBrowserCause("protected.policy.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(fail("provider")))))));
|
|
104
|
+
const caller = access.caller.pipe(publicFailure);
|
|
105
|
+
const pageContext = publicFailure(driver.context);
|
|
103
106
|
const permitObservation = Effect.gen(function* () {
|
|
104
107
|
const context = yield* pageContext;
|
|
105
108
|
let origins;
|
|
@@ -112,7 +115,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
112
115
|
exposures: [...exposures],
|
|
113
116
|
humanExposure,
|
|
114
117
|
humanOrigins
|
|
115
|
-
}).pipe(
|
|
118
|
+
}).pipe(publicFailure);
|
|
116
119
|
if (Redacted.value(yield* caller) !== Redacted.value(principal)) return yield* fail("denied");
|
|
117
120
|
const decision = yield* Schema.decodeEffect(CredentialObservationDecision)(rawDecision).pipe(Effect.mapError(() => fail("observation-blocked")));
|
|
118
121
|
if (decision === "deny") return yield* fail("observation-blocked");
|
|
@@ -122,13 +125,13 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
122
125
|
}
|
|
123
126
|
observation = "approved-after-exposure";
|
|
124
127
|
}
|
|
125
|
-
yield*
|
|
128
|
+
yield* publicFailure(driver.restrictObservation(origins));
|
|
126
129
|
return {
|
|
127
130
|
...context,
|
|
128
131
|
frameOrigins: context.frameOrigins.filter((origin) => origins === void 0 || origins.includes(origin))
|
|
129
132
|
};
|
|
130
133
|
});
|
|
131
|
-
const target = (ref) =>
|
|
134
|
+
const target = (ref) => publicFailure(driver.target(ref));
|
|
132
135
|
const authorizeAction = Effect.fn("ProtectedBrowser.authorizeAction")(function* (action) {
|
|
133
136
|
if (access.authorizeAction === void 0) {
|
|
134
137
|
if (action._tag === "Submit") return yield* fail("unsupported");
|
|
@@ -139,7 +142,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
139
142
|
caller: principal,
|
|
140
143
|
action,
|
|
141
144
|
exposures: [...exposures]
|
|
142
|
-
}).pipe(
|
|
145
|
+
}).pipe(publicFailure);
|
|
143
146
|
if (Redacted.value(yield* caller) !== Redacted.value(principal)) return yield* fail("denied");
|
|
144
147
|
if (action._tag !== "Navigate") {
|
|
145
148
|
const current = yield* target(action.ref);
|
|
@@ -165,12 +168,15 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
165
168
|
duration: remaining,
|
|
166
169
|
orElse: () => Effect.fail(fail("timeout"))
|
|
167
170
|
}), Effect.catchCause((cause) => Effect.gen(function* () {
|
|
171
|
+
const unexpected = cause.reasons.filter((reason) => reason._tag === "Die" || reason._tag === "Fail" && !Schema.is(ProtectedBrowserError)(reason.error));
|
|
172
|
+
if (unexpected.length > 0) yield* reportBrowserCause("protected.operation", Cause.fromReasons(unexpected));
|
|
168
173
|
const error = cause.reasons.find((reason) => reason._tag === "Fail" && Schema.is(ProtectedBrowserError)(reason.error));
|
|
169
|
-
const reason = error?._tag === "Fail" && Schema.is(ProtectedBrowserError)(error.error) ? error.error.reason : "provider";
|
|
174
|
+
const reason = unexpected.length === 0 && error?._tag === "Fail" && Schema.is(ProtectedBrowserError)(error.error) ? error.error.reason : "provider";
|
|
170
175
|
const interrupt = cause.reasons.some((reason) => reason._tag === "Interrupt");
|
|
171
176
|
if (dispatch !== "not-dispatched" || interrupt || reason === "provider" || reason === "timeout") yield* close;
|
|
172
177
|
if (interrupt) return yield* Effect.interrupt;
|
|
173
|
-
|
|
178
|
+
const outcome = fail(dispatch === "possibly-dispatched" && reason === "provider" ? "outcome-unknown" : reason);
|
|
179
|
+
return yield* unexpected.length > 0 ? reportedBrowserError(outcome) : inheritBrowserReport(error?._tag === "Fail" ? error.error : void 0, outcome);
|
|
174
180
|
})), Effect.onInterrupt(() => close), Effect.withTracerEnabled(false));
|
|
175
181
|
})).pipe(Effect.flatMap(Effect.fromOption(() => new ProtectedBrowserError({
|
|
176
182
|
reason: "busy",
|
|
@@ -197,13 +203,13 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
197
203
|
});
|
|
198
204
|
offers.clear();
|
|
199
205
|
dispatch = "possibly-dispatched";
|
|
200
|
-
yield*
|
|
206
|
+
yield* publicFailure(driver.navigate(decoded.url));
|
|
201
207
|
dispatch = "dispatched";
|
|
202
208
|
yield* permitObservation;
|
|
203
209
|
})),
|
|
204
210
|
observe: run(Effect.gen(function* () {
|
|
205
211
|
const before = yield* permitObservation;
|
|
206
|
-
const result = yield*
|
|
212
|
+
const result = yield* publicFailure(driver.discover);
|
|
207
213
|
const after = yield* permitObservation;
|
|
208
214
|
if (before.document !== after.document || result.document !== after.document || result.topOrigin !== after.topOrigin) return yield* fail("stale-reference");
|
|
209
215
|
if (result.frameOrigins.some((origin) => !after.frameOrigins.includes(origin))) return yield* fail("observation-blocked");
|
|
@@ -242,7 +248,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
242
248
|
};
|
|
243
249
|
yield* authorizeAction(action);
|
|
244
250
|
dispatch = "possibly-dispatched";
|
|
245
|
-
yield*
|
|
251
|
+
yield* publicFailure(driver.click(decoded.ref)).pipe(Effect.catch((error) => {
|
|
246
252
|
if (error.reason === "needs-attention") dispatch = "not-dispatched";
|
|
247
253
|
return Effect.fail(error);
|
|
248
254
|
}));
|
|
@@ -261,7 +267,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
261
267
|
target: control.target,
|
|
262
268
|
role: control.role
|
|
263
269
|
});
|
|
264
|
-
yield*
|
|
270
|
+
yield* publicFailure(driver.fill(decoded.ref, control.role, Redacted.make(decoded.value))).pipe(Effect.provideService(ProtectedBrowserDispatch, { mark: Effect.sync(() => {
|
|
265
271
|
dispatch = "possibly-dispatched";
|
|
266
272
|
}) }));
|
|
267
273
|
dispatch = "dispatched";
|
|
@@ -278,7 +284,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
278
284
|
caller: principal,
|
|
279
285
|
kind: decoded.kind,
|
|
280
286
|
target: control.target
|
|
281
|
-
}).pipe(
|
|
287
|
+
}).pipe(publicFailure);
|
|
282
288
|
const now = yield* Clock.currentTimeMillis;
|
|
283
289
|
for (const [ref, offer] of offers) if (offer.expires <= now) offers.delete(ref);
|
|
284
290
|
if (candidates.length > 16 || offers.size + candidates.length > 64) return yield* fail("limit");
|
|
@@ -288,7 +294,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
288
294
|
const pending = /* @__PURE__ */ new Map();
|
|
289
295
|
for (const candidate of candidates) {
|
|
290
296
|
const metadata = yield* decode(CredentialOfferMetadata, candidate.metadata);
|
|
291
|
-
const ref = yield* crypto.randomUUIDv4.pipe(Effect.
|
|
297
|
+
const ref = yield* crypto.randomUUIDv4.pipe(Effect.catch((error) => reportBrowserCause("protected.identity", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(fail("provider")))))));
|
|
292
298
|
pending.set(ref, {
|
|
293
299
|
caller: principal,
|
|
294
300
|
key: candidate.key,
|
|
@@ -336,12 +342,12 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
336
342
|
};
|
|
337
343
|
const authorize = Effect.gen(function* () {
|
|
338
344
|
if (Redacted.value(yield* caller) !== Redacted.value(principal)) return yield* fail("denied");
|
|
339
|
-
yield* access.authorize(authorization).pipe(
|
|
345
|
+
yield* access.authorize(authorization).pipe(publicFailure);
|
|
340
346
|
if (Redacted.value(yield* caller) !== Redacted.value(principal)) return yield* fail("denied");
|
|
341
347
|
});
|
|
342
348
|
yield* authorize;
|
|
343
349
|
yield* validate;
|
|
344
|
-
const raw = yield* access.resolve(authorization).pipe(
|
|
350
|
+
const raw = yield* access.resolve(authorization).pipe(publicFailure);
|
|
345
351
|
const material = yield* Schema.decodeEffect(offer.kind === "login" ? LoginCredential : CardCredential)(raw).pipe(Effect.mapError(() => fail("resolver")));
|
|
346
352
|
for (const field of decoded.fields) if (secretFor(material, field.role) === void 0) return yield* fail("missing-credential");
|
|
347
353
|
offers.delete(decoded.offer);
|
|
@@ -351,7 +357,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
351
357
|
const value = secretFor(material, field.role);
|
|
352
358
|
if (value === void 0) return yield* fail("missing-credential");
|
|
353
359
|
observation = "protected";
|
|
354
|
-
yield*
|
|
360
|
+
yield* publicFailure(driver.fill(field.ref, field.role, value)).pipe(Effect.provideService(ProtectedBrowserDispatch, { mark: Effect.sync(() => {
|
|
355
361
|
dispatch = "possibly-dispatched";
|
|
356
362
|
if (!exposures.some((target) => sameTarget(target, offer.target))) exposures.push(offer.target);
|
|
357
363
|
}) }));
|
|
@@ -364,7 +370,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
|
|
|
364
370
|
yield* authorize;
|
|
365
371
|
const ref = decoded.submit;
|
|
366
372
|
dispatch = "possibly-dispatched";
|
|
367
|
-
yield*
|
|
373
|
+
yield* publicFailure(driver.click(ref)).pipe(Effect.catch((error) => {
|
|
368
374
|
if (error.reason === "needs-attention") dispatch = "dispatched";
|
|
369
375
|
return Effect.fail(error);
|
|
370
376
|
}));
|
|
@@ -575,27 +581,30 @@ const Descriptions = Schema.Array(Schema.NullOr(Description)).check(Schema.isMax
|
|
|
575
581
|
/** One host-private acquired session. The SDK handles and exact-session cleanup have one owner. */
|
|
576
582
|
var ProtectedNativeSession = class extends Context.Service()("@effect-agent/platform-cloudflare/ProtectedNativeSession") {};
|
|
577
583
|
const transportError = (reason) => new ProtectedTransportError({ reason });
|
|
578
|
-
const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.
|
|
579
|
-
const remote = (run) => Effect.tryPromise({
|
|
584
|
+
const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.catch((error) => reportBrowserCause("protected.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(transportError("provider")))))));
|
|
585
|
+
const remote = (operation, run) => Effect.tryPromise({
|
|
580
586
|
try: async (signal) => {
|
|
581
587
|
try {
|
|
582
588
|
return {
|
|
583
589
|
ok: true,
|
|
584
590
|
value: await run(signal)
|
|
585
591
|
};
|
|
586
|
-
} catch {
|
|
587
|
-
return {
|
|
592
|
+
} catch (cause) {
|
|
593
|
+
return {
|
|
594
|
+
ok: false,
|
|
595
|
+
failure: browserFailure(operation, cause)
|
|
596
|
+
};
|
|
588
597
|
}
|
|
589
598
|
},
|
|
590
|
-
catch: () =>
|
|
591
|
-
}).pipe(Effect.flatMap((result) => result.ok ? Effect.succeed(result.value) : Effect.fail(transportError("provider"))));
|
|
599
|
+
catch: (cause) => browserFailure(operation, cause)
|
|
600
|
+
}).pipe(Effect.flatMap((result) => result.ok ? Effect.succeed(result.value) : Effect.fail(result.failure)), Effect.catch((failure) => reportBrowserCause(operation, Cause.fail(failure)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(transportError("provider")))))));
|
|
592
601
|
/** Host-private scoped transport. Session capabilities are requirements; policy is per-pass data. */
|
|
593
602
|
const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(function* (policy) {
|
|
594
603
|
const session = yield* ProtectedNativeSession;
|
|
595
604
|
const { browser, page } = session;
|
|
596
605
|
const crypto = yield* Crypto.Crypto;
|
|
597
606
|
const clock = yield* Clock.Clock;
|
|
598
|
-
const uuid = crypto.randomUUIDv4.pipe(Effect.
|
|
607
|
+
const uuid = crypto.randomUUIDv4.pipe(Effect.catch((error) => reportBrowserCause("protected.identity", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(transportError("provider")))))));
|
|
599
608
|
let closed = false;
|
|
600
609
|
let violation = false;
|
|
601
610
|
let documentRef;
|
|
@@ -652,7 +661,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
|
|
|
652
661
|
});
|
|
653
662
|
const isCurrent = Effect.fn("ProtectedNativeTransport.isCurrent")(function* (state) {
|
|
654
663
|
if (state.frame.detached) return false;
|
|
655
|
-
return yield* remote(() => state.handle.evaluate((held) => {
|
|
664
|
+
return yield* remote("protected.validate-document", () => state.handle.evaluate((held) => {
|
|
656
665
|
if (typeof held !== "object" || held === null) return false;
|
|
657
666
|
return Reflect.get(held, "doc") === Reflect.get(globalThis, "document");
|
|
658
667
|
}));
|
|
@@ -661,7 +670,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
|
|
|
661
670
|
yield* check;
|
|
662
671
|
const state = controls.get(ref);
|
|
663
672
|
if (!state || observationOrigins !== void 0 && !observationOrigins.has(state.control.target.frameOrigin) || state.expires <= (yield* clock.currentTimeMillis) || !(yield* isCurrent(state.frame))) return yield* transportError("stale-reference");
|
|
664
|
-
const current = yield* remote(() => state.frame.handle.evaluate((held, index) => {
|
|
673
|
+
const current = yield* remote("protected.validate-control", () => state.frame.handle.evaluate((held, index) => {
|
|
665
674
|
if (typeof held !== "object" || held === null) return null;
|
|
666
675
|
return Reflect.apply(Reflect.get(held, "validate"), held, [index]);
|
|
667
676
|
}, state.index));
|
|
@@ -704,14 +713,14 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
|
|
|
704
713
|
yield* check;
|
|
705
714
|
yield* origin(url);
|
|
706
715
|
clear();
|
|
707
|
-
yield* remote(() => page.goto(url, { waitUntil: "load" }));
|
|
716
|
+
yield* remote("protected.navigate", () => page.goto(url, { waitUntil: "load" }));
|
|
708
717
|
yield* context;
|
|
709
718
|
}),
|
|
710
719
|
target: (ref) => get(ref).pipe(Effect.map((state) => state.control)),
|
|
711
720
|
discover: Effect.gen(function* () {
|
|
712
721
|
const before = yield* context;
|
|
713
722
|
controls.clear();
|
|
714
|
-
for (const state of frames.values()) yield* remote(() => state.handle.dispose());
|
|
723
|
+
for (const state of frames.values()) yield* remote("protected.dispose", () => state.handle.dispose());
|
|
715
724
|
frames.clear();
|
|
716
725
|
const discovered = [];
|
|
717
726
|
let text = "";
|
|
@@ -722,7 +731,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
|
|
|
722
731
|
if (observationOrigins !== void 0 && !observationOrigins.has(frameOrigin)) continue;
|
|
723
732
|
frameOrigins.add(frameOrigin);
|
|
724
733
|
if (typeof frame.isolatedRealm !== "function") return yield* transportError("unsupported");
|
|
725
|
-
const handle = yield* remote(() => frame.isolatedRealm().evaluateHandle(inspectFrame));
|
|
734
|
+
const handle = yield* remote("protected.discover", () => frame.isolatedRealm().evaluateHandle(inspectFrame));
|
|
726
735
|
const state = {
|
|
727
736
|
frame,
|
|
728
737
|
handle,
|
|
@@ -731,7 +740,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
|
|
|
731
740
|
forms: /* @__PURE__ */ new Map()
|
|
732
741
|
};
|
|
733
742
|
frames.set(frame, state);
|
|
734
|
-
const raw = yield* remote(() => handle.evaluate((held) => {
|
|
743
|
+
const raw = yield* remote("protected.describe-controls", () => handle.evaluate((held) => {
|
|
735
744
|
if (typeof held !== "object" || held === null) return null;
|
|
736
745
|
return Reflect.get(held, "original");
|
|
737
746
|
}));
|
|
@@ -773,7 +782,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
|
|
|
773
782
|
});
|
|
774
783
|
discovered.push(control);
|
|
775
784
|
}
|
|
776
|
-
const rawText = yield* remote(() => handle.evaluate((held) => {
|
|
785
|
+
const rawText = yield* remote("protected.read-text", () => handle.evaluate((held) => {
|
|
777
786
|
if (typeof held !== "object" || held === null) return null;
|
|
778
787
|
return Reflect.apply(Reflect.get(held, "text"), held, []);
|
|
779
788
|
}));
|
|
@@ -796,7 +805,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
|
|
|
796
805
|
const dispatch = yield* ProtectedBrowserDispatch;
|
|
797
806
|
const state = yield* get(ref);
|
|
798
807
|
yield* dispatch.mark;
|
|
799
|
-
const filled = yield* remote(() => state.frame.handle.evaluate((held, index, expectedRole, secret) => {
|
|
808
|
+
const filled = yield* remote("protected.fill", () => state.frame.handle.evaluate((held, index, expectedRole, secret) => {
|
|
800
809
|
if (typeof held !== "object" || held === null) return false;
|
|
801
810
|
return Reflect.apply(Reflect.get(held, "fill"), held, [
|
|
802
811
|
index,
|
|
@@ -809,7 +818,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
|
|
|
809
818
|
}),
|
|
810
819
|
click: Effect.fn("ProtectedNativeTransport.click")(function* (ref) {
|
|
811
820
|
const state = yield* get(ref);
|
|
812
|
-
const clicked = yield* remote(() => state.frame.handle.evaluate((held, index) => {
|
|
821
|
+
const clicked = yield* remote("protected.click", () => state.frame.handle.evaluate((held, index) => {
|
|
813
822
|
if (typeof held !== "object" || held === null) return false;
|
|
814
823
|
return Reflect.apply(Reflect.get(held, "click"), held, [index]);
|
|
815
824
|
}, state.index));
|
|
@@ -834,6 +843,7 @@ var BrowserRunProtectedBinding = class extends Context.Service()("@effect-agent/
|
|
|
834
843
|
*/
|
|
835
844
|
const protectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedBinding)(Effect.gen(function* () {
|
|
836
845
|
const lifecycle = yield* BrowserRunSessionLifecycle;
|
|
846
|
+
const binding = yield* BrowserRunBinding;
|
|
837
847
|
const crypto = yield* Crypto.Crypto;
|
|
838
848
|
return { open: Effect.fn("BrowserRunProtectedTransport.open")(function* (policy, identity) {
|
|
839
849
|
const failure = () => new ProtectedBrowserError({
|
|
@@ -850,108 +860,117 @@ const protectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedBindi
|
|
|
850
860
|
let browser;
|
|
851
861
|
let driver;
|
|
852
862
|
let invalid = false;
|
|
853
|
-
const runCleanup = Effect.runPromiseWith(
|
|
863
|
+
const runCleanup = Effect.runPromiseWith(yield* Effect.context());
|
|
854
864
|
const terminate = Effect.gen(function* () {
|
|
855
865
|
invalid = true;
|
|
856
866
|
driver?.invalidate();
|
|
857
867
|
if (sessionId === void 0) return "unconfirmed";
|
|
858
|
-
const cleanup = yield* lifecycle.close(sessionId).pipe(Effect.as("confirmed"), Effect.catchCause(() => Effect.
|
|
868
|
+
const cleanup = yield* lifecycle.close(sessionId).pipe(Effect.as("confirmed"), Effect.catchCause((cause) => reportBrowserCause("protected.close", cause).pipe(Effect.as("unconfirmed"))), Effect.interruptible, Effect.timeoutOrElse({
|
|
859
869
|
duration: "10 seconds",
|
|
860
|
-
orElse: () =>
|
|
870
|
+
orElse: () => reportBrowserCause("protected.close", Cause.fail(new BrowserRunFailure({
|
|
871
|
+
operation: "protected.close",
|
|
872
|
+
reason: "timeout"
|
|
873
|
+
}))).pipe(Effect.as("unconfirmed"))
|
|
861
874
|
}));
|
|
862
875
|
const connected = browser;
|
|
863
|
-
if (connected !== void 0) yield* Effect.promise(() => connected.disconnect()).pipe(Effect.catchCause(() =>
|
|
876
|
+
if (connected !== void 0) yield* Effect.promise(() => connected.disconnect()).pipe(Effect.catchCause((cause) => reportBrowserCause("protected.disconnect", cause)), Effect.interruptible, Effect.timeoutOrElse({
|
|
864
877
|
duration: "1 second",
|
|
865
|
-
orElse: () =>
|
|
878
|
+
orElse: () => reportBrowserCause("protected.disconnect", Cause.fail(new BrowserRunFailure({
|
|
879
|
+
operation: "protected.disconnect",
|
|
880
|
+
reason: "timeout"
|
|
881
|
+
})))
|
|
866
882
|
}));
|
|
867
883
|
return cleanup;
|
|
868
884
|
});
|
|
869
885
|
const close = yield* Effect.cached(terminate);
|
|
870
|
-
yield* Effect.addFinalizer(() => detached ? Effect.void : close
|
|
886
|
+
yield* Effect.addFinalizer(() => detached ? Effect.void : close);
|
|
887
|
+
let stage = identity === void 0 ? "protected.acquire" : "protected.connect";
|
|
871
888
|
const acquired = yield* Effect.tryPromise({
|
|
872
889
|
try: async (signal) => {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
url.searchParams.set("recording", "false");
|
|
879
|
-
recordingDisabled = url.searchParams.get("recording") === "false";
|
|
880
|
-
return options.browser.fetch(new Request(url, request));
|
|
890
|
+
try {
|
|
891
|
+
sessionId = identity === void 0 ? Redacted.make(await binding.acquire(Math.min(6e5, Math.max(1e4, policy.maxElapsedMillis)), "protected.acquire")) : identity.sessionId;
|
|
892
|
+
if (signal.aborted || invalid) {
|
|
893
|
+
await runCleanup(terminate);
|
|
894
|
+
throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
881
895
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
sessionId = Redacted.make(Schema.decodeSync(Schema.String.check(Schema.isUUID()))(acquired.sessionId));
|
|
888
|
-
if (identity === void 0 && !recordingDisabled || signal.aborted || invalid) {
|
|
889
|
-
await runCleanup(terminate);
|
|
890
|
-
throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
891
|
-
}
|
|
892
|
-
browser = await puppeteer.connect(options.browser, acquired.sessionId);
|
|
893
|
-
if (signal.aborted || invalid) {
|
|
894
|
-
await runCleanup(terminate);
|
|
895
|
-
throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
896
|
-
}
|
|
897
|
-
let page;
|
|
898
|
-
if (identity === void 0) page = await (await browser.createBrowserContext()).newPage();
|
|
899
|
-
else {
|
|
900
|
-
const context = browser.browserContexts().find((candidate) => candidate.id === Redacted.value(identity.contextId));
|
|
901
|
-
if (context === void 0) throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
902
|
-
const pages = await context.pages();
|
|
903
|
-
let found;
|
|
904
|
-
for (const candidate of pages) {
|
|
905
|
-
const client = await candidate.createCDPSession();
|
|
906
|
-
const info = await client.send("Target.getTargetInfo");
|
|
907
|
-
await client.detach();
|
|
908
|
-
if (info.targetInfo.targetId === Redacted.value(identity.targetId)) found = candidate;
|
|
896
|
+
stage = "protected.connect";
|
|
897
|
+
browser = await binding.connect(Redacted.value(sessionId), stage);
|
|
898
|
+
if (signal.aborted || invalid) {
|
|
899
|
+
await runCleanup(terminate);
|
|
900
|
+
throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
909
901
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
902
|
+
let page;
|
|
903
|
+
if (identity === void 0) {
|
|
904
|
+
stage = "protected.context";
|
|
905
|
+
const context = await browser.createBrowserContext();
|
|
906
|
+
stage = "protected.page";
|
|
907
|
+
page = await context.newPage();
|
|
908
|
+
} else {
|
|
909
|
+
stage = "protected.resume";
|
|
910
|
+
const context = browser.browserContexts().find((candidate) => candidate.id === Redacted.value(identity.contextId));
|
|
911
|
+
if (context === void 0) throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
912
|
+
const pages = await context.pages();
|
|
913
|
+
let found;
|
|
914
|
+
for (const candidate of pages) {
|
|
915
|
+
const client = await candidate.createCDPSession();
|
|
916
|
+
const info = await client.send("Target.getTargetInfo");
|
|
917
|
+
await client.detach();
|
|
918
|
+
if (info.targetInfo.targetId === Redacted.value(identity.targetId)) found = candidate;
|
|
919
|
+
}
|
|
920
|
+
if (found === void 0 || pages.length !== 1) throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
921
|
+
page = found;
|
|
922
|
+
}
|
|
923
|
+
stage = "protected.control";
|
|
924
|
+
control = await page.createCDPSession();
|
|
925
|
+
const info = await control.send("Target.getTargetInfo");
|
|
926
|
+
stage = "protected.identity";
|
|
927
|
+
providerIdentity = Schema.decodeSync(ProtectedProviderIdentity)({
|
|
928
|
+
sessionId,
|
|
929
|
+
contextId: Redacted.make(page.browserContext().id ?? ""),
|
|
930
|
+
targetId: Redacted.make(info.targetInfo.targetId)
|
|
931
931
|
});
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
await
|
|
935
|
-
|
|
932
|
+
stage = "protected.interception";
|
|
933
|
+
await page.setBypassServiceWorker(true);
|
|
934
|
+
await page.setRequestInterception(true);
|
|
935
|
+
page.on("request", (request) => {
|
|
936
|
+
let allowed = policy.network._tag === "Unrestricted";
|
|
937
|
+
try {
|
|
938
|
+
const url = new URL(request.url());
|
|
939
|
+
allowed ||= policy.network._tag === "ExactHosts" && url.protocol === "https:" && !url.username && !url.password && policy.network.allowedHosts.includes(url.host);
|
|
940
|
+
} catch {}
|
|
941
|
+
(allowed && !invalid ? request.continue() : request.abort("blockedbyclient")).catch(async (cause) => {
|
|
942
|
+
invalid = true;
|
|
943
|
+
driver?.invalidate();
|
|
944
|
+
await runCleanup(reportBrowserCause("protected.interception", Cause.fail(cause)));
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
if (signal.aborted || invalid) {
|
|
948
|
+
await runCleanup(terminate);
|
|
949
|
+
throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
950
|
+
}
|
|
951
|
+
return ProtectedNativeSession.of({
|
|
952
|
+
browser,
|
|
953
|
+
page,
|
|
954
|
+
close,
|
|
955
|
+
release: Effect.suspend(() => detached ? Effect.void : close.pipe(Effect.asVoid))
|
|
956
|
+
});
|
|
957
|
+
} catch (cause) {
|
|
958
|
+
if (signal.aborted && !(cause instanceof ProtectedTransportError)) await runCleanup(reportBrowserCause(stage, Cause.fail(cause)));
|
|
959
|
+
throw cause;
|
|
936
960
|
}
|
|
937
|
-
return ProtectedNativeSession.of({
|
|
938
|
-
browser,
|
|
939
|
-
page,
|
|
940
|
-
close,
|
|
941
|
-
release: Effect.suspend(() => detached ? Effect.void : close.pipe(Effect.asVoid))
|
|
942
|
-
});
|
|
943
961
|
},
|
|
944
|
-
catch:
|
|
962
|
+
catch: (cause) => browserFailure(stage, cause)
|
|
945
963
|
}).pipe(Effect.timeoutOrElse({
|
|
946
964
|
duration: Math.min(policy.maxElapsedMillis, 3e4),
|
|
947
|
-
orElse: () => Effect.fail(new
|
|
948
|
-
|
|
965
|
+
orElse: () => Effect.fail(new BrowserRunFailure({
|
|
966
|
+
operation: stage,
|
|
949
967
|
reason: "timeout"
|
|
950
968
|
}))
|
|
951
|
-
}), Effect.catch((error) =>
|
|
952
|
-
...
|
|
969
|
+
}), Effect.catch((error) => reportBrowserCause(stage, Cause.fail(error)).pipe(Effect.andThen(close), Effect.flatMap((cleanup) => Effect.fail(reportedBrowserError(new ProtectedBrowserError({
|
|
970
|
+
...failure(),
|
|
971
|
+
reason: error.reason === "timeout" ? "timeout" : "provider",
|
|
953
972
|
cleanup
|
|
954
|
-
}))))));
|
|
973
|
+
})))))));
|
|
955
974
|
driver = yield* makeProtectedNativeTransport(policy).pipe(Effect.provideService(ProtectedNativeSession, acquired), Effect.provideService(Crypto.Crypto, crypto));
|
|
956
975
|
const exact = providerIdentity;
|
|
957
976
|
if (exact === void 0) return yield* failure();
|
|
@@ -964,11 +983,14 @@ const protectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedBindi
|
|
|
964
983
|
const send = Reflect.get(control, "send");
|
|
965
984
|
return await Reflect.apply(send, control, [method, parameters]);
|
|
966
985
|
},
|
|
967
|
-
catch:
|
|
986
|
+
catch: (cause) => browserFailure("protected.command", cause)
|
|
968
987
|
}).pipe(Effect.timeoutOrElse({
|
|
969
988
|
duration: "10 seconds",
|
|
970
|
-
orElse: () => Effect.fail(
|
|
971
|
-
|
|
989
|
+
orElse: () => Effect.fail(new BrowserRunFailure({
|
|
990
|
+
operation: "protected.command",
|
|
991
|
+
reason: "timeout"
|
|
992
|
+
}))
|
|
993
|
+
}), Effect.catch((error) => reportBrowserCause("protected.command", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure())))))),
|
|
972
994
|
detach: Effect.tryPromise({
|
|
973
995
|
try: async () => {
|
|
974
996
|
if (invalid || detached || browser === void 0) throw void 0;
|
|
@@ -976,14 +998,17 @@ const protectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedBindi
|
|
|
976
998
|
detached = true;
|
|
977
999
|
driver?.invalidate();
|
|
978
1000
|
},
|
|
979
|
-
catch:
|
|
1001
|
+
catch: (cause) => browserFailure("protected.detach", cause)
|
|
980
1002
|
}).pipe(Effect.timeoutOrElse({
|
|
981
1003
|
duration: "10 seconds",
|
|
982
|
-
orElse: () => Effect.fail(
|
|
983
|
-
|
|
1004
|
+
orElse: () => Effect.fail(new BrowserRunFailure({
|
|
1005
|
+
operation: "protected.detach",
|
|
1006
|
+
reason: "timeout"
|
|
1007
|
+
}))
|
|
1008
|
+
}), Effect.catch((error) => reportBrowserCause("protected.detach", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure()))))))
|
|
984
1009
|
};
|
|
985
1010
|
}, Effect.withTracerEnabled(false)) };
|
|
986
|
-
}));
|
|
1011
|
+
})).pipe(Layer.provide(BrowserRunBinding.layer(options.browser)));
|
|
987
1012
|
/** One binding implementation serves scoped tools and host-managed protected handoffs. */
|
|
988
1013
|
const browserRunProtectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedTransport)(Effect.gen(function* () {
|
|
989
1014
|
const binding = yield* BrowserRunProtectedBinding;
|
|
@@ -1048,7 +1073,7 @@ const browserRunProtectedHostLayer = () => Layer.effect(BrowserRunProtectedHost)
|
|
|
1048
1073
|
if (handoff?.handoffId === void 0) return yield* failure("denied");
|
|
1049
1074
|
yield* fits(0);
|
|
1050
1075
|
const raw = yield* provider.command("Cloudflare.getHandoffState", {});
|
|
1051
|
-
const result = yield* Schema.decodeUnknownEffect(HandoffState)(raw).pipe(Effect.
|
|
1076
|
+
const result = yield* Schema.decodeUnknownEffect(HandoffState)(raw).pipe(Effect.catch((error) => reportBrowserCause("protected.host.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("provider")))))));
|
|
1052
1077
|
if (result.active && result.handoffId === void 0 || result.handoffId !== void 0 && result.handoffId !== Redacted.value(handoff.handoffId)) return yield* failure("denied");
|
|
1053
1078
|
return BrowserRunHandoffState.make({
|
|
1054
1079
|
active: result.active,
|
|
@@ -1079,13 +1104,13 @@ const browserRunProtectedHostLayer = () => Layer.effect(BrowserRunProtectedHost)
|
|
|
1079
1104
|
const result = yield* provider.command("Cloudflare.handoff", {
|
|
1080
1105
|
instructions: decoded.instructions,
|
|
1081
1106
|
timeout: decoded.timeout
|
|
1082
|
-
}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Handoff)), Effect.
|
|
1107
|
+
}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Handoff)), Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : reportBrowserCause("protected.handoff", cause).pipe(Effect.andThen(state.handle.close), Effect.flatMap((cleanup) => Effect.fail(reportedBrowserError(new ProtectedBrowserError({
|
|
1083
1108
|
reason: "outcome-unknown",
|
|
1084
1109
|
dispatch: "possibly-dispatched",
|
|
1085
1110
|
milestone: suspended.milestone,
|
|
1086
1111
|
observation: "closed",
|
|
1087
1112
|
cleanup
|
|
1088
|
-
}))))), Effect.onInterrupt(() => state.handle.close));
|
|
1113
|
+
})))))), Effect.onInterrupt(() => state.handle.close));
|
|
1089
1114
|
handoff = BrowserRunProtectedCheckpoint.make({
|
|
1090
1115
|
...provider.identity,
|
|
1091
1116
|
protected: suspended,
|
|
@@ -1101,14 +1126,14 @@ const browserRunProtectedHostLayer = () => Layer.effect(BrowserRunProtectedHost)
|
|
|
1101
1126
|
mode: decoded.mode,
|
|
1102
1127
|
expiresInMs: decoded.expiresInMs
|
|
1103
1128
|
});
|
|
1104
|
-
const result = yield* Schema.decodeUnknownEffect(LiveView)(raw).pipe(Effect.
|
|
1105
|
-
return yield* Schema.decodeEffect(BrowserRunLiveViewResult)({ devtoolsFrontendUrl: Redacted.make(result.devtoolsFrontendUrl) }).pipe(Effect.
|
|
1129
|
+
const result = yield* Schema.decodeUnknownEffect(LiveView)(raw).pipe(Effect.catch((error) => reportBrowserCause("protected.host.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("provider")))))));
|
|
1130
|
+
return yield* Schema.decodeEffect(BrowserRunLiveViewResult)({ devtoolsFrontendUrl: Redacted.make(result.devtoolsFrontendUrl) }).pipe(Effect.catch((error) => reportBrowserCause("protected.host.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("provider")))))));
|
|
1106
1131
|
})),
|
|
1107
1132
|
getHandoffState: control(getHandoffState),
|
|
1108
1133
|
getReturnOrigin: control(Effect.gen(function* () {
|
|
1109
1134
|
if ((yield* getHandoffState).active) return yield* failure("busy");
|
|
1110
|
-
const context = yield* provider.driver.context.pipe(Effect.mapError((error) => failure(error.reason)));
|
|
1111
|
-
return yield* Schema.decodeEffect(CredentialOrigin)(context.topOrigin).pipe(Effect.
|
|
1135
|
+
const context = yield* provider.driver.context.pipe(Effect.mapError((error) => inheritBrowserReport(error, failure(error.reason))));
|
|
1136
|
+
return yield* Schema.decodeEffect(CredentialOrigin)(context.topOrigin).pipe(Effect.catch((error) => reportBrowserCause("protected.host.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("provider")))))));
|
|
1112
1137
|
})),
|
|
1113
1138
|
returnControl: control(Effect.gen(function* () {
|
|
1114
1139
|
if ((yield* getHandoffState).active) return yield* failure("busy");
|
|
@@ -1126,7 +1151,7 @@ const browserRunProtectedHostLayer = () => Layer.effect(BrowserRunProtectedHost)
|
|
|
1126
1151
|
return {
|
|
1127
1152
|
open: (policy) => open(policy),
|
|
1128
1153
|
resume: (checkpoint) => Schema.decodeEffect(BrowserRunProtectedCheckpoint)(checkpoint).pipe(Effect.mapError(() => failure("denied")), Effect.flatMap((decoded) => open(decoded.protected.policy, decoded))),
|
|
1129
|
-
closeSession: (id) => lifecycle.close(id).pipe(Effect.as("confirmed"), Effect.catchCause(() => Effect.
|
|
1154
|
+
closeSession: (id) => lifecycle.close(id).pipe(Effect.as("confirmed"), Effect.catchCause((cause) => reportBrowserCause("protected.closeSession", cause).pipe(Effect.as("unconfirmed"))))
|
|
1130
1155
|
};
|
|
1131
1156
|
}));
|
|
1132
1157
|
//#endregion
|