effect-browser 0.2.0-beta.9 → 0.3.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +861 -1161
- package/dist/Browser-B4qncxe7.mjs +628 -0
- package/dist/Browser-B4qncxe7.mjs.map +1 -0
- package/dist/Browser.d.mts +129 -3
- package/dist/Browser.mjs +2 -22
- package/dist/BrowserError-Coy7EW1N.d.mts +140 -0
- package/dist/BrowserError.d.mts +2 -0
- package/dist/BrowserError.mjs +211 -0
- package/dist/BrowserError.mjs.map +1 -0
- package/dist/BrowserEvent-BRu2juEz.d.mts +279 -0
- package/dist/BrowserEvent.d.mts +2 -0
- package/dist/BrowserEvent.mjs +297 -0
- package/dist/BrowserEvent.mjs.map +1 -0
- package/dist/Cdp.d.mts +32 -0
- package/dist/Cdp.mjs +61 -0
- package/dist/Cdp.mjs.map +1 -0
- package/dist/Change-DNvztNLu.mjs +138 -0
- package/dist/Change-DNvztNLu.mjs.map +1 -0
- package/dist/Change-iMlcuzVD.d.mts +71 -0
- package/dist/Change.d.mts +2 -0
- package/dist/Change.mjs +2 -0
- package/dist/Chromium.d.mts +29 -81
- package/dist/Chromium.mjs +57 -392
- package/dist/Chromium.mjs.map +1 -1
- package/dist/Expect.d.mts +62 -0
- package/dist/Expect.mjs +114 -0
- package/dist/Expect.mjs.map +1 -0
- package/dist/Frame-Di2PxyF7.d.mts +119 -0
- package/dist/Frame.d.mts +2 -0
- package/dist/Frame.mjs +125 -0
- package/dist/Frame.mjs.map +1 -0
- package/dist/Moment.d.mts +2 -0
- package/dist/Moment.mjs +288 -0
- package/dist/Moment.mjs.map +1 -0
- package/dist/Motion.d.mts +40 -0
- package/dist/Motion.mjs +122 -0
- package/dist/Motion.mjs.map +1 -0
- package/dist/Page-B5cjaUp5.mjs +618 -0
- package/dist/Page-B5cjaUp5.mjs.map +1 -0
- package/dist/Page-B7X6DQUn.d.mts +595 -0
- package/dist/Page.d.mts +2 -0
- package/dist/Page.mjs +2 -0
- package/dist/Plan-Akcac63g.d.mts +209 -0
- package/dist/Plan-D1wrJVs8.mjs +508 -0
- package/dist/Plan-D1wrJVs8.mjs.map +1 -0
- package/dist/Plan.d.mts +2 -3
- package/dist/Plan.mjs +2 -2
- package/dist/Policy.d.mts +76 -0
- package/dist/Policy.mjs +237 -0
- package/dist/Policy.mjs.map +1 -0
- package/dist/Presentation.d.mts +48 -0
- package/dist/Presentation.mjs +117 -0
- package/dist/Presentation.mjs.map +1 -0
- package/dist/Snapshot-B34hZ4Jl.d.mts +68 -0
- package/dist/Snapshot.d.mts +2 -0
- package/dist/Snapshot.mjs +79 -0
- package/dist/Snapshot.mjs.map +1 -0
- package/dist/Stage.d.mts +64 -0
- package/dist/Stage.mjs +237 -0
- package/dist/Stage.mjs.map +1 -0
- package/dist/Supervisor-CYWFK3vM.d.mts +280 -0
- package/dist/Supervisor-TNKNOVzu.mjs +599 -0
- package/dist/Supervisor-TNKNOVzu.mjs.map +1 -0
- package/dist/Supervisor.d.mts +2 -0
- package/dist/Supervisor.mjs +2 -0
- package/dist/context-B6xr8nVi.mjs +261 -0
- package/dist/context-B6xr8nVi.mjs.map +1 -0
- package/dist/index.d.mts +17 -14
- package/dist/index.mjs +18 -13
- package/dist/match.inpage-xOe9LNO0.mjs +48 -0
- package/dist/match.inpage-xOe9LNO0.mjs.map +1 -0
- package/dist/page-BAf__WKX.mjs +5075 -0
- package/dist/page-BAf__WKX.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/url-Cd04Okid.mjs +264 -0
- package/dist/url-Cd04Okid.mjs.map +1 -0
- package/package.json +96 -61
- package/src/Browser.ts +672 -0
- package/src/BrowserError.ts +234 -0
- package/src/BrowserEvent.ts +296 -0
- package/src/Cdp.ts +101 -0
- package/src/Change.ts +88 -0
- package/src/Chromium.ts +102 -0
- package/src/Expect.ts +180 -0
- package/src/Frame.ts +132 -0
- package/src/Moment.ts +479 -0
- package/src/Motion.ts +166 -0
- package/src/Page.ts +522 -0
- package/src/Plan.ts +663 -0
- package/src/Policy.ts +325 -0
- package/src/Presentation.ts +195 -0
- package/src/Snapshot.ts +89 -0
- package/src/Stage.ts +467 -0
- package/src/Supervisor.ts +599 -0
- package/src/index.ts +20 -0
- package/src/internal/input/actions.ts +610 -0
- package/src/internal/input/dispatch.ts +161 -0
- package/src/internal/input/edit.inpage.ts +162 -0
- package/src/internal/input/evidence.inpage.ts +164 -0
- package/src/internal/input/gate.ts +224 -0
- package/src/internal/input/guard.inpage.ts +605 -0
- package/src/internal/input/guard.ts +184 -0
- package/src/internal/input/keyboard.ts +112 -0
- package/src/internal/input/keys.ts +127 -0
- package/src/internal/input/perform.ts +376 -0
- package/src/internal/input/pointer.ts +153 -0
- package/src/internal/input/replies.ts +195 -0
- package/src/internal/input/settle.ts +50 -0
- package/src/internal/input/style.ts +186 -0
- package/src/internal/input/targets.inpage.ts +293 -0
- package/src/internal/input/targets.ts +148 -0
- package/src/internal/page/bridge.ts +391 -0
- package/src/internal/page/context.ts +314 -0
- package/src/internal/page/identity.ts +35 -0
- package/src/internal/page/lane.ts +311 -0
- package/src/internal/page/navigation.ts +183 -0
- package/src/internal/page/openings.ts +87 -0
- package/src/internal/page/page.ts +165 -0
- package/src/internal/page/url.ts +100 -0
- package/src/internal/page/viewport.ts +43 -0
- package/src/internal/pictures/capture.ts +776 -0
- package/src/internal/pictures/clock.ts +141 -0
- package/src/internal/pictures/counts.ts +92 -0
- package/src/internal/pictures/pictures.ts +521 -0
- package/src/internal/pictures/transport.ts +243 -0
- package/src/internal/reading/choose.ts +100 -0
- package/src/internal/reading/context.inpage.ts +201 -0
- package/src/internal/reading/controls.inpage.ts +158 -0
- package/src/internal/reading/expect.inpage.ts +70 -0
- package/src/internal/reading/match.inpage.ts +92 -0
- package/src/internal/reading/names.inpage.ts +306 -0
- package/src/internal/reading/outline.inpage.ts +260 -0
- package/src/internal/reading/reading.ts +223 -0
- package/src/internal/reading/ready.inpage.ts +164 -0
- package/src/internal/reading/ready.ts +111 -0
- package/src/internal/reading/subjects.inpage.ts +188 -0
- package/src/internal/reading/text.inpage.ts +135 -0
- package/src/internal/reading/walk.inpage.ts +220 -0
- package/src/internal/supervisor/lifecycle.ts +416 -0
- package/src/internal/timeline/changes.inpage.ts +216 -0
- package/src/internal/timeline/changes.ts +100 -0
- package/src/internal/timeline/events.ts +94 -0
- package/src/internal/timeline/fold.inpage.ts +123 -0
- package/src/internal/timeline/history.inpage.ts +229 -0
- package/src/internal/timeline/marks.inpage.ts +207 -0
- package/src/internal/timeline/record.inpage.ts +133 -0
- package/src/internal/timeline/sight.inpage.ts +122 -0
- package/src/internal/timeline/window.ts +96 -0
- package/src/internal/timeline/words.ts +65 -0
- package/dist/Bootstrap-B9UUXsvF.d.mts +0 -183
- package/dist/Bootstrap-CeQOWBy6.mjs +0 -189
- package/dist/Bootstrap-CeQOWBy6.mjs.map +0 -1
- package/dist/Bootstrap.d.mts +0 -3
- package/dist/Bootstrap.mjs +0 -2
- package/dist/Browser-BF9_sORP.d.mts +0 -388
- package/dist/Browser.mjs.map +0 -1
- package/dist/BrowserData-CPIIIHol.d.mts +0 -585
- package/dist/BrowserData.d.mts +0 -3
- package/dist/BrowserData.mjs +0 -695
- package/dist/BrowserData.mjs.map +0 -1
- package/dist/BrowserRuntime-B6nT1G4X.d.mts +0 -148
- package/dist/BrowserRuntime-C1r2aSRK.mjs +0 -6820
- package/dist/BrowserRuntime-C1r2aSRK.mjs.map +0 -1
- package/dist/BrowserRuntime.d.mts +0 -3
- package/dist/BrowserRuntime.mjs +0 -3
- package/dist/Capture.d.mts +0 -56
- package/dist/Capture.mjs +0 -678
- package/dist/Capture.mjs.map +0 -1
- package/dist/CaptureData-CYriZHX1.mjs +0 -201
- package/dist/CaptureData-CYriZHX1.mjs.map +0 -1
- package/dist/CaptureData.d.mts +0 -219
- package/dist/CaptureData.mjs +0 -2
- package/dist/Errors.d.mts +0 -158
- package/dist/Errors.mjs +0 -207
- package/dist/Errors.mjs.map +0 -1
- package/dist/Images-yCG9BmRU.mjs +0 -76
- package/dist/Images-yCG9BmRU.mjs.map +0 -1
- package/dist/OperationOptions-DAeRS6O9.mjs +0 -55
- package/dist/OperationOptions-DAeRS6O9.mjs.map +0 -1
- package/dist/PageControl.d.mts +0 -15
- package/dist/PageControl.mjs +0 -34
- package/dist/PageControl.mjs.map +0 -1
- package/dist/PageRegistry-B3q9DS3g.mjs +0 -164
- package/dist/PageRegistry-B3q9DS3g.mjs.map +0 -1
- package/dist/Plan-Dyxu2q8C.mjs +0 -543
- package/dist/Plan-Dyxu2q8C.mjs.map +0 -1
- package/dist/PlanData-B668RxbQ.d.mts +0 -4831
- package/dist/PlanData.d.mts +0 -3
- package/dist/PlanData.mjs +0 -268
- package/dist/PlanData.mjs.map +0 -1
- package/dist/PlanInput-BJcPPncs.mjs +0 -75
- package/dist/PlanInput-BJcPPncs.mjs.map +0 -1
- package/dist/PublicSession-DCSMMqOj.mjs +0 -5602
- package/dist/PublicSession-DCSMMqOj.mjs.map +0 -1
- package/dist/Testing.d.mts +0 -479
- package/dist/Testing.mjs +0 -2147
- package/dist/Testing.mjs.map +0 -1
- package/dist/Timeline-fV22Xk-S.d.mts +0 -29
- package/dist/Timeline.d.mts +0 -4
- package/dist/Timeline.mjs +0 -20
- package/dist/Timeline.mjs.map +0 -1
- package/dist/TimelineData-BsOn-YGZ.d.mts +0 -3818
- package/dist/TimelineData.d.mts +0 -3
- package/dist/TimelineData.mjs +0 -482
- package/dist/TimelineData.mjs.map +0 -1
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { DialogShown, Disconnected, PageClosed, PageOpened, PageUntracked, RecordedEvent, SessionEnding } from "./BrowserEvent.mjs";
|
|
3
|
+
import { BrowserError, Busy, Closed, EventHistoryExpired, Failed, InvalidRequest, Limit, Suspended } from "./BrowserError.mjs";
|
|
4
|
+
import { i as failWith, s as undispatched, t as call } from "./context-B6xr8nVi.mjs";
|
|
5
|
+
import { c as FailFast, i as redact } from "./url-Cd04Okid.mjs";
|
|
6
|
+
import { n as make$5, o as mapping } from "./page-BAf__WKX.mjs";
|
|
7
|
+
import { Cause, Clock, Context, Crypto, DateTime, Deferred, Duration, Effect, Exit, Option, PlatformError, Queue, Schema, Scope, Semaphore, Stream } from "effect";
|
|
8
|
+
//#region src/internal/input/gate.ts
|
|
9
|
+
/**
|
|
10
|
+
* A browser's input authority: stop admitting writes, drain admitted operations through their
|
|
11
|
+
* records and native replies, then retain the stop for its scopes. Reads never enter this gate.
|
|
12
|
+
*/
|
|
13
|
+
const without = (set, id) => new Set([...set].filter((each) => each !== id));
|
|
14
|
+
const step = (state, input, stopped) => {
|
|
15
|
+
switch (input._tag) {
|
|
16
|
+
case "Enter": return state._tag === "Open" ? {
|
|
17
|
+
...state,
|
|
18
|
+
inputs: /* @__PURE__ */ new Set([...state.inputs, input.id])
|
|
19
|
+
} : state;
|
|
20
|
+
case "End": {
|
|
21
|
+
if (state._tag === "Held") return state;
|
|
22
|
+
const inputs = without(state.inputs, input.id);
|
|
23
|
+
return state._tag === "Draining" && inputs.size === 0 ? {
|
|
24
|
+
_tag: "Held",
|
|
25
|
+
claims: state.claims,
|
|
26
|
+
stopped
|
|
27
|
+
} : {
|
|
28
|
+
...state,
|
|
29
|
+
inputs
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
case "Suspend":
|
|
33
|
+
if (state._tag !== "Open") return {
|
|
34
|
+
...state,
|
|
35
|
+
claims: /* @__PURE__ */ new Set([...state.claims, input.id])
|
|
36
|
+
};
|
|
37
|
+
return state.inputs.size === 0 ? {
|
|
38
|
+
_tag: "Held",
|
|
39
|
+
claims: /* @__PURE__ */ new Set([input.id]),
|
|
40
|
+
stopped
|
|
41
|
+
} : {
|
|
42
|
+
_tag: "Draining",
|
|
43
|
+
inputs: state.inputs,
|
|
44
|
+
claims: /* @__PURE__ */ new Set([input.id]),
|
|
45
|
+
startedAt: stopped.at,
|
|
46
|
+
deadline: stopped.at + input.drain
|
|
47
|
+
};
|
|
48
|
+
case "Leave": {
|
|
49
|
+
if (state._tag === "Open" || !state.claims.has(input.id)) return state;
|
|
50
|
+
const claims = without(state.claims, input.id);
|
|
51
|
+
return claims.size > 0 ? {
|
|
52
|
+
...state,
|
|
53
|
+
claims
|
|
54
|
+
} : {
|
|
55
|
+
_tag: "Open",
|
|
56
|
+
inputs: state._tag === "Draining" ? state.inputs : /* @__PURE__ */ new Set()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
case "Expire": return state._tag === "Draining" && stopped.at >= state.deadline ? {
|
|
60
|
+
_tag: "Open",
|
|
61
|
+
inputs: state.inputs
|
|
62
|
+
} : state;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const make$4 = (options) => {
|
|
66
|
+
let state = {
|
|
67
|
+
_tag: "Open",
|
|
68
|
+
inputs: /* @__PURE__ */ new Set()
|
|
69
|
+
};
|
|
70
|
+
let next = 0;
|
|
71
|
+
let refused = Deferred.makeUnsafe();
|
|
72
|
+
let ready = Deferred.makeUnsafe();
|
|
73
|
+
const error = (operation) => new BrowserError({
|
|
74
|
+
operation,
|
|
75
|
+
reason: options.closedBy() === void 0 ? new Suspended() : new Closed({ cause: options.closedBy() ?? "connection" }),
|
|
76
|
+
dispatched: false
|
|
77
|
+
});
|
|
78
|
+
const stopped = () => ({
|
|
79
|
+
since: options.sequence(),
|
|
80
|
+
at: options.now()
|
|
81
|
+
});
|
|
82
|
+
const apply = (input) => {
|
|
83
|
+
if (input._tag !== "Expire" && state._tag === "Draining" && options.now() >= state.deadline) apply({ _tag: "Expire" });
|
|
84
|
+
const before = state;
|
|
85
|
+
state = step(state, input, stopped());
|
|
86
|
+
if (input._tag === "Expire" && before._tag === "Draining" && state._tag === "Open") Deferred.doneUnsafe(ready, Exit.fail(new BrowserError({
|
|
87
|
+
operation: "suspend",
|
|
88
|
+
reason: new Busy({
|
|
89
|
+
waitedMillis: Math.round(options.now() - before.startedAt),
|
|
90
|
+
ahead: before.inputs.size
|
|
91
|
+
}),
|
|
92
|
+
dispatched: false
|
|
93
|
+
})));
|
|
94
|
+
if (before._tag === "Open" && state._tag !== "Open") Deferred.doneUnsafe(refused, Exit.void);
|
|
95
|
+
if (before._tag === "Draining" && state._tag === "Held") Deferred.doneUnsafe(ready, Exit.succeed(state.stopped));
|
|
96
|
+
if (before._tag !== "Open" && state._tag === "Open") {
|
|
97
|
+
refused = Deferred.makeUnsafe();
|
|
98
|
+
ready = Deferred.makeUnsafe();
|
|
99
|
+
}
|
|
100
|
+
return state;
|
|
101
|
+
};
|
|
102
|
+
const check = (operation) => Effect.suspend(() => state._tag === "Open" && options.closedBy() === void 0 ? Effect.void : Effect.fail(error(operation)));
|
|
103
|
+
const suspend = (drain = options.drain) => Effect.suspend(() => {
|
|
104
|
+
const duration = Duration.fromInput(drain);
|
|
105
|
+
if (Option.isNone(duration) || !Duration.isFinite(duration.value) || Duration.toMillis(duration.value) < 0) return Effect.fail(new BrowserError({
|
|
106
|
+
operation: "suspend",
|
|
107
|
+
reason: new InvalidRequest({ detail: "drain must be a finite nonnegative duration" }),
|
|
108
|
+
dispatched: false
|
|
109
|
+
}));
|
|
110
|
+
const id = ++next;
|
|
111
|
+
const leave = Effect.sync(() => apply({
|
|
112
|
+
_tag: "Leave",
|
|
113
|
+
id
|
|
114
|
+
}));
|
|
115
|
+
return Effect.acquireRelease(Effect.gen(function* () {
|
|
116
|
+
if (options.closedBy() !== void 0) return yield* error("suspend");
|
|
117
|
+
const current = apply({
|
|
118
|
+
_tag: "Suspend",
|
|
119
|
+
id,
|
|
120
|
+
drain: Duration.toMillis(duration.value)
|
|
121
|
+
});
|
|
122
|
+
if (current._tag === "Held") return current.stopped;
|
|
123
|
+
if (current._tag !== "Draining") return yield* Effect.die("suspension did not retain authority");
|
|
124
|
+
const pending = ready;
|
|
125
|
+
const expire = Effect.suspend(() => {
|
|
126
|
+
const left = current.deadline - options.now();
|
|
127
|
+
return left > 0 ? Effect.sleep(Duration.millis(left)).pipe(Effect.andThen(expire)) : Effect.sync(() => apply({ _tag: "Expire" })).pipe(Effect.andThen(Deferred.await(pending)));
|
|
128
|
+
});
|
|
129
|
+
const wait = Effect.raceFirst(Deferred.await(pending), expire);
|
|
130
|
+
return yield* Effect.raceFirst(wait, options.lost.pipe(Effect.flatMap(() => Effect.fail(error("suspend"))))).pipe(Effect.interruptible, Effect.filterOrFail(() => options.closedBy() === void 0, () => error("suspend")), Effect.onError(() => leave));
|
|
131
|
+
}), () => leave);
|
|
132
|
+
}).pipe(Effect.provideService(Clock.Clock, options.clock));
|
|
133
|
+
return {
|
|
134
|
+
check,
|
|
135
|
+
suspend,
|
|
136
|
+
refused: (operation) => Effect.suspend(() => state._tag === "Open" ? Deferred.await(refused).pipe(Effect.andThen(Effect.fail(error(operation)))) : Effect.fail(error(operation))),
|
|
137
|
+
enter: (operation) => Effect.suspend(() => {
|
|
138
|
+
if (state._tag !== "Open" || options.closedBy() !== void 0) return Effect.fail(error(operation));
|
|
139
|
+
const id = ++next;
|
|
140
|
+
apply({
|
|
141
|
+
_tag: "Enter",
|
|
142
|
+
id
|
|
143
|
+
});
|
|
144
|
+
return Effect.succeed(() => {
|
|
145
|
+
apply({
|
|
146
|
+
_tag: "End",
|
|
147
|
+
id
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
})
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/internal/page/identity.ts
|
|
155
|
+
/** Connection identity uses the supplied Crypto service, or the host's Web Crypto on Node/Bun. */
|
|
156
|
+
const native = () => Crypto.make({
|
|
157
|
+
randomBytes: (size) => globalThis.crypto.getRandomValues(new Uint8Array(size)),
|
|
158
|
+
digest: (algorithm, data) => Effect.tryPromise({
|
|
159
|
+
try: () => globalThis.crypto.subtle.digest(algorithm, new Uint8Array(data)),
|
|
160
|
+
catch: (cause) => PlatformError.systemError({
|
|
161
|
+
module: "Crypto",
|
|
162
|
+
method: "digest",
|
|
163
|
+
_tag: "Unknown",
|
|
164
|
+
description: "Could not compute digest",
|
|
165
|
+
cause
|
|
166
|
+
})
|
|
167
|
+
}).pipe(Effect.map((bytes) => new Uint8Array(bytes)))
|
|
168
|
+
});
|
|
169
|
+
const make$3 = Effect.gen(function* () {
|
|
170
|
+
const supplied = yield* Effect.serviceOption(Crypto.Crypto);
|
|
171
|
+
return yield* Option.getOrElse(supplied, native).randomUUIDv4.pipe(Effect.mapError(() => undispatched("make", new Failed({ detail: "Could not name connection" }))));
|
|
172
|
+
});
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/internal/page/openings.ts
|
|
175
|
+
/** A page's input waits for the actual tabs it opened to finish the browser's registration. */
|
|
176
|
+
const make$2 = Effect.fnUntraced(function* (playwright, cdp, registrations, closedBy) {
|
|
177
|
+
const failed = (reason) => Exit.fail(new BrowserError({
|
|
178
|
+
operation: "newPage",
|
|
179
|
+
reason,
|
|
180
|
+
dispatched: false
|
|
181
|
+
}));
|
|
182
|
+
let active;
|
|
183
|
+
const gone = Deferred.makeUnsafe();
|
|
184
|
+
const windowOpened = () => {
|
|
185
|
+
if (active !== void 0) active.windows += 1;
|
|
186
|
+
};
|
|
187
|
+
const popup = (opened) => {
|
|
188
|
+
if (active === void 0) return;
|
|
189
|
+
const registered = registrations.get(opened);
|
|
190
|
+
active.registrations.push(registered === void 0 ? failed(new Failed({ detail: "the popup has no registration" })) : Deferred.await(registered));
|
|
191
|
+
Deferred.doneUnsafe(active.changed, Exit.void);
|
|
192
|
+
active.changed = Deferred.makeUnsafe();
|
|
193
|
+
};
|
|
194
|
+
const clear = Effect.sync(() => {
|
|
195
|
+
active = void 0;
|
|
196
|
+
});
|
|
197
|
+
yield* Effect.acquireRelease(Effect.sync(() => {
|
|
198
|
+
cdp.on("Page.windowOpen", windowOpened);
|
|
199
|
+
playwright.on("popup", popup);
|
|
200
|
+
}), () => Effect.sync(() => {
|
|
201
|
+
active = void 0;
|
|
202
|
+
cdp.off("Page.windowOpen", windowOpened);
|
|
203
|
+
playwright.off("popup", popup);
|
|
204
|
+
Deferred.doneUnsafe(gone, failed(new Closed({ cause: closedBy() })));
|
|
205
|
+
}));
|
|
206
|
+
return {
|
|
207
|
+
mark: () => {
|
|
208
|
+
const mark = {
|
|
209
|
+
windows: 0,
|
|
210
|
+
registrations: [],
|
|
211
|
+
changed: Deferred.makeUnsafe()
|
|
212
|
+
};
|
|
213
|
+
active = mark;
|
|
214
|
+
return mark;
|
|
215
|
+
},
|
|
216
|
+
clear,
|
|
217
|
+
settle: (mark) => Effect.gen(function* () {
|
|
218
|
+
while (mark.registrations.length < mark.windows) yield* Deferred.await(mark.changed);
|
|
219
|
+
yield* Effect.forEach(mark.registrations, (registration) => registration);
|
|
220
|
+
}).pipe(Effect.raceFirst(Deferred.await(gone)), Effect.ensuring(clear))
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/internal/timeline/events.ts
|
|
225
|
+
/**
|
|
226
|
+
* The browser's bounded event history and replay cursors. Readers share the same records and
|
|
227
|
+
* wakeup generation, so a delayed reader cannot block input or retain an unbounded private queue.
|
|
228
|
+
*/
|
|
229
|
+
/** The owning Browser validates capacity and closes this timeline with its scope. */
|
|
230
|
+
const make$1 = (capacity) => {
|
|
231
|
+
let records = [];
|
|
232
|
+
let sequence = 0;
|
|
233
|
+
let closed = false;
|
|
234
|
+
let changed = Deferred.makeUnsafe();
|
|
235
|
+
const wake = () => {
|
|
236
|
+
const previous = changed;
|
|
237
|
+
changed = Deferred.makeUnsafe();
|
|
238
|
+
Deferred.doneUnsafe(previous, Effect.void);
|
|
239
|
+
};
|
|
240
|
+
const publish = (event) => {
|
|
241
|
+
if (closed) return sequence;
|
|
242
|
+
sequence += 1;
|
|
243
|
+
const recorded = new RecordedEvent({
|
|
244
|
+
sequence,
|
|
245
|
+
event
|
|
246
|
+
});
|
|
247
|
+
records = records.length < capacity ? [...records, recorded] : [...records.slice(1), recorded];
|
|
248
|
+
wake();
|
|
249
|
+
return sequence;
|
|
250
|
+
};
|
|
251
|
+
const stream = (after) => Stream.unwrap(Effect.sync(() => {
|
|
252
|
+
let cursor = after ?? sequence;
|
|
253
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > sequence) return Stream.fail(undispatched("events", new InvalidRequest({ detail: "the event cursor must be a nonnegative integer no greater than the latest sequence" })));
|
|
254
|
+
return Stream.fromEffectRepeat(Effect.gen(function* () {
|
|
255
|
+
while (true) {
|
|
256
|
+
const oldest = records[0]?.sequence ?? sequence + 1;
|
|
257
|
+
if (cursor < oldest - 1) return yield* undispatched("events", new EventHistoryExpired({
|
|
258
|
+
after: cursor,
|
|
259
|
+
oldest,
|
|
260
|
+
latest: sequence
|
|
261
|
+
}));
|
|
262
|
+
const next = records[cursor - oldest + 1];
|
|
263
|
+
if (next !== void 0) {
|
|
264
|
+
cursor = next.sequence;
|
|
265
|
+
return next;
|
|
266
|
+
}
|
|
267
|
+
if (closed) return yield* Cause.done();
|
|
268
|
+
const waiting = changed;
|
|
269
|
+
yield* Deferred.await(waiting);
|
|
270
|
+
}
|
|
271
|
+
}));
|
|
272
|
+
}));
|
|
273
|
+
return {
|
|
274
|
+
publish,
|
|
275
|
+
sequence: () => sequence,
|
|
276
|
+
recent: Effect.sync(() => records),
|
|
277
|
+
stream,
|
|
278
|
+
close: Effect.sync(() => {
|
|
279
|
+
if (closed) return;
|
|
280
|
+
closed = true;
|
|
281
|
+
wake();
|
|
282
|
+
})
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/Browser.ts
|
|
287
|
+
/**
|
|
288
|
+
* A live browser: its pages and what happens in it.
|
|
289
|
+
*
|
|
290
|
+
* `Browser` is a service with one implementation, built by a provider over a Playwright browser
|
|
291
|
+
* context: `Chromium` launches one locally, `Cdp` connects to any DevTools endpoint and
|
|
292
|
+
* `effect-browserbase` allocates a hosted one. Code written against `Browser` runs on all three.
|
|
293
|
+
* A page is named by its CDP target id, so a new connection to the same browser finds it again.
|
|
294
|
+
*
|
|
295
|
+
* @since 0.3.0
|
|
296
|
+
*/
|
|
297
|
+
var Browser_exports = /* @__PURE__ */ __exportAll({
|
|
298
|
+
Browser: () => Browser,
|
|
299
|
+
Suspension: () => Suspension,
|
|
300
|
+
make: () => make
|
|
301
|
+
});
|
|
302
|
+
/** When admitted input finished; events after `since` describe the handoff interval. */
|
|
303
|
+
var Suspension = class extends Schema.Class("effect-browser/Suspension")({
|
|
304
|
+
since: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
305
|
+
at: Schema.Finite
|
|
306
|
+
}) {};
|
|
307
|
+
var Browser = class extends Context.Service()("effect-browser/Browser") {};
|
|
308
|
+
/** The settings every page shares, and how many events the browser keeps, from its options. */
|
|
309
|
+
const settingsOf = Effect.fnUntraced(function* (options) {
|
|
310
|
+
const invalid = (detail) => undispatched("make", new InvalidRequest({ detail }));
|
|
311
|
+
const bound = (name, input, fallback) => {
|
|
312
|
+
const decoded = Duration.fromInput(input ?? fallback);
|
|
313
|
+
return Option.isSome(decoded) && Number.isFinite(Duration.toMillis(decoded.value)) && Duration.isPositive(decoded.value) ? Effect.succeed(decoded.value) : Effect.fail(invalid(`${name} must be finite and greater than zero`));
|
|
314
|
+
};
|
|
315
|
+
const settings = {
|
|
316
|
+
actionTimeout: yield* bound("actionTimeout", options.actionTimeout, Duration.seconds(10)),
|
|
317
|
+
navigationTimeout: yield* bound("navigationTimeout", options.navigationTimeout, Duration.seconds(30)),
|
|
318
|
+
frameHistory: yield* bound("frameHistory", options.frameHistory, Duration.seconds(5)),
|
|
319
|
+
guard: options.guard,
|
|
320
|
+
policyTimeout: yield* bound("policyTimeout", options.policyTimeout, Duration.minutes(5))
|
|
321
|
+
};
|
|
322
|
+
const eventHistory = options.eventHistory ?? 4096;
|
|
323
|
+
const { maxPages } = options;
|
|
324
|
+
if (!Number.isSafeInteger(eventHistory) || eventHistory < 1) return yield* invalid("eventHistory must be a positive safe integer");
|
|
325
|
+
if (maxPages !== void 0 && (!Number.isSafeInteger(maxPages) || maxPages < 1)) return yield* invalid("maxPages must be a positive safe integer");
|
|
326
|
+
return {
|
|
327
|
+
settings,
|
|
328
|
+
eventHistory,
|
|
329
|
+
maxPages
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
/**
|
|
333
|
+
* A provider that ends a session at `expiresAt` by its own clock can cut the connection a little
|
|
334
|
+
* before this host's clock gets there.
|
|
335
|
+
*/
|
|
336
|
+
const endMargin = Duration.seconds(5);
|
|
337
|
+
/**
|
|
338
|
+
* The browser's loss, once: its context closing from the other side, at or after the session's
|
|
339
|
+
* end or before it, or `lose`, as the owner's scope releases it. Playwright leaves the calls in
|
|
340
|
+
* flight on the library's own sessions unanswered when the connection drops, so `untilLost` fails
|
|
341
|
+
* each with the browser, as a closed page's call fails.
|
|
342
|
+
*/
|
|
343
|
+
const watchLoss = Effect.fnUntraced(function* (context, expiresAt, clock, disconnected) {
|
|
344
|
+
let lostBy;
|
|
345
|
+
const lost = yield* Deferred.make();
|
|
346
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
347
|
+
const gone = () => /* @__PURE__ */ new Error("Target page, context or browser has been closed");
|
|
348
|
+
const lose = (cause) => {
|
|
349
|
+
if (lostBy !== void 0) return;
|
|
350
|
+
lostBy = cause;
|
|
351
|
+
for (const reject of inFlight) reject(gone());
|
|
352
|
+
disconnected(cause);
|
|
353
|
+
Deferred.doneUnsafe(lost, Exit.succeed(cause));
|
|
354
|
+
};
|
|
355
|
+
const untilLost = (reply) => {
|
|
356
|
+
if (lostBy !== void 0) {
|
|
357
|
+
reply.catch(() => void 0);
|
|
358
|
+
return Promise.reject(gone());
|
|
359
|
+
}
|
|
360
|
+
return new Promise((resolve, reject) => {
|
|
361
|
+
inFlight.add(reject);
|
|
362
|
+
reply.then(resolve, reject).finally(() => inFlight.delete(reject));
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
const ended = () => expiresAt !== void 0 && clock.currentTimeMillisUnsafe() >= DateTime.toEpochMillis(expiresAt) - Duration.toMillis(endMargin);
|
|
366
|
+
const onClose = () => lose(ended() ? "session" : "connection");
|
|
367
|
+
context.on("close", onClose);
|
|
368
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => context.off("close", onClose)));
|
|
369
|
+
if (context.browser()?.isConnected() === false) onClose();
|
|
370
|
+
const native = (operation, run) => call(operation, run, () => lostBy ?? "page");
|
|
371
|
+
return {
|
|
372
|
+
lostBy: () => lostBy,
|
|
373
|
+
lost: Deferred.await(lost),
|
|
374
|
+
lose,
|
|
375
|
+
untilLost,
|
|
376
|
+
native
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
/**
|
|
380
|
+
* What a page's own Playwright events publish, its close and crash aside, while its scope is
|
|
381
|
+
* open. Dialogs are answered: alerts accepted, others dismissed. The page's own session tells its
|
|
382
|
+
* documents' commits and loads.
|
|
383
|
+
*/
|
|
384
|
+
const listen = (playwright, page, publish, now, on) => {
|
|
385
|
+
const dialog = (shown) => {
|
|
386
|
+
const kind = shown.type();
|
|
387
|
+
const answer = kind === "alert" || kind === "beforeunload" ? "accepted" : "dismissed";
|
|
388
|
+
const message = shown.message().slice(0, 500);
|
|
389
|
+
publish(new DialogShown({
|
|
390
|
+
at: now(),
|
|
391
|
+
page,
|
|
392
|
+
kind,
|
|
393
|
+
message,
|
|
394
|
+
answer
|
|
395
|
+
}));
|
|
396
|
+
(answer === "accepted" ? shown.accept() : shown.dismiss()).catch(() => void 0);
|
|
397
|
+
};
|
|
398
|
+
return Effect.acquireRelease(Effect.sync(() => {
|
|
399
|
+
playwright.on("close", on.close);
|
|
400
|
+
playwright.on("crash", on.crash);
|
|
401
|
+
playwright.on("dialog", dialog);
|
|
402
|
+
}), () => Effect.sync(() => {
|
|
403
|
+
playwright.off("close", on.close);
|
|
404
|
+
playwright.off("crash", on.crash);
|
|
405
|
+
playwright.off("dialog", dialog);
|
|
406
|
+
}));
|
|
407
|
+
};
|
|
408
|
+
/**
|
|
409
|
+
* Register each page the site opens, such as a popup, as Playwright reports it, for as long as the
|
|
410
|
+
* scope is open. One that could not be tracked is published, unless it closed as it opened.
|
|
411
|
+
*/
|
|
412
|
+
const followOpened = Effect.fnUntraced(function* (context, register, publish, now, registrations) {
|
|
413
|
+
const opened = yield* Queue.unbounded();
|
|
414
|
+
const onPage = (playwright) => {
|
|
415
|
+
registrations.set(playwright, Deferred.makeUnsafe());
|
|
416
|
+
Queue.offerUnsafe(opened, playwright);
|
|
417
|
+
};
|
|
418
|
+
const untracked = (playwright) => (error) => Effect.sync(() => {
|
|
419
|
+
const url = redact(playwright.url());
|
|
420
|
+
if (!playwright.isClosed()) publish(new PageUntracked({
|
|
421
|
+
at: now(),
|
|
422
|
+
url,
|
|
423
|
+
detail: error.message
|
|
424
|
+
}));
|
|
425
|
+
});
|
|
426
|
+
context.on("page", onPage);
|
|
427
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => context.off("page", onPage)));
|
|
428
|
+
yield* Queue.take(opened).pipe(Effect.flatMap((playwright) => register(playwright).pipe(Effect.asVoid, Effect.onExit((exit) => Effect.sync(() => {
|
|
429
|
+
const done = registrations.get(playwright);
|
|
430
|
+
if (done !== void 0) Deferred.doneUnsafe(done, exit);
|
|
431
|
+
})), Effect.catch(untracked(playwright)))), Effect.forever, Effect.forkScoped);
|
|
432
|
+
});
|
|
433
|
+
/** Release something native, on the owner's clock, giving up after a second: cleanup only. */
|
|
434
|
+
const releaseNative = (clock) => (run) => Effect.tryPromise(run).pipe(Effect.interruptible, Effect.timeoutOrElse({
|
|
435
|
+
duration: Duration.seconds(1),
|
|
436
|
+
orElse: () => Effect.void
|
|
437
|
+
}), Effect.provideService(Clock.Clock, clock), Effect.ignore);
|
|
438
|
+
/** A page's own events among the browser's retained ones, and the openings of tabs it opened. */
|
|
439
|
+
const eventsOf = (recent, page) => Effect.map(recent, (records) => records.flatMap(({ event }) => "page" in event && event.page === page || event._tag === "PageOpened" && event.opener === page ? [event] : []));
|
|
440
|
+
/** A script as its documents run it: everywhere, or as a block where its `match` allows. */
|
|
441
|
+
const scoped = ({ match, source }) => match === void 0 ? source : `if (${String(match)}.test(location.href)) {\n${source}\n}`;
|
|
442
|
+
/**
|
|
443
|
+
* At most `maxPages` open pages, those a site opened included, which are never refused: an open
|
|
444
|
+
* past it waits for one to close, within `wait` on the owner's clock, then fails `Limit`, or fails
|
|
445
|
+
* at once under `FailFast`. An open counts until its page is tracked.
|
|
446
|
+
*/
|
|
447
|
+
const pageBudget = (maxPages, tracked, wait, clock) => {
|
|
448
|
+
let opening = 0;
|
|
449
|
+
let roomMade = Deferred.makeUnsafe();
|
|
450
|
+
const makeRoom = () => {
|
|
451
|
+
Deferred.doneUnsafe(roomMade, Exit.void);
|
|
452
|
+
roomMade = Deferred.makeUnsafe();
|
|
453
|
+
};
|
|
454
|
+
const full = (limit) => tracked() + opening >= limit;
|
|
455
|
+
const room = (limit) => Effect.gen(function* () {
|
|
456
|
+
const refused = undispatched("newPage", new Limit({ maxPages: limit }));
|
|
457
|
+
if ((yield* FailFast) && full(limit)) return yield* refused;
|
|
458
|
+
yield* Effect.gen(function* () {
|
|
459
|
+
while (full(limit)) yield* Deferred.await(roomMade);
|
|
460
|
+
opening += 1;
|
|
461
|
+
}).pipe(Effect.timeoutOrElse({
|
|
462
|
+
duration: wait,
|
|
463
|
+
orElse: () => Effect.fail(refused)
|
|
464
|
+
}), Effect.provideService(Clock.Clock, clock));
|
|
465
|
+
});
|
|
466
|
+
const open = (opened) => maxPages === void 0 ? opened : Effect.uninterruptibleMask((restore) => restore(room(maxPages)).pipe(Effect.andThen(restore(opened).pipe(Effect.ensuring(Effect.sync(() => {
|
|
467
|
+
opening -= 1;
|
|
468
|
+
makeRoom();
|
|
469
|
+
}))))));
|
|
470
|
+
return {
|
|
471
|
+
makeRoom,
|
|
472
|
+
open
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
/** A call sent now and awaited later, by whatever needs its answer. */
|
|
476
|
+
const sentNow = (sent) => {
|
|
477
|
+
sent.catch(() => void 0);
|
|
478
|
+
return sent;
|
|
479
|
+
};
|
|
480
|
+
/** Close tab resources after publishing their close; a browser loss speaks for all its tabs. */
|
|
481
|
+
const closures = (lostBy, publish, now) => Effect.gen(function* () {
|
|
482
|
+
const closing = yield* Queue.unbounded();
|
|
483
|
+
yield* Queue.take(closing).pipe(Effect.tap(({ page, cause }) => Effect.sync(() => {
|
|
484
|
+
if (lostBy() === void 0) publish(new PageClosed({
|
|
485
|
+
at: now(),
|
|
486
|
+
page,
|
|
487
|
+
cause
|
|
488
|
+
}));
|
|
489
|
+
})), Effect.flatMap(({ scope }) => Scope.close(scope, Exit.void)), Effect.forever, Effect.forkScoped);
|
|
490
|
+
return closing;
|
|
491
|
+
});
|
|
492
|
+
/**
|
|
493
|
+
* Build the service over a context the provider owns. The provider closes the context; this only
|
|
494
|
+
* tracks its pages and events, for as long as the surrounding scope is open. Each page's protocol
|
|
495
|
+
* session, capture and listeners end when that page closes or when the scope does, so a closed
|
|
496
|
+
* tab retains nothing and the context keeps none of them afterwards.
|
|
497
|
+
*/
|
|
498
|
+
const make = Effect.fn("Browser.make")(function* (context, info, options = {}) {
|
|
499
|
+
const clock = yield* Clock.Clock;
|
|
500
|
+
const connection = yield* make$3;
|
|
501
|
+
const now = () => Number(clock.monotonicTimeNanosUnsafe()) / 1e6;
|
|
502
|
+
const { settings, eventHistory, maxPages } = yield* settingsOf(options);
|
|
503
|
+
const mapping$1 = mapping(now);
|
|
504
|
+
const timeline = make$1(eventHistory);
|
|
505
|
+
yield* Effect.addFinalizer(() => timeline.close);
|
|
506
|
+
const publish = timeline.publish;
|
|
507
|
+
if (info.expiresAt !== void 0) publish(new SessionEnding({
|
|
508
|
+
at: now(),
|
|
509
|
+
expiresAt: info.expiresAt
|
|
510
|
+
}));
|
|
511
|
+
const { lostBy, lost, lose, untilLost, native } = yield* watchLoss(context, info.expiresAt, clock, (cause) => publish(new Disconnected({
|
|
512
|
+
at: now(),
|
|
513
|
+
cause
|
|
514
|
+
})));
|
|
515
|
+
const inputGate = make$4({
|
|
516
|
+
now,
|
|
517
|
+
sequence: timeline.sequence,
|
|
518
|
+
closedBy: lostBy,
|
|
519
|
+
lost,
|
|
520
|
+
clock,
|
|
521
|
+
drain: settings.navigationTimeout
|
|
522
|
+
});
|
|
523
|
+
const registry = /* @__PURE__ */ new Map();
|
|
524
|
+
const registrations = /* @__PURE__ */ new WeakMap();
|
|
525
|
+
const registering = yield* Semaphore.make(1);
|
|
526
|
+
const pagesScope = yield* Scope.fork(yield* Scope.Scope);
|
|
527
|
+
const release = releaseNative(clock);
|
|
528
|
+
const budget = pageBudget(maxPages, () => registry.size, settings.actionTimeout, clock);
|
|
529
|
+
const closing = yield* closures(lostBy, publish, now);
|
|
530
|
+
const register = (playwright) => registering.withPermits(1)(Effect.gen(function* () {
|
|
531
|
+
const known = registry.get(playwright);
|
|
532
|
+
if (known !== void 0) return known;
|
|
533
|
+
if (playwright.isClosed()) return yield* failWith("newPage", new Failed({ detail: "the page closed as it opened" }));
|
|
534
|
+
const scope = yield* Scope.fork(pagesScope);
|
|
535
|
+
return yield* Effect.gen(function* () {
|
|
536
|
+
let crashed = false;
|
|
537
|
+
const closedBy = () => lostBy() ?? (crashed ? "crashed" : "page");
|
|
538
|
+
const cdp = yield* native("newPage", () => context.newCDPSession(playwright));
|
|
539
|
+
yield* Effect.addFinalizer(() => release(() => cdp.detach()));
|
|
540
|
+
const send = (method, params) => untilLost(cdp.send(method, params));
|
|
541
|
+
const focusing = sentNow(send("Emulation.setFocusEmulationEnabled", { enabled: true }));
|
|
542
|
+
const paging = sentNow(send("Page.enable"));
|
|
543
|
+
const { targetInfo } = yield* native("newPage", () => send("Target.getTargetInfo"));
|
|
544
|
+
const id = targetInfo.targetId;
|
|
545
|
+
const page = yield* make$5({
|
|
546
|
+
id,
|
|
547
|
+
session: info.id,
|
|
548
|
+
connection,
|
|
549
|
+
url: targetInfo.url,
|
|
550
|
+
playwright,
|
|
551
|
+
cdp,
|
|
552
|
+
untilLost,
|
|
553
|
+
settings,
|
|
554
|
+
inputGate,
|
|
555
|
+
clock,
|
|
556
|
+
mapping: mapping$1,
|
|
557
|
+
publish,
|
|
558
|
+
recentEvents: eventsOf(timeline.recent, id),
|
|
559
|
+
openings: yield* make$2(playwright, cdp, registrations, closedBy),
|
|
560
|
+
focused: call("focus", () => focusing, closedBy).pipe(Effect.asVoid),
|
|
561
|
+
paging: (operation) => call(operation, () => paging, closedBy).pipe(Effect.asVoid),
|
|
562
|
+
closedBy,
|
|
563
|
+
capture: info.capture
|
|
564
|
+
});
|
|
565
|
+
const close = () => {
|
|
566
|
+
if (registry.get(playwright) !== page) return;
|
|
567
|
+
registry.delete(playwright);
|
|
568
|
+
budget.makeRoom();
|
|
569
|
+
Queue.offerUnsafe(closing, {
|
|
570
|
+
scope,
|
|
571
|
+
page: id,
|
|
572
|
+
cause: crashed ? "crashed" : "page"
|
|
573
|
+
});
|
|
574
|
+
};
|
|
575
|
+
const crash = () => {
|
|
576
|
+
crashed = true;
|
|
577
|
+
playwright.close().catch(() => void 0);
|
|
578
|
+
};
|
|
579
|
+
registry.set(playwright, page);
|
|
580
|
+
publish(new PageOpened({
|
|
581
|
+
at: now(),
|
|
582
|
+
page: id,
|
|
583
|
+
url: redact(targetInfo.url),
|
|
584
|
+
opener: targetInfo.openerId
|
|
585
|
+
}));
|
|
586
|
+
yield* listen(playwright, id, publish, now, {
|
|
587
|
+
close,
|
|
588
|
+
crash
|
|
589
|
+
});
|
|
590
|
+
if (playwright.isClosed()) close();
|
|
591
|
+
return page;
|
|
592
|
+
}).pipe(Scope.provide(scope), Effect.onError(() => Scope.close(scope, Exit.void)));
|
|
593
|
+
}));
|
|
594
|
+
for (const script of options.initScripts ?? []) yield* native("initScripts", () => context.addInitScript({ content: scoped(script) }));
|
|
595
|
+
yield* followOpened(context, register, publish, now, registrations);
|
|
596
|
+
for (const existing of context.pages()) yield* register(existing);
|
|
597
|
+
const pages = Effect.sync(() => [...registry.values()]);
|
|
598
|
+
const newPage = Effect.fn("Browser.newPage")(function* (url) {
|
|
599
|
+
const page = yield* budget.open(native("newPage", () => context.newPage()).pipe(Effect.flatMap((playwright) => register(playwright).pipe(Effect.onError(() => release(() => playwright.close()))))));
|
|
600
|
+
if (url !== void 0) yield* page.goto(url).pipe(Effect.onError(() => page.close.pipe(Effect.ignore)));
|
|
601
|
+
return page;
|
|
602
|
+
});
|
|
603
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => lose("released")));
|
|
604
|
+
const service = {
|
|
605
|
+
id: info.id,
|
|
606
|
+
connection,
|
|
607
|
+
provider: info.provider,
|
|
608
|
+
expiresAt: info.expiresAt,
|
|
609
|
+
now: Effect.sync(now),
|
|
610
|
+
suspend: (options = {}) => Effect.map(inputGate.suspend(options.drain), (held) => new Suspension(held)),
|
|
611
|
+
context,
|
|
612
|
+
pages,
|
|
613
|
+
page: (id) => Effect.map(pages, (open) => Option.fromNullishOr(open.find((page) => page.id === id))),
|
|
614
|
+
firstPage: pages.pipe(Effect.map((open) => Option.fromNullishOr(open[0])), Effect.flatMap(Option.match({
|
|
615
|
+
onNone: () => newPage(),
|
|
616
|
+
onSome: Effect.succeed
|
|
617
|
+
}))),
|
|
618
|
+
newPage,
|
|
619
|
+
disconnected: lost,
|
|
620
|
+
events: (eventOptions = {}) => timeline.stream(eventOptions.after),
|
|
621
|
+
recentEvents: timeline.recent.pipe(Effect.map((records) => records.map((record) => record.event)))
|
|
622
|
+
};
|
|
623
|
+
return Browser.of(service);
|
|
624
|
+
});
|
|
625
|
+
//#endregion
|
|
626
|
+
export { make as i, Browser_exports as n, Suspension as r, Browser as t };
|
|
627
|
+
|
|
628
|
+
//# sourceMappingURL=Browser-B4qncxe7.mjs.map
|