effect-browser 0.2.0-beta.7 → 0.2.0-beta.8
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 +486 -152
- package/dist/Browser-n-bKgBNN.d.mts +372 -0
- package/dist/Browser.d.mts +3 -200
- package/dist/Browser.mjs +9 -1
- package/dist/Browser.mjs.map +1 -1
- package/dist/{BrowserData-KHk4o__c.d.mts → BrowserData-CPIIIHol.d.mts} +81 -7
- package/dist/BrowserData.d.mts +2 -2
- package/dist/BrowserData.mjs +104 -6
- package/dist/BrowserData.mjs.map +1 -1
- package/dist/{BrowserRuntime-hxkTNlRa.d.mts → BrowserRuntime-CFe9uUTv.d.mts} +16 -11
- package/dist/{BrowserRuntime-Cr76CN4I.mjs → BrowserRuntime-HJ1yYlFS.mjs} +4990 -4309
- package/dist/BrowserRuntime-HJ1yYlFS.mjs.map +1 -0
- package/dist/BrowserRuntime.d.mts +2 -2
- package/dist/BrowserRuntime.mjs +2 -1
- package/dist/Capture.d.mts +9 -7
- package/dist/Capture.mjs +157 -32
- package/dist/Capture.mjs.map +1 -1
- package/dist/{CaptureData-Dys0Ko9d.mjs → CaptureData-CYriZHX1.mjs} +25 -6
- package/dist/CaptureData-CYriZHX1.mjs.map +1 -0
- package/dist/CaptureData.d.mts +62 -3
- package/dist/CaptureData.mjs +2 -2
- package/dist/Chromium.d.mts +2 -2
- package/dist/Chromium.mjs +19 -10
- package/dist/Chromium.mjs.map +1 -1
- package/dist/Errors.d.mts +66 -17
- package/dist/Errors.mjs +47 -5
- package/dist/Errors.mjs.map +1 -1
- package/dist/Images-CaaPNeed.mjs +76 -0
- package/dist/Images-CaaPNeed.mjs.map +1 -0
- package/dist/OperationOptions-DAeRS6O9.mjs +55 -0
- package/dist/OperationOptions-DAeRS6O9.mjs.map +1 -0
- package/dist/PageControl.d.mts +6 -6
- package/dist/PageControl.mjs +16 -10
- package/dist/PageControl.mjs.map +1 -1
- package/dist/PageRegistry-B3q9DS3g.mjs +164 -0
- package/dist/PageRegistry-B3q9DS3g.mjs.map +1 -0
- package/dist/Plan-D8qQ48Ic.mjs +512 -0
- package/dist/Plan-D8qQ48Ic.mjs.map +1 -0
- package/dist/Plan.d.mts +3 -0
- package/dist/Plan.mjs +2 -0
- package/dist/PlanData-B668RxbQ.d.mts +4831 -0
- package/dist/PlanData.d.mts +3 -0
- package/dist/PlanData.mjs +268 -0
- package/dist/PlanData.mjs.map +1 -0
- package/dist/PlanInput-BJcPPncs.mjs +75 -0
- package/dist/PlanInput-BJcPPncs.mjs.map +1 -0
- package/dist/PublicSession-Bs61Eal8.mjs +5601 -0
- package/dist/PublicSession-Bs61Eal8.mjs.map +1 -0
- package/dist/Testing.d.mts +48 -6
- package/dist/Testing.mjs +651 -113
- package/dist/Testing.mjs.map +1 -1
- package/dist/Timeline-fV22Xk-S.d.mts +29 -0
- package/dist/Timeline.d.mts +4 -0
- package/dist/Timeline.mjs +20 -0
- package/dist/Timeline.mjs.map +1 -0
- package/dist/TimelineData-BsOn-YGZ.d.mts +3818 -0
- package/dist/TimelineData.d.mts +3 -0
- package/dist/TimelineData.mjs +482 -0
- package/dist/TimelineData.mjs.map +1 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +5 -1
- package/package.json +17 -1
- package/dist/Association-CH3kPHXE.mjs +0 -167
- package/dist/Association-CH3kPHXE.mjs.map +0 -1
- package/dist/BrowserRuntime-Cr76CN4I.mjs.map +0 -1
- package/dist/CaptureData-Dys0Ko9d.mjs.map +0 -1
- package/dist/PageControlAssociation-DabEg5AR.mjs +0 -10
- package/dist/PageControlAssociation-DabEg5AR.mjs.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
export { AcquireOptions, Acquisition, BrowserBinding, type CleanupStep, Connection, type ConnectionCleanup, type ConnectionState, DownloadEvent, FileRequest, Integration, Lifetime, PlaywrightOptions, ResolvedFileSelection, Runtime, RuntimeOptions, Source, cleanupStep, make, noConnection, playwright, reported };
|
|
1
|
+
import { S as reported, _ as CleanupStep, a as DownloadEvent, b as cleanupStep, c as Lifetime, d as Runtime, f as RuntimeOptions, g as playwright, h as make, i as Connection, l as PlaywrightOptions, m as TransferOperations, n as Acquisition, o as FileRequest, p as Source, r as BrowserBinding, s as Integration, t as AcquireOptions, u as ResolvedFileSelection, v as ConnectionCleanup, x as noConnection, y as ConnectionState } from "./BrowserRuntime-CFe9uUTv.mjs";
|
|
2
|
+
export { AcquireOptions, Acquisition, BrowserBinding, type CleanupStep, Connection, type ConnectionCleanup, type ConnectionState, DownloadEvent, FileRequest, Integration, Lifetime, PlaywrightOptions, ResolvedFileSelection, Runtime, RuntimeOptions, Source, TransferOperations, cleanupStep, make, noConnection, playwright, reported };
|
|
3
3
|
export {};
|
package/dist/BrowserRuntime.mjs
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as make, r as playwright, t as DownloadEvent } from "./BrowserRuntime-HJ1yYlFS.mjs";
|
|
2
|
+
import { c as noConnection, l as reported, s as cleanupStep } from "./PublicSession-Bs61Eal8.mjs";
|
|
2
3
|
export { DownloadEvent, cleanupStep, make, noConnection, playwright, reported };
|
package/dist/Capture.d.mts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { BrowserError } from "./Errors.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { CaptureOptions, CaptureSize, CaptureSnapshot, CaptureSummary, CapturedFrame } from "./CaptureData.mjs";
|
|
2
|
+
import { u as Page } from "./Browser-n-bKgBNN.mjs";
|
|
3
|
+
import { CaptureOptions, CaptureQualification, CaptureSize, CaptureSnapshot, CaptureSummary, CapturedFrame } from "./CaptureData.mjs";
|
|
4
4
|
import { Crypto, Effect, PlatformError, Scope, Stream } from "effect";
|
|
5
5
|
//#region src/Capture.d.ts
|
|
6
6
|
/** Live stream/Effect capabilities intentionally have no data schema or serialization contract. */
|
|
7
7
|
export interface CaptureInterval {
|
|
8
|
+
/** Opaque original interval identity; metadata refers to this id and actual frame sequences. */
|
|
9
|
+
readonly id: string;
|
|
8
10
|
/** Single subscription. Ending or interrupting it stops this interval, not its browser. */
|
|
9
11
|
readonly frames: Stream.Stream<CapturedFrame, BrowserError>;
|
|
10
12
|
/**
|
|
@@ -18,14 +20,14 @@ export interface CaptureInterval {
|
|
|
18
20
|
/**
|
|
19
21
|
* Capture one remote page independently of the session's selected page.
|
|
20
22
|
*
|
|
21
|
-
* Requires
|
|
22
|
-
*
|
|
23
|
+
* Requires an exact issued Page; copying an object or decoding a durable reference cannot
|
|
24
|
+
* copy its capture authority. The private owner is not frame data.
|
|
23
25
|
* `CapturedFrame` and `CaptureOptions` are Schema values as well as structural types. Frame
|
|
24
26
|
* decoding checks binary/metadata fields, not the complete JPEG bitstream or target authority,
|
|
25
27
|
* and does not copy bytes. Options decoding preserves omissions; admission applies defaults.
|
|
26
28
|
* `CaptureInterval` remains a live scoped capability, not a schema or JSON/Tool value.
|
|
27
29
|
*/
|
|
28
|
-
export declare const start:
|
|
30
|
+
export declare const start: (page: Page, options?: CaptureOptions) => Effect.Effect<CaptureInterval, BrowserError, Scope.Scope>;
|
|
29
31
|
/**
|
|
30
32
|
* A lazy, scoped frame stream. Each subscription acquires one interval and finalizes it when
|
|
31
33
|
* consumption ends, fails or is interrupted; the browser remains owned by its caller.
|
|
@@ -33,7 +35,7 @@ export declare const start: <E>(session: BrowserSession<E>, options?: CaptureOpt
|
|
|
33
35
|
* Unconfirmed native cleanup retains the page reservation. Use start when the host needs
|
|
34
36
|
* explicit stop acknowledgement, interval snapshots and the final capture summary.
|
|
35
37
|
*/
|
|
36
|
-
export declare const stream:
|
|
38
|
+
export declare const stream: (page: Page, options?: CaptureOptions) => Stream.Stream<CapturedFrame, BrowserError>;
|
|
37
39
|
/** A `multipart/x-mixed-replace` body and the content type that names its boundary. */
|
|
38
40
|
export interface MultipartBody<E, R> {
|
|
39
41
|
readonly contentType: string;
|
|
@@ -49,6 +51,6 @@ export interface MultipartBody<E, R> {
|
|
|
49
51
|
*/
|
|
50
52
|
export declare const multipart: <E, R>(frames: Stream.Stream<CapturedFrame, E, R>) => Effect.Effect<MultipartBody<E, R>, PlatformError.PlatformError, Crypto.Crypto>;
|
|
51
53
|
//#endregion
|
|
52
|
-
export { CaptureOptions, CaptureSize, CaptureSnapshot, CaptureSummary, CapturedFrame };
|
|
54
|
+
export { CaptureOptions, CaptureQualification, CaptureSize, CaptureSnapshot, CaptureSummary, CapturedFrame };
|
|
53
55
|
export {};
|
|
54
56
|
//# sourceMappingURL=Capture.d.mts.map
|
package/dist/Capture.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { BrowserError, Reasons } from "./Errors.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { t as capturePageParent } from "./PageRegistry-B3q9DS3g.mjs";
|
|
3
|
+
import { A as stream$1, O as span, k as start$1, n as checkedOperationOptions, t as checked } from "./PublicSession-Bs61Eal8.mjs";
|
|
4
|
+
import { t as jpegGeometry } from "./Images-CaaPNeed.mjs";
|
|
5
|
+
import { a as CaptureSummary, c as CaptureLimits, i as CaptureSnapshot, l as CaptureMaxima, n as CaptureQualification, o as CapturedFrame, r as CaptureSize, s as CaptureDefaults, t as CaptureOptions } from "./CaptureData-CYriZHX1.mjs";
|
|
6
|
+
import { Cause, Clock, Crypto, Deferred, Duration, Effect, Exit, Option, Queue, Schema, Stream } from "effect";
|
|
6
7
|
//#region src/internal/capture/FrameBuffer.ts
|
|
7
8
|
/** Callback-side bounded retention. No Promise, Effect, blocked producer, or per-frame fiber. */
|
|
8
9
|
var FrameBuffer = class {
|
|
@@ -83,7 +84,7 @@ const Metadata = Schema.Struct({
|
|
|
83
84
|
});
|
|
84
85
|
const decodeMetadata = Schema.decodeUnknownOption(Metadata);
|
|
85
86
|
/** Private seam for deterministic callback/lifetime tests. The public start accepts a real live session. */
|
|
86
|
-
const
|
|
87
|
+
const acquireCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
87
88
|
const maxFrames = options.maxFrames ?? CaptureDefaults.maxFrames;
|
|
88
89
|
const maxBytes = options.maxBufferedBytes ?? CaptureDefaults.maxBufferedBytes;
|
|
89
90
|
const maxFrameBytes = options.maxFrameBytes ?? CaptureDefaults.maxFrameBytes;
|
|
@@ -108,7 +109,10 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
108
109
|
reason: Reasons.Configuration.make({}),
|
|
109
110
|
outcome: "undispatched"
|
|
110
111
|
})));
|
|
111
|
-
|
|
112
|
+
let interval;
|
|
113
|
+
yield* Effect.addFinalizer((exit) => Effect.sync(() => interval?.end(exit)));
|
|
114
|
+
const clock = parent.owner.clock;
|
|
115
|
+
const captureId = yield* parent.newCaptureId;
|
|
112
116
|
const wake = yield* Queue.dropping(1);
|
|
113
117
|
const finished = yield* Queue.dropping(1);
|
|
114
118
|
const completed = yield* Deferred.make();
|
|
@@ -119,29 +123,49 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
119
123
|
let startSettled = true;
|
|
120
124
|
let target;
|
|
121
125
|
let leaseKey;
|
|
126
|
+
let targetStatus;
|
|
127
|
+
let metadata;
|
|
128
|
+
let terminalAuthority;
|
|
122
129
|
let ended = false, subscribed = false, cleanupFinished = false;
|
|
123
130
|
let reason = "stopped";
|
|
124
131
|
let error;
|
|
125
132
|
let received = 0, delivered = 0, rejected = 0, duplicates = 0, late = 0, lateRun = 0;
|
|
126
133
|
let first, last;
|
|
127
134
|
let document = 0;
|
|
135
|
+
let captureBoundary = 0;
|
|
136
|
+
let acceptedBoundary = -1;
|
|
128
137
|
let initialUrl = null;
|
|
138
|
+
let initialUrlQualification = "Unread";
|
|
129
139
|
let documentBoundariesTruncated = false;
|
|
130
140
|
const documentBoundaries = [];
|
|
131
141
|
/** A URL change is recorded; only a new document advances the frame attribution. */
|
|
132
142
|
const nextDocument = (url, sameDocument = false) => {
|
|
133
143
|
if (ended) return;
|
|
144
|
+
captureBoundary++;
|
|
134
145
|
if (!sameDocument) document++;
|
|
135
146
|
if (documentBoundaries.length >= MaxDocumentBoundaries) {
|
|
136
147
|
documentBoundaries.shift();
|
|
137
148
|
documentBoundariesTruncated = true;
|
|
138
149
|
}
|
|
139
|
-
|
|
150
|
+
const boundary = {
|
|
140
151
|
document,
|
|
141
152
|
sameDocument,
|
|
142
153
|
observedMonotonicNanos: clock.monotonicTimeNanosUnsafe(),
|
|
143
154
|
afterSequence: received === 0 ? null : received - 1,
|
|
144
155
|
url: documentUrl(url)
|
|
156
|
+
};
|
|
157
|
+
documentBoundaries.push(boundary);
|
|
158
|
+
if (target !== void 0) metadata?.({
|
|
159
|
+
_tag: "CaptureBoundary",
|
|
160
|
+
captureId,
|
|
161
|
+
target,
|
|
162
|
+
captureBoundary,
|
|
163
|
+
captureDocument: document,
|
|
164
|
+
sameDocument: boundary.sameDocument,
|
|
165
|
+
observedMonotonicNanos: boundary.observedMonotonicNanos,
|
|
166
|
+
afterSequence: boundary.afterSequence,
|
|
167
|
+
url: boundary.url,
|
|
168
|
+
urlQualification: boundary.url === null ? "Omitted" : "NativeCached"
|
|
145
169
|
});
|
|
146
170
|
};
|
|
147
171
|
let geometry;
|
|
@@ -165,6 +189,7 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
165
189
|
if (afterStart) {
|
|
166
190
|
nativeStop = "confirmed";
|
|
167
191
|
releaseReservation();
|
|
192
|
+
if (cleanupFinished) publishCapture("Stopped");
|
|
168
193
|
} else {
|
|
169
194
|
preStartStopConfirmed = true;
|
|
170
195
|
confirmPostStartStop();
|
|
@@ -182,13 +207,34 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
182
207
|
ended = true;
|
|
183
208
|
reason = why;
|
|
184
209
|
error = failure;
|
|
210
|
+
publishCapture("Ended");
|
|
185
211
|
Queue.offerUnsafe(wake, void 0);
|
|
186
212
|
Queue.offerUnsafe(finished, void 0);
|
|
187
213
|
};
|
|
188
214
|
const snapshot = () => {
|
|
189
215
|
if (target === void 0) throw new Error("Capture target was not admitted");
|
|
216
|
+
const status = targetStatus?.();
|
|
217
|
+
const ownerPhase = parent.owner.state.phase;
|
|
218
|
+
const ownerGeneration = parent.owner.state.generation;
|
|
219
|
+
const sessionFenced = [
|
|
220
|
+
"uncertain",
|
|
221
|
+
"faulted",
|
|
222
|
+
"closing",
|
|
223
|
+
"closed"
|
|
224
|
+
].includes(ownerPhase);
|
|
225
|
+
const containment = status !== void 0 && status.containment._tag !== "NotRequired" ? { ...status.containment } : sessionFenced ? {
|
|
226
|
+
_tag: "SessionFenced",
|
|
227
|
+
generation: ownerGeneration
|
|
228
|
+
} : { _tag: "NotRequired" };
|
|
229
|
+
const qualification = {
|
|
230
|
+
authority: status?.phase === "closed" || terminalAuthority === "closed" ? "closed" : target.generation !== ownerGeneration ? "stale" : status?.phase ?? terminalAuthority ?? "open",
|
|
231
|
+
containment,
|
|
232
|
+
ownerPhase,
|
|
233
|
+
ownerGeneration
|
|
234
|
+
};
|
|
190
235
|
return CaptureSummary.make({
|
|
191
236
|
target,
|
|
237
|
+
qualification,
|
|
192
238
|
reason,
|
|
193
239
|
received,
|
|
194
240
|
delivered,
|
|
@@ -211,10 +257,38 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
211
257
|
...error === void 0 ? {} : { error: BrowserError.make({
|
|
212
258
|
operation: error.operation,
|
|
213
259
|
reason: { ...error.reason },
|
|
214
|
-
outcome: error.outcome
|
|
260
|
+
outcome: error.outcome,
|
|
261
|
+
...error.containment !== void 0 ? { containment: { ...error.containment } } : containment._tag === "NotRequired" ? {} : { containment }
|
|
215
262
|
}) }
|
|
216
263
|
});
|
|
217
264
|
};
|
|
265
|
+
const publishCapture = (phase) => {
|
|
266
|
+
if (metadata === void 0 || target === void 0) return;
|
|
267
|
+
const facts = snapshot();
|
|
268
|
+
metadata({
|
|
269
|
+
_tag: "Capture",
|
|
270
|
+
captureId,
|
|
271
|
+
target,
|
|
272
|
+
phase,
|
|
273
|
+
latePhase: ended && (phase === "Watching" || phase === "Started"),
|
|
274
|
+
observedMonotonicNanos: clock.monotonicTimeNanosUnsafe(),
|
|
275
|
+
captureBoundary,
|
|
276
|
+
captureDocument: document,
|
|
277
|
+
qualification: facts.qualification,
|
|
278
|
+
initialUrl,
|
|
279
|
+
initialUrlQualification,
|
|
280
|
+
reason: ended ? reason : null,
|
|
281
|
+
nativeStop: phase === "Stopped" ? nativeStop : null,
|
|
282
|
+
received,
|
|
283
|
+
delivered,
|
|
284
|
+
discarded: facts.discarded,
|
|
285
|
+
overflow: facts.overflow,
|
|
286
|
+
late,
|
|
287
|
+
duplicates,
|
|
288
|
+
rejected,
|
|
289
|
+
upstreamDrops: "unknown"
|
|
290
|
+
});
|
|
291
|
+
};
|
|
218
292
|
const stopNative = (yield* Effect.cached(Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
219
293
|
finish("stopped");
|
|
220
294
|
if (startPromise !== void 0 && !startSettled) {
|
|
@@ -226,7 +300,7 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
226
300
|
reason: Reasons.Provider.make({}),
|
|
227
301
|
outcome: "unknown"
|
|
228
302
|
})
|
|
229
|
-
}).pipe(Effect.timeout(2e3), Effect.exit));
|
|
303
|
+
}).pipe(Effect.timeout(2e3), span("Browser.capture.start-settlement"), Effect.exit));
|
|
230
304
|
}
|
|
231
305
|
if (source !== void 0) {
|
|
232
306
|
const stopping = source;
|
|
@@ -240,7 +314,7 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
240
314
|
reason: Reasons.Provider.make({}),
|
|
241
315
|
outcome: "unknown"
|
|
242
316
|
})
|
|
243
|
-
}).pipe(Effect.timeout(3e3))).pipe(Effect.exit);
|
|
317
|
+
}).pipe(Effect.timeout(3e3), span("Browser.capture.native-stop"))).pipe(Effect.exit);
|
|
244
318
|
if (Exit.isSuccess(stopped) && afterStart) {
|
|
245
319
|
nativeStop = "confirmed";
|
|
246
320
|
releaseReservation();
|
|
@@ -248,8 +322,15 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
248
322
|
} else nativeStop = "confirmed";
|
|
249
323
|
if (nativeStop === "confirmed") releaseReservation();
|
|
250
324
|
cleanupFinished = true;
|
|
325
|
+
publishCapture("Stopped");
|
|
326
|
+
interval?.end(error === void 0 ? Exit.void : Exit.fail(error), {
|
|
327
|
+
"browser.capture.reason": reason,
|
|
328
|
+
"browser.capture.native-stop": nativeStop,
|
|
329
|
+
"browser.capture.received": received,
|
|
330
|
+
"browser.capture.discarded": buffer.dropped + rejected + late + duplicates
|
|
331
|
+
});
|
|
251
332
|
return snapshot();
|
|
252
|
-
})))).pipe(Effect.tap(() => Deferred.succeed(completed, void 0)));
|
|
333
|
+
}).pipe(span("Browser.capture.cleanup"))).pipe(Effect.provideService(Clock.Clock, clock)))).pipe(Effect.tap(() => Deferred.succeed(completed, void 0)));
|
|
253
334
|
const receive = (frame) => {
|
|
254
335
|
if (ended) return;
|
|
255
336
|
if (target === void 0 || parent.owner.state.generation !== target.generation || parent.owner.state.phase !== "open") {
|
|
@@ -359,6 +440,7 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
359
440
|
first ??= meta.timestamp;
|
|
360
441
|
last = meta.timestamp;
|
|
361
442
|
lateRun = 0;
|
|
443
|
+
const receivedMonotonicNanos = clock.monotonicTimeNanosUnsafe();
|
|
362
444
|
buffer.offer({
|
|
363
445
|
bytes: new Uint8Array(frame.data),
|
|
364
446
|
mediaType: "image/jpeg",
|
|
@@ -367,11 +449,28 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
367
449
|
document,
|
|
368
450
|
sourceTimeMillis: meta.timestamp,
|
|
369
451
|
sourceClock: "presentation-unix-millis",
|
|
370
|
-
receivedMonotonicNanos
|
|
452
|
+
receivedMonotonicNanos,
|
|
371
453
|
...dimensions,
|
|
372
454
|
viewportWidth: meta.viewportWidth,
|
|
373
455
|
viewportHeight: meta.viewportHeight
|
|
374
456
|
});
|
|
457
|
+
if (captureBoundary !== acceptedBoundary) {
|
|
458
|
+
acceptedBoundary = captureBoundary;
|
|
459
|
+
metadata?.({
|
|
460
|
+
_tag: "FirstFrame",
|
|
461
|
+
captureId,
|
|
462
|
+
target,
|
|
463
|
+
captureBoundary,
|
|
464
|
+
captureDocument: document,
|
|
465
|
+
frameSequence: sequence,
|
|
466
|
+
sourceTimeMillis: meta.timestamp,
|
|
467
|
+
sourceClock: "presentation-unix-millis",
|
|
468
|
+
receivedMonotonicNanos,
|
|
469
|
+
...dimensions,
|
|
470
|
+
viewportWidth: meta.viewportWidth,
|
|
471
|
+
viewportHeight: meta.viewportHeight
|
|
472
|
+
});
|
|
473
|
+
}
|
|
375
474
|
Queue.offerUnsafe(wake, void 0);
|
|
376
475
|
} catch {
|
|
377
476
|
rejected++;
|
|
@@ -382,9 +481,14 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
382
481
|
}));
|
|
383
482
|
}
|
|
384
483
|
};
|
|
484
|
+
yield* parent.validate("capture-start");
|
|
485
|
+
const requested = parent.page;
|
|
486
|
+
const generation = parent.owner.state.generation;
|
|
385
487
|
yield* parent.owner.guard("capture-start", (ticket) => Effect.gen(function* () {
|
|
386
|
-
const resolved = yield* parent.resolve(ticket,
|
|
488
|
+
const resolved = yield* parent.resolve(ticket, requested);
|
|
387
489
|
target = Object.freeze(resolved.target);
|
|
490
|
+
targetStatus = resolved.status;
|
|
491
|
+
metadata = resolved.metadata;
|
|
388
492
|
const resolvedSource = resolved.source;
|
|
389
493
|
source = resolvedSource;
|
|
390
494
|
leaseKey = resolved.key;
|
|
@@ -406,10 +510,19 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
406
510
|
}),
|
|
407
511
|
outcome: "undispatched"
|
|
408
512
|
});
|
|
513
|
+
yield* Effect.uninterruptible(start$1("Browser.capture.interval", { attributes: {
|
|
514
|
+
"browser.operation": "capture",
|
|
515
|
+
"browser.capture.lifetime": options.lifetime === "page" ? "page" : "document"
|
|
516
|
+
} }).pipe(Effect.tap((started) => Effect.sync(() => {
|
|
517
|
+
interval = started;
|
|
518
|
+
}))));
|
|
409
519
|
lease = {
|
|
520
|
+
pageId: target.pageId,
|
|
410
521
|
reservedBytes: maxBytes,
|
|
411
522
|
stop: stopNative.pipe(Effect.asVoid),
|
|
412
523
|
invalidate: (why) => {
|
|
524
|
+
if (why === "target-closed") terminalAuthority = "closed";
|
|
525
|
+
else if (why === "closed") terminalAuthority ??= "closing";
|
|
413
526
|
if (why === "target-closed") releaseReservation();
|
|
414
527
|
finish(why === "resized" ? "resized" : "target-changed", BrowserError.make({
|
|
415
528
|
operation: "capture",
|
|
@@ -424,6 +537,7 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
424
537
|
rejected += buffer.size;
|
|
425
538
|
buffer.clear();
|
|
426
539
|
}))));
|
|
540
|
+
publishCapture("Reserved");
|
|
427
541
|
startSettled = false;
|
|
428
542
|
yield* Effect.tryPromise({
|
|
429
543
|
try: () => {
|
|
@@ -434,6 +548,8 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
434
548
|
invalidate: (why) => lease?.invalidate(why),
|
|
435
549
|
opened: (url) => {
|
|
436
550
|
initialUrl = documentUrl(url);
|
|
551
|
+
initialUrlQualification = initialUrl === null ? "Omitted" : "NativeCached";
|
|
552
|
+
publishCapture("Watching");
|
|
437
553
|
},
|
|
438
554
|
...size === void 0 ? {} : { size },
|
|
439
555
|
...options.lifetime === "page" ? { document: nextDocument } : {}
|
|
@@ -454,10 +570,22 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
454
570
|
reason: Reasons.Provider.make({}),
|
|
455
571
|
outcome: "unknown"
|
|
456
572
|
})
|
|
457
|
-
});
|
|
573
|
+
}).pipe(span("Browser.capture.native-start"));
|
|
574
|
+
publishCapture("Started");
|
|
458
575
|
ticket.check();
|
|
459
|
-
}), {
|
|
460
|
-
|
|
576
|
+
}), {
|
|
577
|
+
charge: false,
|
|
578
|
+
admission: options.admission,
|
|
579
|
+
targetScope: () => ({ pageId: requested.pageId }),
|
|
580
|
+
preflight: parent.validate("capture-start").pipe(Effect.andThen(Effect.suspend(() => generation === parent.owner.state.generation ? Effect.void : Effect.fail(BrowserError.make({
|
|
581
|
+
operation: "capture-start",
|
|
582
|
+
reason: Reasons.Stale.make({}),
|
|
583
|
+
outcome: "undispatched"
|
|
584
|
+
})))))
|
|
585
|
+
}).pipe(Effect.provideService(Clock.Clock, clock), Effect.onExit((exit) => Effect.sync(() => {
|
|
586
|
+
if (Exit.isFailure(exit)) interval?.end(exit);
|
|
587
|
+
})), Effect.onError(() => lease === void 0 ? Effect.void : stopNative.pipe(Effect.asVoid)), Effect.onInterrupt(() => lease === void 0 ? Effect.void : stopNative.pipe(Effect.asVoid)));
|
|
588
|
+
yield* Effect.raceFirst(Queue.take(finished).pipe(Effect.as(false)), clock.sleep(Duration.millis(duration)).pipe(Effect.as(true))).pipe(Effect.tap((expired) => expired ? Effect.sync(() => finish("duration-limit")) : Effect.void), Effect.andThen(stopNative), Effect.forkScoped);
|
|
461
589
|
const next = Effect.suspend(() => {
|
|
462
590
|
const frame = buffer.take();
|
|
463
591
|
if (frame !== void 0) {
|
|
@@ -468,6 +596,7 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
468
596
|
return Queue.take(wake).pipe(Effect.andThen(next));
|
|
469
597
|
});
|
|
470
598
|
return {
|
|
599
|
+
id: captureId,
|
|
471
600
|
frames: Stream.unwrap(Effect.suspend(() => {
|
|
472
601
|
if (subscribed) return Effect.fail(BrowserError.make({
|
|
473
602
|
operation: "capture-consume",
|
|
@@ -476,7 +605,7 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
476
605
|
}));
|
|
477
606
|
subscribed = true;
|
|
478
607
|
return Effect.succeed(Stream.fromEffectRepeat(next).pipe(Stream.ensuring(stopNative.pipe(Effect.asVoid))));
|
|
479
|
-
})),
|
|
608
|
+
})).pipe(stream$1("Browser.capture.consume")),
|
|
480
609
|
snapshot: Effect.sync(() => {
|
|
481
610
|
const { reason, nativeStop, ...metadata } = snapshot();
|
|
482
611
|
return CaptureSnapshot.make({
|
|
@@ -492,34 +621,30 @@ const startCapture = Effect.fnUntraced(function* (parent, options = {}) {
|
|
|
492
621
|
completed: Deferred.await(completed).pipe(Effect.map(snapshot))
|
|
493
622
|
};
|
|
494
623
|
});
|
|
624
|
+
const startCapture = (parent, options = {}) => acquireCapture(parent, options).pipe(span("Browser.capture.start", { attributes: { "browser.operation": "capture-start" } }));
|
|
495
625
|
//#endregion
|
|
496
626
|
//#region src/Capture.ts
|
|
497
627
|
/**
|
|
498
628
|
* Capture one remote page independently of the session's selected page.
|
|
499
629
|
*
|
|
500
|
-
* Requires
|
|
501
|
-
*
|
|
630
|
+
* Requires an exact issued Page; copying an object or decoding a durable reference cannot
|
|
631
|
+
* copy its capture authority. The private owner is not frame data.
|
|
502
632
|
* `CapturedFrame` and `CaptureOptions` are Schema values as well as structural types. Frame
|
|
503
633
|
* decoding checks binary/metadata fields, not the complete JPEG bitstream or target authority,
|
|
504
634
|
* and does not copy bytes. Options decoding preserves omissions; admission applies defaults.
|
|
505
635
|
* `CaptureInterval` remains a live scoped capability, not a schema or JSON/Tool value.
|
|
506
636
|
*/
|
|
507
|
-
const start = (
|
|
508
|
-
const parent =
|
|
637
|
+
const start = (page, options = {}) => Effect.suspend(() => {
|
|
638
|
+
const parent = capturePageParent(page);
|
|
509
639
|
if (parent === void 0) return Effect.fail(BrowserError.make({
|
|
510
640
|
operation: "capture",
|
|
511
641
|
reason: Reasons.UnregisteredSession.make({}),
|
|
512
642
|
outcome: "undispatched"
|
|
513
643
|
}));
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
outcome: "undispatched"
|
|
519
|
-
})), Effect.flatMap((target) => startCapture(parent, {
|
|
520
|
-
...options,
|
|
521
|
-
target
|
|
522
|
-
})));
|
|
644
|
+
return checked(CaptureOptions, options, "capture").pipe(Effect.flatMap(({ admission, ...capture }) => checkedOperationOptions(admission === void 0 ? {} : { admission }, "capture").pipe(Effect.flatMap((operationOptions) => startCapture(parent, {
|
|
645
|
+
...capture,
|
|
646
|
+
...operationOptions.admission === void 0 ? {} : { admission: operationOptions.admission }
|
|
647
|
+
})))));
|
|
523
648
|
});
|
|
524
649
|
/**
|
|
525
650
|
* A lazy, scoped frame stream. Each subscription acquires one interval and finalizes it when
|
|
@@ -528,7 +653,7 @@ const start = (session, options = {}) => Effect.suspend(() => {
|
|
|
528
653
|
* Unconfirmed native cleanup retains the page reservation. Use start when the host needs
|
|
529
654
|
* explicit stop acknowledgement, interval snapshots and the final capture summary.
|
|
530
655
|
*/
|
|
531
|
-
const stream = (
|
|
656
|
+
const stream = (page, options = {}) => Stream.unwrap(start(page, options).pipe(Effect.map((interval) => interval.frames)));
|
|
532
657
|
/**
|
|
533
658
|
* Frames as motion JPEG for an `<img>`: one `multipart/x-mixed-replace` response, as WHATWG HTML
|
|
534
659
|
* defines it for images. Each frame is followed at once by the next part's delimiter and headers,
|
|
@@ -548,6 +673,6 @@ const multipart = (frames) => Effect.map(Effect.flatMap(Crypto.Crypto, (crypto)
|
|
|
548
673
|
};
|
|
549
674
|
});
|
|
550
675
|
//#endregion
|
|
551
|
-
export { CaptureOptions, CaptureSize, CaptureSnapshot, CaptureSummary, CapturedFrame, multipart, start, stream };
|
|
676
|
+
export { CaptureOptions, CaptureQualification, CaptureSize, CaptureSnapshot, CaptureSummary, CapturedFrame, multipart, start, stream };
|
|
552
677
|
|
|
553
678
|
//# sourceMappingURL=Capture.mjs.map
|