@effect-agent/platform-cloudflare 0.1.0-beta.121 → 0.1.0-beta.122

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.
@@ -49,9 +49,41 @@ const reportBrowserCause = (operation, cause) => ErrorReporter.report(Cause.from
49
49
  //#endregion
50
50
  //#region src/internal/browser-binding.ts
51
51
  const Acquired = Schema.Struct({ sessionId: Schema.String.check(Schema.isUUID()) });
52
+ const VersionReply = Schema.fromJsonString(Schema.Struct({
53
+ id: Schema.optionalKey(Schema.Int),
54
+ result: Schema.optionalKey(Schema.Unknown),
55
+ error: Schema.optionalKey(Schema.Unknown)
56
+ }));
57
+ const Version = Schema.Struct({
58
+ product: Schema.NonEmptyString,
59
+ protocolVersion: Schema.NonEmptyString
60
+ });
52
61
  /** Native transport port shared by both scoped adapters; the Layer owns the foreign binding. */
53
62
  var BrowserRunBinding = class extends Context.Service()("@effect-agent/platform-cloudflare/internal/BrowserRunBinding") {
54
63
  static layer(browser) {
64
+ const upgrade = async (sessionId, operation, signal) => {
65
+ const response = await browser.fetch(`https://browser-rendering.cloudflare.com/v1/devtools/browser/${sessionId}`, {
66
+ headers: { Upgrade: "websocket" },
67
+ ...signal === void 0 ? {} : { signal }
68
+ });
69
+ if (response.status !== 101) {
70
+ const failure = new BrowserRunFailure({
71
+ operation,
72
+ reason: "provider",
73
+ status: response.status
74
+ });
75
+ try {
76
+ await response.body?.cancel();
77
+ } catch {}
78
+ throw failure;
79
+ }
80
+ const socket = response.webSocket;
81
+ if (socket === null) throw new BrowserRunFailure({
82
+ operation,
83
+ reason: "malformed"
84
+ });
85
+ return socket;
86
+ };
55
87
  return Layer.succeed(this)({
56
88
  acquire: async (keepAliveMillis, operation) => {
57
89
  const response = await browser.fetch(`https://browser-rendering.cloudflare.com/v1/devtools/browser?keep_alive=${keepAliveMillis}&recording=false`, { method: "POST" });
@@ -68,62 +100,171 @@ var BrowserRunBinding = class extends Context.Service()("@effect-agent/platform-
68
100
  }
69
101
  return Schema.decodeUnknownSync(Acquired)(await response.json()).sessionId;
70
102
  },
71
- connect: async (sessionId, operation, signal) => {
103
+ connect: (sessionId, operation, signal) => {
72
104
  let socket;
105
+ let retired = false;
73
106
  let closedByAbort = false;
74
- try {
75
- const response = await browser.fetch(`https://browser-rendering.cloudflare.com/v1/devtools/browser/${sessionId}`, {
76
- headers: { Upgrade: "websocket" },
77
- ...signal === void 0 ? {} : { signal }
78
- });
79
- if (response.status !== 101) {
80
- const failure = new BrowserRunFailure({
107
+ let retirementFailure;
108
+ const disconnectOperation = operation === "protected.connect" ? "protected.disconnect" : "interactive.disconnect";
109
+ const transport = {
110
+ send: (message) => {
111
+ if (retired || socket === void 0) throw new BrowserRunFailure({
81
112
  operation,
82
- reason: "provider",
83
- status: response.status
113
+ reason: "provider"
84
114
  });
85
- try {
86
- await response.body?.cancel();
87
- } catch {}
88
- throw failure;
115
+ socket.send(message);
116
+ },
117
+ close: () => retireNow()
118
+ };
119
+ const onMessage = (event) => {
120
+ if (!retired) transport.onmessage?.(event.data);
121
+ };
122
+ const fence = () => {
123
+ if (retired) return;
124
+ retired = true;
125
+ const notify = transport.onclose;
126
+ transport.onmessage = void 0;
127
+ transport.onclose = void 0;
128
+ try {
129
+ notify?.();
130
+ } finally {
131
+ socket?.removeEventListener("message", onMessage);
89
132
  }
90
- const connected = response.webSocket;
91
- if (connected === null) throw new BrowserRunFailure({
92
- operation,
93
- reason: "malformed"
94
- });
95
- socket = connected;
96
- const transport = {
97
- send: (message) => connected.send(message),
98
- close: () => connected.close()
133
+ };
134
+ const retireNow = () => {
135
+ try {
136
+ fence();
137
+ } catch (cause) {
138
+ retirementFailure ??= browserFailure(disconnectOperation, cause);
139
+ }
140
+ try {
141
+ if (socket !== void 0 && socket.readyState < WebSocket.CLOSING) socket.close();
142
+ } catch (cause) {
143
+ retirementFailure ??= browserFailure(disconnectOperation, cause);
144
+ }
145
+ if (retirementFailure !== void 0) throw retirementFailure;
146
+ };
147
+ const abort = () => {
148
+ closedByAbort = socket?.readyState === WebSocket.OPEN;
149
+ try {
150
+ retireNow();
151
+ } catch {}
152
+ };
153
+ signal?.addEventListener("abort", abort, { once: true });
154
+ return {
155
+ browser: (async () => {
156
+ try {
157
+ signal?.throwIfAborted();
158
+ socket = await upgrade(sessionId, operation, signal);
159
+ socket.accept();
160
+ if (retired || signal?.aborted) {
161
+ retireNow();
162
+ signal?.throwIfAborted();
163
+ throw new BrowserRunFailure({
164
+ operation,
165
+ reason: "provider"
166
+ });
167
+ }
168
+ socket.addEventListener("message", onMessage);
169
+ socket.addEventListener("close", () => {
170
+ try {
171
+ fence();
172
+ } catch (cause) {
173
+ retirementFailure ??= browserFailure(disconnectOperation, cause);
174
+ }
175
+ }, { once: true });
176
+ return await puppeteer.connect({ transport });
177
+ } catch (cause) {
178
+ try {
179
+ retireNow();
180
+ } catch {}
181
+ const failure = browserFailure(operation, cause);
182
+ throw cause instanceof Error && signal?.aborted === true && (cause === signal.reason || closedByAbort && cause.name === "TargetCloseError") ? reportedBrowserError(failure) : failure;
183
+ } finally {
184
+ signal?.removeEventListener("abort", abort);
185
+ }
186
+ })(),
187
+ retire: Effect.callback((resume) => {
188
+ const connected = socket;
189
+ const onClose = () => resume(Effect.void);
190
+ try {
191
+ retireNow();
192
+ if (connected === void 0 || connected.readyState === WebSocket.CLOSED) resume(Effect.void);
193
+ else connected.addEventListener("close", onClose, { once: true });
194
+ } catch (cause) {
195
+ resume(Effect.fail(browserFailure(disconnectOperation, cause)));
196
+ }
197
+ return Effect.sync(() => connected?.removeEventListener("close", onClose));
198
+ })
199
+ };
200
+ },
201
+ keepAlive: Effect.fnUntraced(function* (sessionId) {
202
+ const operation = "protected.keepAlive";
203
+ const runReport = Effect.runPromiseWith(yield* Effect.context());
204
+ const release = (socket) => Effect.sync(() => socket.close()).pipe(Effect.catchCause((cause) => reportBrowserCause(operation, cause)));
205
+ const socket = yield* Effect.acquireRelease(Effect.tryPromise({
206
+ try: async (signal) => {
207
+ let acquired;
208
+ try {
209
+ acquired = await upgrade(sessionId, operation, signal);
210
+ acquired.accept();
211
+ signal.throwIfAborted();
212
+ return acquired;
213
+ } catch (cause) {
214
+ if (acquired !== void 0) await runReport(release(acquired));
215
+ if (signal.aborted && cause !== signal.reason) await runReport(reportBrowserCause(operation, Cause.fail(cause)));
216
+ throw cause;
217
+ }
218
+ },
219
+ catch: (cause) => browserFailure(operation, cause)
220
+ }), release, { interruptible: true });
221
+ yield* Effect.callback((resume) => {
222
+ const finish = (result) => {
223
+ removeListeners();
224
+ resume(result);
99
225
  };
100
- connected.addEventListener("message", (event) => {
101
- transport.onmessage?.(event.data);
102
- });
103
- connected.addEventListener("close", () => transport.onclose?.());
104
- connected.accept();
105
- const abort = () => {
226
+ const fail = (reason) => finish(Effect.fail(new BrowserRunFailure({
227
+ operation,
228
+ reason
229
+ })));
230
+ const onMessage = (event) => {
106
231
  try {
107
- const wasOpen = connected.readyState === WebSocket.OPEN;
108
- connected.close();
109
- closedByAbort = wasOpen;
110
- } catch {}
232
+ if (typeof event.data !== "string" || event.data.length > 16384) {
233
+ fail("malformed");
234
+ return;
235
+ }
236
+ const reply = Schema.decodeSync(VersionReply)(event.data);
237
+ if (reply.id !== 1) return;
238
+ if (reply.error !== void 0) {
239
+ fail("provider");
240
+ return;
241
+ }
242
+ Schema.decodeUnknownSync(Version)(reply.result);
243
+ finish(Effect.void);
244
+ } catch {
245
+ fail("malformed");
246
+ }
111
247
  };
112
- signal?.addEventListener("abort", abort, { once: true });
248
+ const onClose = () => fail("provider");
249
+ const removeListeners = () => {
250
+ socket.removeEventListener("message", onMessage);
251
+ socket.removeEventListener("close", onClose);
252
+ socket.removeEventListener("error", onClose);
253
+ };
254
+ socket.addEventListener("message", onMessage);
255
+ socket.addEventListener("close", onClose);
256
+ socket.addEventListener("error", onClose);
113
257
  try {
114
- signal?.throwIfAborted();
115
- return await puppeteer.connect({ transport });
116
- } finally {
117
- signal?.removeEventListener("abort", abort);
258
+ socket.send(JSON.stringify({
259
+ id: 1,
260
+ method: "Browser.getVersion"
261
+ }));
262
+ } catch (cause) {
263
+ finish(Effect.fail(browserFailure(operation, cause)));
118
264
  }
119
- } catch (cause) {
120
- try {
121
- socket?.close();
122
- } catch {}
123
- const failure = browserFailure(operation, cause);
124
- throw cause instanceof Error && signal?.aborted === true && (cause === signal.reason || closedByAbort && cause.name === "TargetCloseError") ? reportedBrowserError(failure) : failure;
125
- }
126
- }
265
+ return Effect.sync(removeListeners);
266
+ });
267
+ }, Effect.scoped)
127
268
  });
128
269
  }
129
270
  };
@@ -443,7 +584,7 @@ var BrowserRunInteractiveBinding = class BrowserRunInteractiveBinding extends Co
443
584
  const viewport = options.viewport === void 0 ? void 0 : yield* decodeViewport(options.viewport);
444
585
  return {
445
586
  acquire: async (keepAliveMillis) => binding.acquire(keepAliveMillis, "interactive.acquire"),
446
- connect: async (sessionId, signal) => makeProductionBrowser(await binding.connect(sessionId, "interactive.connect", signal), sessionId, viewport),
587
+ connect: async (sessionId, signal) => makeProductionBrowser(await binding.connect(sessionId, "interactive.connect", signal).browser, sessionId, viewport),
447
588
  closeSession: (sessionId) => lifecycle.close(sessionId).pipe(Effect.mapError((cause) => actionError("close", cause)))
448
589
  };
449
590
  })).pipe(Layer.provide(BrowserRunBinding.layer(options.browser)));
@@ -1777,4 +1918,4 @@ const CloudflareInteractiveBrowser = {
1777
1918
  //#endregion
1778
1919
  export { reportBrowserCause as C, inheritBrowserReport as S, BrowserRunCleanupError as _, BrowserRunInteractiveCheckpoint as a, BrowserRunFailure as b, BrowserRunLiveViewResult as c, BrowserRunViewport as d, CloudflareInteractiveBrowser as f, isBrowserRunUndispatchedActionError as g, browserRunInteractiveLayer as h, BrowserRunInteractiveBinding as i, BrowserRunPageIdentity as l, browserRunInteractiveImplementation as m, BrowserRunHandoffResult as n, BrowserRunInteractiveHost as o, browserRunInteractiveHostLayer as p, BrowserRunHandoffState as r, BrowserRunLiveViewRequest as s, BrowserRunHandoffRequest as t, BrowserRunPageObservation as u, BrowserRunSessionLifecycle as v, reportedBrowserError as w, browserFailure as x, BrowserRunBinding as y };
1779
1920
 
1780
- //# sourceMappingURL=InteractiveBrowser-DhOkdUx9.mjs.map
1921
+ //# sourceMappingURL=InteractiveBrowser-DXwmvWdA.mjs.map