effect-browser 0.2.0-beta.6 → 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 +507 -130
- package/dist/Browser-n-bKgBNN.d.mts +372 -0
- package/dist/Browser.d.mts +3 -189
- package/dist/Browser.mjs +9 -1
- package/dist/Browser.mjs.map +1 -1
- package/dist/{BrowserData-DPh3Oaof.d.mts → BrowserData-CPIIIHol.d.mts} +84 -10
- package/dist/BrowserData.d.mts +2 -2
- package/dist/BrowserData.mjs +106 -7
- package/dist/BrowserData.mjs.map +1 -1
- package/dist/{BrowserRuntime-BBAjpVFa.d.mts → BrowserRuntime-CFe9uUTv.d.mts} +16 -11
- package/dist/{BrowserRuntime-DImn_Oe_.mjs → BrowserRuntime-HJ1yYlFS.mjs} +4977 -4134
- 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 +69 -17
- package/dist/Errors.mjs +57 -6
- 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 +675 -123
- 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-BsSfBYVx.mjs +0 -138
- package/dist/Association-BsSfBYVx.mjs.map +0 -1
- package/dist/BrowserRuntime-DImn_Oe_.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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Reasons } from "./Errors.mjs";
|
|
2
|
+
import { y as failure } from "./PublicSession-Bs61Eal8.mjs";
|
|
3
|
+
//#region src/internal/browser/Images.ts
|
|
4
|
+
const malformed = () => failure(Reasons.Malformed.make({}));
|
|
5
|
+
/** A checked read: a truncated segment is malformed framing, never `undefined` arithmetic. */
|
|
6
|
+
const byteAt = (bytes, index) => {
|
|
7
|
+
const byte = bytes[index];
|
|
8
|
+
if (byte === void 0) throw malformed();
|
|
9
|
+
return byte;
|
|
10
|
+
};
|
|
11
|
+
/** Parse framing only; a caller decoding media must still validate its complete bitstream. */
|
|
12
|
+
const pngGeometry = (bytes) => {
|
|
13
|
+
if (bytes.length < 33 || [
|
|
14
|
+
137,
|
|
15
|
+
80,
|
|
16
|
+
78,
|
|
17
|
+
71,
|
|
18
|
+
13,
|
|
19
|
+
10,
|
|
20
|
+
26,
|
|
21
|
+
10
|
|
22
|
+
].some((b, i) => bytes[i] !== b) || bytes[12] !== 73 || bytes[13] !== 72 || bytes[14] !== 68 || bytes[15] !== 82) throw malformed();
|
|
23
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
24
|
+
if (view.getUint32(8) !== 13) throw malformed();
|
|
25
|
+
const width = view.getUint32(16), height = view.getUint32(20);
|
|
26
|
+
if (width === 0 || height === 0) throw malformed();
|
|
27
|
+
return {
|
|
28
|
+
width,
|
|
29
|
+
height
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** JPEG SOF dimensions avoid trusting the requested size or CSS viewport dimensions. */
|
|
33
|
+
const jpegGeometry = (bytes) => {
|
|
34
|
+
if (bytes.length < 4 || bytes[0] !== 255 || bytes[1] !== 216) throw malformed();
|
|
35
|
+
let offset = 2;
|
|
36
|
+
while (offset + 3 < bytes.length) {
|
|
37
|
+
if (bytes[offset++] !== 255) throw malformed();
|
|
38
|
+
while (bytes[offset] === 255) offset++;
|
|
39
|
+
const marker = bytes[offset++];
|
|
40
|
+
if (marker === void 0 || marker === 217 || marker === 218) break;
|
|
41
|
+
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
42
|
+
if (offset + 1 >= bytes.length) throw malformed();
|
|
43
|
+
const size = byteAt(bytes, offset) * 256 + byteAt(bytes, offset + 1);
|
|
44
|
+
if (size < 2 || offset + size > bytes.length) throw malformed();
|
|
45
|
+
if ([
|
|
46
|
+
192,
|
|
47
|
+
193,
|
|
48
|
+
194,
|
|
49
|
+
195,
|
|
50
|
+
197,
|
|
51
|
+
198,
|
|
52
|
+
199,
|
|
53
|
+
201,
|
|
54
|
+
202,
|
|
55
|
+
203,
|
|
56
|
+
205,
|
|
57
|
+
206,
|
|
58
|
+
207
|
|
59
|
+
].includes(marker)) {
|
|
60
|
+
if (size < 8) throw malformed();
|
|
61
|
+
const height = byteAt(bytes, offset + 3) * 256 + byteAt(bytes, offset + 4);
|
|
62
|
+
const width = byteAt(bytes, offset + 5) * 256 + byteAt(bytes, offset + 6);
|
|
63
|
+
if (width === 0 || height === 0) throw malformed();
|
|
64
|
+
return {
|
|
65
|
+
width,
|
|
66
|
+
height
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
offset += size;
|
|
70
|
+
}
|
|
71
|
+
throw malformed();
|
|
72
|
+
};
|
|
73
|
+
//#endregion
|
|
74
|
+
export { pngGeometry as n, jpegGeometry as t };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=Images-CaaPNeed.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Images-CaaPNeed.mjs","names":[],"sources":["../src/internal/browser/Images.ts"],"sourcesContent":["import { Reasons } from \"../../Errors.ts\";\nimport { failure } from \"./NativeCalls.ts\";\n\nconst malformed = () => failure(Reasons.Malformed.make({}));\n\n/** A checked read: a truncated segment is malformed framing, never `undefined` arithmetic. */\nconst byteAt = (bytes: Uint8Array, index: number): number => {\n const byte = bytes[index];\n\n if (byte === undefined) throw malformed();\n\n return byte;\n};\n\n/** Parse framing only; a caller decoding media must still validate its complete bitstream. */\nexport const pngGeometry = (bytes: Uint8Array): { width: number; height: number } => {\n const signature = [137, 80, 78, 71, 13, 10, 26, 10];\n\n if (\n bytes.length < 33 ||\n signature.some((b, i) => bytes[i] !== b) ||\n bytes[12] !== 73 ||\n bytes[13] !== 72 ||\n bytes[14] !== 68 ||\n bytes[15] !== 82\n )\n throw malformed();\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n\n if (view.getUint32(8) !== 13) throw malformed();\n\n const width = view.getUint32(16),\n height = view.getUint32(20);\n\n if (width === 0 || height === 0) throw malformed();\n\n return { width, height };\n};\n\n/** JPEG SOF dimensions avoid trusting the requested size or CSS viewport dimensions. */\nexport const jpegGeometry = (bytes: Uint8Array): { width: number; height: number } => {\n if (bytes.length < 4 || bytes[0] !== 255 || bytes[1] !== 216) throw malformed();\n let offset = 2;\n\n while (offset + 3 < bytes.length) {\n if (bytes[offset++] !== 255) throw malformed();\n while (bytes[offset] === 255) offset++;\n const marker = bytes[offset++];\n\n if (marker === undefined || marker === 217 || marker === 218) break;\n if (marker === 1 || (marker >= 208 && marker <= 215)) continue;\n if (offset + 1 >= bytes.length) throw malformed();\n const size = byteAt(bytes, offset) * 256 + byteAt(bytes, offset + 1);\n\n if (size < 2 || offset + size > bytes.length) throw malformed();\n if ([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207].includes(marker)) {\n if (size < 8) throw malformed();\n const height = byteAt(bytes, offset + 3) * 256 + byteAt(bytes, offset + 4);\n const width = byteAt(bytes, offset + 5) * 256 + byteAt(bytes, offset + 6);\n\n if (width === 0 || height === 0) throw malformed();\n\n return { width, height };\n }\n offset += size;\n }\n throw malformed();\n};\n"],"mappings":";;;AAGA,MAAM,kBAAkB,QAAQ,QAAQ,UAAU,KAAK,CAAC,CAAC,CAAC;;AAG1D,MAAM,UAAU,OAAmB,UAA0B;CAC3D,MAAM,OAAO,MAAM;CAEnB,IAAI,SAAS,KAAA,GAAW,MAAM,UAAU;CAExC,OAAO;AACT;;AAGA,MAAa,eAAe,UAAyD;CAGnF,IACE,MAAM,SAAS,MACf;EAJiB;EAAK;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAItC,CAAC,CAAC,MAAM,GAAG,MAAM,MAAM,OAAO,CAAC,KACvC,MAAM,QAAQ,MACd,MAAM,QAAQ,MACd,MAAM,QAAQ,MACd,MAAM,QAAQ,IAEd,MAAM,UAAU;CAClB,MAAM,OAAO,IAAI,SAAS,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU;CAE1E,IAAI,KAAK,UAAU,CAAC,MAAM,IAAI,MAAM,UAAU;CAE9C,MAAM,QAAQ,KAAK,UAAU,EAAE,GAC7B,SAAS,KAAK,UAAU,EAAE;CAE5B,IAAI,UAAU,KAAK,WAAW,GAAG,MAAM,UAAU;CAEjD,OAAO;EAAE;EAAO;CAAO;AACzB;;AAGA,MAAa,gBAAgB,UAAyD;CACpF,IAAI,MAAM,SAAS,KAAK,MAAM,OAAO,OAAO,MAAM,OAAO,KAAK,MAAM,UAAU;CAC9E,IAAI,SAAS;CAEb,OAAO,SAAS,IAAI,MAAM,QAAQ;EAChC,IAAI,MAAM,cAAc,KAAK,MAAM,UAAU;EAC7C,OAAO,MAAM,YAAY,KAAK;EAC9B,MAAM,SAAS,MAAM;EAErB,IAAI,WAAW,KAAA,KAAa,WAAW,OAAO,WAAW,KAAK;EAC9D,IAAI,WAAW,KAAM,UAAU,OAAO,UAAU,KAAM;EACtD,IAAI,SAAS,KAAK,MAAM,QAAQ,MAAM,UAAU;EAChD,MAAM,OAAO,OAAO,OAAO,MAAM,IAAI,MAAM,OAAO,OAAO,SAAS,CAAC;EAEnE,IAAI,OAAO,KAAK,SAAS,OAAO,MAAM,QAAQ,MAAM,UAAU;EAC9D,IAAI;GAAC;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;EAAG,CAAC,CAAC,SAAS,MAAM,GAAG;GACtF,IAAI,OAAO,GAAG,MAAM,UAAU;GAC9B,MAAM,SAAS,OAAO,OAAO,SAAS,CAAC,IAAI,MAAM,OAAO,OAAO,SAAS,CAAC;GACzE,MAAM,QAAQ,OAAO,OAAO,SAAS,CAAC,IAAI,MAAM,OAAO,OAAO,SAAS,CAAC;GAExE,IAAI,UAAU,KAAK,WAAW,GAAG,MAAM,UAAU;GAEjD,OAAO;IAAE;IAAO;GAAO;EACzB;EACA,UAAU;CACZ;CACA,MAAM,UAAU;AAClB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Duration, Option, Schema } from "effect";
|
|
2
|
+
//#region src/internal/browser/OperationOptions.ts
|
|
3
|
+
const FiniteDurationValue = Schema.Union([Schema.TaggedStruct("Millis", { millis: Schema.Finite }), Schema.TaggedStruct("Nanos", { nanos: Schema.BigInt })]);
|
|
4
|
+
const FiniteDurationInput = Schema.Union([
|
|
5
|
+
Schema.Duration.check(Schema.makeFilter((duration) => Schema.is(FiniteDurationValue)(duration.value))),
|
|
6
|
+
Schema.Finite,
|
|
7
|
+
Schema.BigInt,
|
|
8
|
+
Schema.TemplateLiteral([
|
|
9
|
+
Schema.Finite,
|
|
10
|
+
" ",
|
|
11
|
+
Schema.Literals([
|
|
12
|
+
"nano",
|
|
13
|
+
"nanos",
|
|
14
|
+
"micro",
|
|
15
|
+
"micros",
|
|
16
|
+
"milli",
|
|
17
|
+
"millis",
|
|
18
|
+
"second",
|
|
19
|
+
"seconds",
|
|
20
|
+
"minute",
|
|
21
|
+
"minutes",
|
|
22
|
+
"hour",
|
|
23
|
+
"hours",
|
|
24
|
+
"day",
|
|
25
|
+
"days",
|
|
26
|
+
"week",
|
|
27
|
+
"weeks"
|
|
28
|
+
])
|
|
29
|
+
]),
|
|
30
|
+
Schema.Literals(["Infinity", "-Infinity"]),
|
|
31
|
+
Schema.Tuple([Schema.Finite, Schema.Finite]),
|
|
32
|
+
Schema.Struct({
|
|
33
|
+
weeks: Schema.optionalKey(Schema.Finite),
|
|
34
|
+
days: Schema.optionalKey(Schema.Finite),
|
|
35
|
+
hours: Schema.optionalKey(Schema.Finite),
|
|
36
|
+
minutes: Schema.optionalKey(Schema.Finite),
|
|
37
|
+
seconds: Schema.optionalKey(Schema.Finite),
|
|
38
|
+
milliseconds: Schema.optionalKey(Schema.Finite),
|
|
39
|
+
microseconds: Schema.optionalKey(Schema.Finite),
|
|
40
|
+
nanoseconds: Schema.optionalKey(Schema.Finite)
|
|
41
|
+
})
|
|
42
|
+
]).check(Schema.makeFilter((input) => Option.exists(Duration.fromInput(input), (duration) => Duration.isFinite(duration) && !Duration.isNegative(duration) && Number.isFinite(Duration.toMillis(duration))), { message: "Queue duration must be finite and nonnegative" }));
|
|
43
|
+
/** Host configuration only; never part of model-facing request schemas. */
|
|
44
|
+
const AdmissionOptionsSchema = Schema.Struct({ queue: Schema.optionalKey(FiniteDurationInput) });
|
|
45
|
+
const OperationOptionsSchema = Schema.Struct({
|
|
46
|
+
timeoutMillis: Schema.optionalKey(Schema.Int.check(Schema.isBetween({
|
|
47
|
+
minimum: 1,
|
|
48
|
+
maximum: 6e4
|
|
49
|
+
}))),
|
|
50
|
+
admission: Schema.optionalKey(AdmissionOptionsSchema)
|
|
51
|
+
});
|
|
52
|
+
//#endregion
|
|
53
|
+
export { FiniteDurationInput as n, OperationOptionsSchema as r, AdmissionOptionsSchema as t };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=OperationOptions-DAeRS6O9.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OperationOptions-DAeRS6O9.mjs","names":[],"sources":["../src/internal/browser/OperationOptions.ts"],"sourcesContent":["import { Duration, Option, Schema } from \"effect\";\n\nconst FiniteDurationValue = Schema.Union([\n Schema.TaggedStruct(\"Millis\", { millis: Schema.Finite }),\n Schema.TaggedStruct(\"Nanos\", { nanos: Schema.BigInt }),\n]);\n\n// Duration.fromInput intentionally normalizes raw NaN to zero. Validate raw components first\n// so an invalid queue allowance cannot silently become fail-fast admission.\nexport const FiniteDurationInput = Schema.Union([\n // The built-in declaration checks only the Duration marker. Validate its representation\n // before arithmetic so a malformed host value is a typed refusal rather than a defect.\n Schema.Duration.check(\n Schema.makeFilter((duration) => Schema.is(FiniteDurationValue)(duration.value)),\n ),\n Schema.Finite,\n Schema.BigInt,\n Schema.TemplateLiteral([\n Schema.Finite,\n \" \",\n Schema.Literals([\n \"nano\",\n \"nanos\",\n \"micro\",\n \"micros\",\n \"milli\",\n \"millis\",\n \"second\",\n \"seconds\",\n \"minute\",\n \"minutes\",\n \"hour\",\n \"hours\",\n \"day\",\n \"days\",\n \"week\",\n \"weeks\",\n ]),\n ]),\n Schema.Literals([\"Infinity\", \"-Infinity\"]),\n Schema.Tuple([Schema.Finite, Schema.Finite]),\n Schema.Struct({\n weeks: Schema.optionalKey(Schema.Finite),\n days: Schema.optionalKey(Schema.Finite),\n hours: Schema.optionalKey(Schema.Finite),\n minutes: Schema.optionalKey(Schema.Finite),\n seconds: Schema.optionalKey(Schema.Finite),\n milliseconds: Schema.optionalKey(Schema.Finite),\n microseconds: Schema.optionalKey(Schema.Finite),\n nanoseconds: Schema.optionalKey(Schema.Finite),\n }),\n]).check(\n Schema.makeFilter(\n (input) =>\n Option.exists(\n Duration.fromInput(input),\n (duration) =>\n Duration.isFinite(duration) &&\n !Duration.isNegative(duration) &&\n Number.isFinite(Duration.toMillis(duration)),\n ),\n { message: \"Queue duration must be finite and nonnegative\" },\n ),\n);\n\n/** Host configuration only; never part of model-facing request schemas. */\nexport const AdmissionOptionsSchema = Schema.Struct({\n queue: Schema.optionalKey(FiniteDurationInput),\n});\n\nexport const OperationOptionsSchema = Schema.Struct({\n timeoutMillis: Schema.optionalKey(\n Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 60000 })),\n ),\n admission: Schema.optionalKey(AdmissionOptionsSchema),\n});\n"],"mappings":";;AAEA,MAAM,sBAAsB,OAAO,MAAM,CACvC,OAAO,aAAa,UAAU,EAAE,QAAQ,OAAO,OAAO,CAAC,GACvD,OAAO,aAAa,SAAS,EAAE,OAAO,OAAO,OAAO,CAAC,CACvD,CAAC;AAID,MAAa,sBAAsB,OAAO,MAAM;CAG9C,OAAO,SAAS,MACd,OAAO,YAAY,aAAa,OAAO,GAAG,mBAAmB,CAAC,CAAC,SAAS,KAAK,CAAC,CAChF;CACA,OAAO;CACP,OAAO;CACP,OAAO,gBAAgB;EACrB,OAAO;EACP;EACA,OAAO,SAAS;GACd;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;CACH,CAAC;CACD,OAAO,SAAS,CAAC,YAAY,WAAW,CAAC;CACzC,OAAO,MAAM,CAAC,OAAO,QAAQ,OAAO,MAAM,CAAC;CAC3C,OAAO,OAAO;EACZ,OAAO,OAAO,YAAY,OAAO,MAAM;EACvC,MAAM,OAAO,YAAY,OAAO,MAAM;EACtC,OAAO,OAAO,YAAY,OAAO,MAAM;EACvC,SAAS,OAAO,YAAY,OAAO,MAAM;EACzC,SAAS,OAAO,YAAY,OAAO,MAAM;EACzC,cAAc,OAAO,YAAY,OAAO,MAAM;EAC9C,cAAc,OAAO,YAAY,OAAO,MAAM;EAC9C,aAAa,OAAO,YAAY,OAAO,MAAM;CAC/C,CAAC;AACH,CAAC,CAAC,CAAC,MACD,OAAO,YACJ,UACC,OAAO,OACL,SAAS,UAAU,KAAK,IACvB,aACC,SAAS,SAAS,QAAQ,KAC1B,CAAC,SAAS,WAAW,QAAQ,KAC7B,OAAO,SAAS,SAAS,SAAS,QAAQ,CAAC,CAC/C,GACF,EAAE,SAAS,gDAAgD,CAC7D,CACF;;AAGA,MAAa,yBAAyB,OAAO,OAAO,EAClD,OAAO,OAAO,YAAY,mBAAmB,EAC/C,CAAC;AAED,MAAa,yBAAyB,OAAO,OAAO;CAClD,eAAe,OAAO,YACpB,OAAO,IAAI,MAAM,OAAO,UAAU;EAAE,SAAS;EAAG,SAAS;CAAM,CAAC,CAAC,CACnE;CACA,WAAW,OAAO,YAAY,sBAAsB;AACtD,CAAC"}
|
package/dist/PageControl.d.mts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { BrowserError } from "./Errors.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { F as PageExecutionState, L as PageSuspension } from "./BrowserData-CPIIIHol.mjs";
|
|
3
|
+
import { l as OperationOptions, u as Page } from "./Browser-n-bKgBNN.mjs";
|
|
4
4
|
import { Effect } from "effect";
|
|
5
5
|
//#region src/PageControl.d.ts
|
|
6
6
|
/** Last acknowledged state; no remote guarantee survives connection loss or external control. */
|
|
7
|
-
export declare const state:
|
|
8
|
-
/** Explicit host-only hold requiring
|
|
9
|
-
export declare const suspend:
|
|
7
|
+
export declare const state: (page: Page, options?: OperationOptions) => Effect.Effect<PageExecutionState, BrowserError>;
|
|
8
|
+
/** Explicit host-only hold requiring pageControl support; capture ACKs never invoke it. */
|
|
9
|
+
export declare const suspend: (page: Page, options?: OperationOptions) => Effect.Effect<PageSuspension, BrowserError>;
|
|
10
10
|
/** Consumes the exact live receipt. Activation is intentional; stale receipts never replay commands. */
|
|
11
|
-
export declare const resume:
|
|
11
|
+
export declare const resume: (page: Page, receipt: PageSuspension, options?: OperationOptions) => Effect.Effect<void, BrowserError>;
|
|
12
12
|
//#endregion
|
|
13
13
|
export { PageExecutionState, PageSuspension };
|
|
14
14
|
export {};
|
package/dist/PageControl.mjs
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import { BrowserError, Reasons } from "./Errors.mjs";
|
|
2
|
-
import { PageExecutionState,
|
|
3
|
-
import {
|
|
2
|
+
import { PageExecutionState, PageSuspension } from "./BrowserData.mjs";
|
|
3
|
+
import { s as pageControl } from "./PageRegistry-B3q9DS3g.mjs";
|
|
4
|
+
import { n as checkedOperationOptions } from "./PublicSession-Bs61Eal8.mjs";
|
|
4
5
|
import { Effect, Schema } from "effect";
|
|
5
6
|
//#region src/PageControl.ts
|
|
6
|
-
const owner = (
|
|
7
|
-
const
|
|
8
|
-
return
|
|
7
|
+
const owner = (page) => Effect.suspend(() => {
|
|
8
|
+
const association = pageControl(page);
|
|
9
|
+
return association === void 0 ? Effect.fail(BrowserError.make({
|
|
9
10
|
operation: "page-control",
|
|
10
11
|
reason: Reasons.UnregisteredSession.make({}),
|
|
11
12
|
outcome: "undispatched"
|
|
12
|
-
})) : Effect.succeed(
|
|
13
|
+
})) : Effect.succeed(association);
|
|
13
14
|
});
|
|
14
15
|
const invalid = () => BrowserError.make({
|
|
15
16
|
operation: "page-control",
|
|
16
17
|
reason: Reasons.Configuration.make({}),
|
|
17
18
|
outcome: "undispatched"
|
|
18
19
|
});
|
|
20
|
+
const requested = (page, options) => owner(page).pipe(Effect.flatMap((association) => checkedOperationOptions(options, "page-control").pipe(Effect.map((options) => ({
|
|
21
|
+
port: association.port,
|
|
22
|
+
page: association.page,
|
|
23
|
+
options
|
|
24
|
+
})))));
|
|
19
25
|
/** Last acknowledged state; no remote guarantee survives connection loss or external control. */
|
|
20
|
-
const state = (
|
|
21
|
-
/** Explicit host-only hold requiring
|
|
22
|
-
const suspend = (
|
|
26
|
+
const state = (page, options) => requested(page, options).pipe(Effect.flatMap(({ port, page, options }) => port.state(page, options)));
|
|
27
|
+
/** Explicit host-only hold requiring pageControl support; capture ACKs never invoke it. */
|
|
28
|
+
const suspend = (page, options) => requested(page, options).pipe(Effect.flatMap(({ port, page, options }) => port.suspend(page, options)));
|
|
23
29
|
/** Consumes the exact live receipt. Activation is intentional; stale receipts never replay commands. */
|
|
24
|
-
const resume = (
|
|
30
|
+
const resume = (page, receipt, options) => Schema.decodeEffect(PageSuspension)(receipt).pipe(Effect.mapError(invalid), Effect.map((value) => Object.freeze(PageSuspension.make({ ...value }))), Effect.flatMap((value) => owner(page).pipe(Effect.flatMap(({ port, page }) => page.pageId !== value.pageId || page.targetId !== value.targetId ? Effect.fail(invalid()) : checkedOperationOptions(options, "page-control").pipe(Effect.flatMap((options) => port.resume(value, options)))))));
|
|
25
31
|
//#endregion
|
|
26
32
|
export { PageExecutionState, PageSuspension, resume, state, suspend };
|
|
27
33
|
|
package/dist/PageControl.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageControl.mjs","names":[],"sources":["../src/PageControl.ts"],"sourcesContent":["import { Effect, Schema } from \"effect\";\n\nimport type {
|
|
1
|
+
{"version":3,"file":"PageControl.mjs","names":[],"sources":["../src/PageControl.ts"],"sourcesContent":["import { Effect, Schema } from \"effect\";\n\nimport type { OperationOptions, Page } from \"./Browser.ts\";\nimport { type PageExecutionState, PageSuspension } from \"./BrowserData.ts\";\nimport { BrowserError, Reasons } from \"./Errors.ts\";\nimport { pageControl } from \"./internal/browser/PageRegistry.ts\";\nimport { checkedOperationOptions } from \"./internal/browser/PublicSession.ts\";\nexport { PageExecutionState, PageSuspension } from \"./BrowserData.ts\";\n\nconst owner = (page: Page) =>\n Effect.suspend(() => {\n const association = pageControl(page);\n\n return association === undefined\n ? Effect.fail(\n BrowserError.make({\n operation: \"page-control\",\n reason: Reasons.UnregisteredSession.make({}),\n outcome: \"undispatched\",\n }),\n )\n : Effect.succeed(association);\n });\n\nconst invalid = () =>\n BrowserError.make({\n operation: \"page-control\",\n reason: Reasons.Configuration.make({}),\n outcome: \"undispatched\",\n });\n\nconst requested = (page: Page, options?: OperationOptions) =>\n owner(page).pipe(\n Effect.flatMap((association) =>\n checkedOperationOptions(options, \"page-control\").pipe(\n Effect.map((options) => ({ port: association.port, page: association.page, options })),\n ),\n ),\n );\n\n/** Last acknowledged state; no remote guarantee survives connection loss or external control. */\nexport const state = (\n page: Page,\n options?: OperationOptions,\n): Effect.Effect<PageExecutionState, BrowserError> =>\n requested(page, options).pipe(\n Effect.flatMap(({ port, page, options }) => port.state(page, options)),\n );\n\n/** Explicit host-only hold requiring pageControl support; capture ACKs never invoke it. */\nexport const suspend = (\n page: Page,\n options?: OperationOptions,\n): Effect.Effect<PageSuspension, BrowserError> =>\n requested(page, options).pipe(\n Effect.flatMap(({ port, page, options }) => port.suspend(page, options)),\n );\n\n/** Consumes the exact live receipt. Activation is intentional; stale receipts never replay commands. */\nexport const resume = (\n page: Page,\n receipt: PageSuspension,\n options?: OperationOptions,\n): Effect.Effect<void, BrowserError> =>\n Schema.decodeEffect(PageSuspension)(receipt).pipe(\n Effect.mapError(invalid),\n Effect.map((value) => Object.freeze(PageSuspension.make({ ...value }))),\n Effect.flatMap((value) =>\n owner(page).pipe(\n Effect.flatMap(({ port, page }) =>\n page.pageId !== value.pageId || page.targetId !== value.targetId\n ? Effect.fail(invalid())\n : checkedOperationOptions(options, \"page-control\").pipe(\n Effect.flatMap((options) => port.resume(value, options)),\n ),\n ),\n ),\n ),\n );\n"],"mappings":";;;;;;AASA,MAAM,SAAS,SACb,OAAO,cAAc;CACnB,MAAM,cAAc,YAAY,IAAI;CAEpC,OAAO,gBAAgB,KAAA,IACnB,OAAO,KACL,aAAa,KAAK;EAChB,WAAW;EACX,QAAQ,QAAQ,oBAAoB,KAAK,CAAC,CAAC;EAC3C,SAAS;CACX,CAAC,CACH,IACA,OAAO,QAAQ,WAAW;AAChC,CAAC;AAEH,MAAM,gBACJ,aAAa,KAAK;CAChB,WAAW;CACX,QAAQ,QAAQ,cAAc,KAAK,CAAC,CAAC;CACrC,SAAS;AACX,CAAC;AAEH,MAAM,aAAa,MAAY,YAC7B,MAAM,IAAI,CAAC,CAAC,KACV,OAAO,SAAS,gBACd,wBAAwB,SAAS,cAAc,CAAC,CAAC,KAC/C,OAAO,KAAK,aAAa;CAAE,MAAM,YAAY;CAAM,MAAM,YAAY;CAAM;AAAQ,EAAE,CACvF,CACF,CACF;;AAGF,MAAa,SACX,MACA,YAEA,UAAU,MAAM,OAAO,CAAC,CAAC,KACvB,OAAO,SAAS,EAAE,MAAM,MAAM,cAAc,KAAK,MAAM,MAAM,OAAO,CAAC,CACvE;;AAGF,MAAa,WACX,MACA,YAEA,UAAU,MAAM,OAAO,CAAC,CAAC,KACvB,OAAO,SAAS,EAAE,MAAM,MAAM,cAAc,KAAK,QAAQ,MAAM,OAAO,CAAC,CACzE;;AAGF,MAAa,UACX,MACA,SACA,YAEA,OAAO,aAAa,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,KAC3C,OAAO,SAAS,OAAO,GACvB,OAAO,KAAK,UAAU,OAAO,OAAO,eAAe,KAAK,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,GACtE,OAAO,SAAS,UACd,MAAM,IAAI,CAAC,CAAC,KACV,OAAO,SAAS,EAAE,MAAM,WACtB,KAAK,WAAW,MAAM,UAAU,KAAK,aAAa,MAAM,WACpD,OAAO,KAAK,QAAQ,CAAC,IACrB,wBAAwB,SAAS,cAAc,CAAC,CAAC,KAC/C,OAAO,SAAS,YAAY,KAAK,OAAO,OAAO,OAAO,CAAC,CACzD,CACN,CACF,CACF,CACF"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { BrowserError, Reasons } from "./Errors.mjs";
|
|
2
|
+
import { Deferred, Effect } from "effect";
|
|
3
|
+
//#region src/internal/browser/PageRegistry.ts
|
|
4
|
+
/**
|
|
5
|
+
* The session's registered pages at their connection generations. Callers read the records; only
|
|
6
|
+
* these transitions change them.
|
|
7
|
+
*/
|
|
8
|
+
const makePageRegistry = () => {
|
|
9
|
+
const pages = /* @__PURE__ */ new Map();
|
|
10
|
+
const owned = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
const state = (record) => {
|
|
12
|
+
const found = owned.get(record);
|
|
13
|
+
if (found === void 0) throw new TypeError("Page record belongs to another registry");
|
|
14
|
+
return found;
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
get: (pageId) => pages.get(pageId),
|
|
18
|
+
records: () => [...pages.values()],
|
|
19
|
+
/** Registers a page at a new generation, replacing any record of an earlier one. */
|
|
20
|
+
add: (init) => {
|
|
21
|
+
const record = {
|
|
22
|
+
identity: init.identity,
|
|
23
|
+
admission: init.admission,
|
|
24
|
+
info: init.info,
|
|
25
|
+
frames: /* @__PURE__ */ new Map(),
|
|
26
|
+
phase: init.paused ? "paused" : "open",
|
|
27
|
+
containment: { _tag: "NotRequired" },
|
|
28
|
+
store: init.store,
|
|
29
|
+
retirement: init.retirement,
|
|
30
|
+
get terminal() {
|
|
31
|
+
return init.terminal();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
owned.set(record, record);
|
|
35
|
+
pages.set(init.info.pageId, record);
|
|
36
|
+
return record;
|
|
37
|
+
},
|
|
38
|
+
/** A popup/dialog policy holds an open page for its operator. */
|
|
39
|
+
pause: (pageId) => {
|
|
40
|
+
const page = pages.get(pageId);
|
|
41
|
+
if (page !== void 0 && page.phase === "open") page.phase = "paused";
|
|
42
|
+
},
|
|
43
|
+
/** The page is being closed: its issued authority refuses new work. */
|
|
44
|
+
closing: (pageId) => {
|
|
45
|
+
const page = pages.get(pageId);
|
|
46
|
+
if (page !== void 0 && page.phase !== "closed") page.phase = "closing";
|
|
47
|
+
},
|
|
48
|
+
/** The page closed: it settles its closure attempt and leaves the registry. */
|
|
49
|
+
closed: (pageId) => {
|
|
50
|
+
const page = pages.get(pageId);
|
|
51
|
+
if (page === void 0) return;
|
|
52
|
+
page.phase = "closed";
|
|
53
|
+
if (page.attempt !== void 0) Deferred.doneUnsafe(page.attempt, Effect.succeed(true));
|
|
54
|
+
pages.delete(pageId);
|
|
55
|
+
},
|
|
56
|
+
/** Records how an unknown outcome on this page was contained. */
|
|
57
|
+
contain: (record, containment) => {
|
|
58
|
+
state(record).containment = containment;
|
|
59
|
+
},
|
|
60
|
+
/** The native closure now in flight for this page. */
|
|
61
|
+
attempting: (record, attempt) => {
|
|
62
|
+
state(record).attempt = attempt;
|
|
63
|
+
},
|
|
64
|
+
/** The page's record of one frame, registered on first use. */
|
|
65
|
+
frame: (record, frameId) => {
|
|
66
|
+
const page = state(record);
|
|
67
|
+
const existing = page.frames.get(frameId);
|
|
68
|
+
if (existing !== void 0) return existing;
|
|
69
|
+
const frame = { detached: false };
|
|
70
|
+
page.frames.set(frameId, frame);
|
|
71
|
+
return frame;
|
|
72
|
+
},
|
|
73
|
+
/** A frame left its page: Frames issued for it are refused from now on. */
|
|
74
|
+
detachFrame: (pageId, frameId) => {
|
|
75
|
+
const page = pages.get(pageId);
|
|
76
|
+
const frame = page?.frames.get(frameId);
|
|
77
|
+
if (frame !== void 0) frame.detached = true;
|
|
78
|
+
page?.frames.delete(frameId);
|
|
79
|
+
},
|
|
80
|
+
clear: () => {
|
|
81
|
+
pages.clear();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
const issued = /* @__PURE__ */ new WeakMap();
|
|
86
|
+
const canonical = /* @__PURE__ */ new WeakMap();
|
|
87
|
+
/** Registers a session's own capture parent under the session object itself. */
|
|
88
|
+
const issueSession = (session, capture) => {
|
|
89
|
+
issued.set(session, {
|
|
90
|
+
_tag: "Session",
|
|
91
|
+
capture
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
/** The canonical Page for `record`: built by `make` and registered the first time only. */
|
|
95
|
+
const issuePage = (record, make, authority) => {
|
|
96
|
+
const existing = canonical.get(record);
|
|
97
|
+
if (existing !== void 0) return existing;
|
|
98
|
+
const page = make();
|
|
99
|
+
const { controls, capture, pageControl } = authority();
|
|
100
|
+
canonical.set(record, page);
|
|
101
|
+
issued.set(page, {
|
|
102
|
+
_tag: "Page",
|
|
103
|
+
record,
|
|
104
|
+
controls,
|
|
105
|
+
capture,
|
|
106
|
+
pageControl: {
|
|
107
|
+
port: pageControl.port,
|
|
108
|
+
page: Object.freeze({ ...pageControl.page })
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return page;
|
|
112
|
+
};
|
|
113
|
+
/** An issued Frame keeps its own exact controls and the owner of the session that issued it. */
|
|
114
|
+
const issueFrame = (frame, owner, controls) => {
|
|
115
|
+
issued.set(frame, {
|
|
116
|
+
_tag: "Frame",
|
|
117
|
+
owner,
|
|
118
|
+
controls
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
const sessionOwner = (session) => {
|
|
122
|
+
const entry = issued.get(session);
|
|
123
|
+
return entry?._tag === "Session" ? entry.capture.owner : void 0;
|
|
124
|
+
};
|
|
125
|
+
/** The issued Page `page` is, if this exact object was issued by the session owning `owner`. */
|
|
126
|
+
const issuedPageOf = (page, owner) => {
|
|
127
|
+
const entry = issued.get(page);
|
|
128
|
+
return entry?._tag === "Page" && entry.capture.owner === owner ? entry : void 0;
|
|
129
|
+
};
|
|
130
|
+
/** Capture binds only to original issued Page authority; a session association is insufficient. */
|
|
131
|
+
const capturePageParent = (page) => {
|
|
132
|
+
const entry = issued.get(page);
|
|
133
|
+
return entry?._tag === "Page" ? entry.capture : void 0;
|
|
134
|
+
};
|
|
135
|
+
/** The page control an issued Page carries. */
|
|
136
|
+
const pageControl = (page) => {
|
|
137
|
+
const entry = issued.get(page);
|
|
138
|
+
return entry?._tag === "Page" ? entry.pageControl : void 0;
|
|
139
|
+
};
|
|
140
|
+
const unregistered = (operation) => Effect.fail(BrowserError.make({
|
|
141
|
+
operation,
|
|
142
|
+
reason: Reasons.UnregisteredSession.make({}),
|
|
143
|
+
outcome: "undispatched"
|
|
144
|
+
}));
|
|
145
|
+
/** Provider bridges borrow the exact issued page on this session's original owner. */
|
|
146
|
+
const resolvePageControlsForSession = (session, page, operation = "target") => Effect.suspend(() => {
|
|
147
|
+
const owner = sessionOwner(session);
|
|
148
|
+
const entry = owner === void 0 ? void 0 : issuedPageOf(page, owner);
|
|
149
|
+
return entry === void 0 ? unregistered(operation) : entry.controls.validate(operation).pipe(Effect.as(entry.controls));
|
|
150
|
+
});
|
|
151
|
+
/**
|
|
152
|
+
* Tools borrow an exact issued Page, or an exact Frame one of its Pages issued, on this session's
|
|
153
|
+
* original owner. Capture, page control and provider transfers stay Page-only.
|
|
154
|
+
*/
|
|
155
|
+
const resolveTargetControlsForSession = (session, target, operation = "target") => Effect.suspend(() => {
|
|
156
|
+
const entry = issued.get(target);
|
|
157
|
+
if (entry?._tag !== "Frame") return resolvePageControlsForSession(session, target, operation);
|
|
158
|
+
if (sessionOwner(session) !== entry.owner) return unregistered(operation);
|
|
159
|
+
return entry.controls.validate(operation).pipe(Effect.as(entry.controls));
|
|
160
|
+
});
|
|
161
|
+
//#endregion
|
|
162
|
+
export { issuedPageOf as a, resolvePageControlsForSession as c, issueSession as i, resolveTargetControlsForSession as l, issueFrame as n, makePageRegistry as o, issuePage as r, pageControl as s, capturePageParent as t };
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=PageRegistry-B3q9DS3g.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PageRegistry-B3q9DS3g.mjs","names":[],"sources":["../src/internal/browser/PageRegistry.ts"],"sourcesContent":["import { Deferred, Effect } from \"effect\";\n\nimport type { OperationOptions, Page, PageStatus } from \"../../Browser.ts\";\nimport type { PageExecutionState, PageInfo, PageSuspension, Target } from \"../../BrowserData.ts\";\nimport { BrowserError, Reasons, type BrowserOperation } from \"../../Errors.ts\";\nimport type { Terminal } from \"../../TimelineData.ts\";\nimport type { makeRetirement } from \"../timeline/Retirement.ts\";\nimport type { makeStore } from \"../timeline/Store.ts\";\nimport type { AdmissionLane } from \"./Admission.ts\";\nimport type { CaptureParent, PageCaptureParent } from \"./Association.ts\";\nimport type { Owner } from \"./Owner.ts\";\nimport type { PageControls } from \"./Session.ts\";\n\n/** One frame of a registered page. Frames issued for a detached one are refused. */\nexport interface FrameRecord {\n readonly detached: boolean;\n}\n\n/** The session's record of one registered page at one connection generation. */\nexport interface PageRecord {\n readonly identity: Target;\n readonly admission: AdmissionLane;\n readonly info: PageInfo;\n readonly frames: ReadonlyMap<string, FrameRecord>;\n readonly phase: \"open\" | \"paused\" | \"closing\" | \"closed\";\n readonly containment: PageStatus[\"containment\"];\n readonly store: ReturnType<typeof makeStore>;\n readonly terminal: Terminal | null;\n readonly retirement: ReturnType<typeof makeRetirement>;\n /** The native closure in flight for this page, settled true once it closed. */\n readonly attempt?: Deferred.Deferred<boolean>;\n}\n\ninterface FrameState {\n detached: boolean;\n}\n\ninterface RecordState {\n readonly identity: Target;\n readonly admission: AdmissionLane;\n readonly info: PageInfo;\n readonly frames: Map<string, FrameState>;\n phase: PageRecord[\"phase\"];\n containment: PageStatus[\"containment\"];\n readonly store: ReturnType<typeof makeStore>;\n readonly terminal: Terminal | null;\n readonly retirement: ReturnType<typeof makeRetirement>;\n attempt?: Deferred.Deferred<boolean>;\n}\n\n/**\n * The session's registered pages at their connection generations. Callers read the records; only\n * these transitions change them.\n */\nexport const makePageRegistry = () => {\n const pages = new Map<string, RecordState>();\n // Every record this registry made, by identity, including ones it no longer lists.\n const owned = new WeakMap<PageRecord, RecordState>();\n\n const state = (record: PageRecord): RecordState => {\n const found = owned.get(record);\n\n if (found === undefined) throw new TypeError(\"Page record belongs to another registry\");\n\n return found;\n };\n\n return {\n get: (pageId: string): PageRecord | undefined => pages.get(pageId),\n records: (): ReadonlyArray<PageRecord> => [...pages.values()],\n /** Registers a page at a new generation, replacing any record of an earlier one. */\n add: (init: {\n readonly identity: Target;\n readonly admission: AdmissionLane;\n readonly info: PageInfo;\n readonly paused: boolean;\n readonly store: ReturnType<typeof makeStore>;\n readonly retirement: ReturnType<typeof makeRetirement>;\n readonly terminal: () => Terminal | null;\n }): PageRecord => {\n const record: RecordState = {\n identity: init.identity,\n admission: init.admission,\n info: init.info,\n frames: new Map(),\n phase: init.paused ? \"paused\" : \"open\",\n containment: { _tag: \"NotRequired\" },\n store: init.store,\n retirement: init.retirement,\n get terminal() {\n return init.terminal();\n },\n };\n\n owned.set(record, record);\n pages.set(init.info.pageId, record);\n\n return record;\n },\n /** A popup/dialog policy holds an open page for its operator. */\n pause: (pageId: string) => {\n const page = pages.get(pageId);\n\n if (page !== undefined && page.phase === \"open\") page.phase = \"paused\";\n },\n /** The page is being closed: its issued authority refuses new work. */\n closing: (pageId: string) => {\n const page = pages.get(pageId);\n\n if (page !== undefined && page.phase !== \"closed\") page.phase = \"closing\";\n },\n /** The page closed: it settles its closure attempt and leaves the registry. */\n closed: (pageId: string) => {\n const page = pages.get(pageId);\n\n if (page === undefined) return;\n page.phase = \"closed\";\n if (page.attempt !== undefined) Deferred.doneUnsafe(page.attempt, Effect.succeed(true));\n pages.delete(pageId);\n },\n /** Records how an unknown outcome on this page was contained. */\n contain: (record: PageRecord, containment: PageStatus[\"containment\"]) => {\n state(record).containment = containment;\n },\n /** The native closure now in flight for this page. */\n attempting: (record: PageRecord, attempt: Deferred.Deferred<boolean>) => {\n state(record).attempt = attempt;\n },\n /** The page's record of one frame, registered on first use. */\n frame: (record: PageRecord, frameId: string): FrameRecord => {\n const page = state(record);\n const existing = page.frames.get(frameId);\n\n if (existing !== undefined) return existing;\n const frame: FrameState = { detached: false };\n\n page.frames.set(frameId, frame);\n\n return frame;\n },\n /** A frame left its page: Frames issued for it are refused from now on. */\n detachFrame: (pageId: string, frameId: string) => {\n const page = pages.get(pageId);\n const frame = page?.frames.get(frameId);\n\n if (frame !== undefined) frame.detached = true;\n page?.frames.delete(frameId);\n },\n clear: () => {\n pages.clear();\n },\n };\n};\n\nexport type PageRegistry = ReturnType<typeof makePageRegistry>;\n\nexport interface PageControlPort {\n readonly state: (\n page: PageInfo,\n options?: OperationOptions,\n ) => Effect.Effect<PageExecutionState, BrowserError>;\n readonly suspend: (\n page: PageInfo,\n options?: OperationOptions,\n ) => Effect.Effect<PageSuspension, BrowserError>;\n readonly resume: (\n receipt: PageSuspension,\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n}\n\nexport interface PageControlAssociation {\n readonly port: PageControlPort;\n readonly page: PageInfo;\n}\n\n/** What one issued Page carries: its record, controls, capture parent and page control. */\nexport interface IssuedPage {\n readonly record: PageRecord;\n readonly controls: PageControls;\n readonly capture: PageCaptureParent;\n readonly pageControl: PageControlAssociation;\n}\n\ntype Issued =\n | { readonly _tag: \"Session\"; readonly capture: CaptureParent }\n | ({ readonly _tag: \"Page\" } & IssuedPage)\n | { readonly _tag: \"Frame\"; readonly owner: Owner; readonly controls: PageControls };\n\n// This is a private capability registry, not stored domain data. Exact live identity is required:\n// spreading or cloning a session, Page or Frame must not copy authority over its owner, capture\n// leases, reservations or page control. A public or enumerable field would weaken that boundary.\n// Schemas describe data, never this mutable ownership state.\nconst issued = new WeakMap<object, Issued>();\n// One canonical Page per page record: issuing the same record again returns the same object.\nconst canonical = new WeakMap<PageRecord, Page>();\n\n/** Registers a session's own capture parent under the session object itself. */\nexport const issueSession = (session: object, capture: CaptureParent): void => {\n issued.set(session, { _tag: \"Session\", capture });\n};\n\n/** The canonical Page for `record`: built by `make` and registered the first time only. */\nexport const issuePage = (\n record: PageRecord,\n make: () => Page,\n authority: () => Omit<IssuedPage, \"record\">,\n): Page => {\n const existing = canonical.get(record);\n\n if (existing !== undefined) return existing;\n const page = make();\n const { controls, capture, pageControl } = authority();\n\n canonical.set(record, page);\n issued.set(page, {\n _tag: \"Page\",\n record,\n controls,\n capture,\n pageControl: { port: pageControl.port, page: Object.freeze({ ...pageControl.page }) },\n });\n\n return page;\n};\n\n/** An issued Frame keeps its own exact controls and the owner of the session that issued it. */\nexport const issueFrame = (frame: object, owner: Owner, controls: PageControls): void => {\n issued.set(frame, { _tag: \"Frame\", owner, controls });\n};\n\nconst sessionOwner = (session: object): Owner | undefined => {\n const entry = issued.get(session);\n\n return entry?._tag === \"Session\" ? entry.capture.owner : undefined;\n};\n\n/** The issued Page `page` is, if this exact object was issued by the session owning `owner`. */\nexport const issuedPageOf = (page: object, owner: Owner): IssuedPage | undefined => {\n const entry = issued.get(page);\n\n return entry?._tag === \"Page\" && entry.capture.owner === owner ? entry : undefined;\n};\n\n/** Capture binds only to original issued Page authority; a session association is insufficient. */\nexport const capturePageParent = (page: object): PageCaptureParent | undefined => {\n const entry = issued.get(page);\n\n return entry?._tag === \"Page\" ? entry.capture : undefined;\n};\n\n/** The page control an issued Page carries. */\nexport const pageControl = (page: object): PageControlAssociation | undefined => {\n const entry = issued.get(page);\n\n return entry?._tag === \"Page\" ? entry.pageControl : undefined;\n};\n\nconst unregistered = (operation: BrowserOperation) =>\n Effect.fail(\n BrowserError.make({\n operation,\n reason: Reasons.UnregisteredSession.make({}),\n outcome: \"undispatched\",\n }),\n );\n\n/** Provider bridges borrow the exact issued page on this session's original owner. */\nexport const resolvePageControlsForSession = (\n session: object,\n page: object,\n operation: BrowserOperation = \"target\",\n): Effect.Effect<PageControls, BrowserError> =>\n Effect.suspend(() => {\n const owner = sessionOwner(session);\n const entry = owner === undefined ? undefined : issuedPageOf(page, owner);\n\n return entry === undefined\n ? unregistered(operation)\n : entry.controls.validate(operation).pipe(Effect.as(entry.controls));\n });\n\n/**\n * Tools borrow an exact issued Page, or an exact Frame one of its Pages issued, on this session's\n * original owner. Capture, page control and provider transfers stay Page-only.\n */\nexport const resolveTargetControlsForSession = (\n session: object,\n target: object,\n operation: BrowserOperation = \"target\",\n): Effect.Effect<PageControls, BrowserError> =>\n Effect.suspend(() => {\n const entry = issued.get(target);\n\n if (entry?._tag !== \"Frame\") return resolvePageControlsForSession(session, target, operation);\n if (sessionOwner(session) !== entry.owner) return unregistered(operation);\n\n return entry.controls.validate(operation).pipe(Effect.as(entry.controls));\n });\n"],"mappings":"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"}
|