@modelprofile.com/browser-runtime 2.0.0 → 2.1.1
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/changelog.md +18 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.runtime.d.ts +6 -0
- package/dist_ts/classes.runtime.js +187 -37
- package/dist_ts/confinement.js +48 -7
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/interfaces.d.ts +19 -9
- package/package.json +1 -1
- package/readme.hints.md +1 -0
- package/readme.md +3 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.runtime.ts +207 -41
- package/ts/confinement.ts +49 -6
- package/ts/index.ts +1 -0
- package/ts/interfaces.ts +26 -9
package/ts/confinement.ts
CHANGED
|
@@ -28,18 +28,32 @@ const listenOnUnsupportedPort = async (
|
|
|
28
28
|
export const runProductionConfinementProbe = async (
|
|
29
29
|
context: IBrowserConfinementProbeContext,
|
|
30
30
|
): Promise<void> => {
|
|
31
|
+
const initialState = context.session.getState();
|
|
32
|
+
const initialStateJson = JSON.stringify(initialState);
|
|
33
|
+
let probeTabId: string | undefined;
|
|
34
|
+
let probeFailed = false;
|
|
35
|
+
let probeError: unknown;
|
|
31
36
|
let originHits = 0;
|
|
32
37
|
const fixture = await listenOnUnsupportedPort(
|
|
33
38
|
new Set(context.proxy.allowedTargetPorts),
|
|
34
39
|
() => { originHits += 1; },
|
|
35
40
|
);
|
|
36
|
-
const closeFixture = async (): Promise<void> => new Promise((resolve) => {
|
|
37
|
-
fixture.server.close(() => resolve());
|
|
41
|
+
const closeFixture = async (): Promise<void> => new Promise((resolve, reject) => {
|
|
42
|
+
fixture.server.close((error) => error ? reject(error) : resolve());
|
|
38
43
|
});
|
|
39
44
|
try {
|
|
45
|
+
const probeTab = await context.session.createTab(
|
|
46
|
+
{ activate: false },
|
|
47
|
+
{ signal: context.signal },
|
|
48
|
+
);
|
|
49
|
+
probeTabId = probeTab.id;
|
|
40
50
|
const beforeLoopback = context.proxy.getStats();
|
|
41
51
|
await context.session.navigate(
|
|
42
|
-
{
|
|
52
|
+
{
|
|
53
|
+
url: `http://127.0.0.1:${fixture.port}/browser-runtime-probe`,
|
|
54
|
+
tabId: probeTab.id,
|
|
55
|
+
timeoutMs: 3000,
|
|
56
|
+
},
|
|
43
57
|
{ signal: context.signal },
|
|
44
58
|
).catch(() => undefined);
|
|
45
59
|
const afterLoopback = context.proxy.getStats();
|
|
@@ -53,7 +67,11 @@ export const runProductionConfinementProbe = async (
|
|
|
53
67
|
|
|
54
68
|
const beforeInvalid = context.proxy.getStats();
|
|
55
69
|
await context.session.navigate(
|
|
56
|
-
{
|
|
70
|
+
{
|
|
71
|
+
url: 'http://browser-runtime-confinement.invalid/',
|
|
72
|
+
tabId: probeTab.id,
|
|
73
|
+
timeoutMs: 3000,
|
|
74
|
+
},
|
|
57
75
|
{ signal: context.signal },
|
|
58
76
|
).catch(() => undefined);
|
|
59
77
|
const afterInvalid = context.proxy.getStats();
|
|
@@ -73,7 +91,7 @@ export const runProductionConfinementProbe = async (
|
|
|
73
91
|
try { await pc.setLocalDescription(await pc.createOffer()); } catch {}
|
|
74
92
|
setTimeout(() => { pc.close(); resolve({ candidateEmitted: emitted }); }, 1000);
|
|
75
93
|
})`,
|
|
76
|
-
{ timeoutMs: 2500, maxOutputBytes: 1024 },
|
|
94
|
+
{ tabId: probeTab.id, timeoutMs: 2500, maxOutputBytes: 1024 },
|
|
77
95
|
{ signal: context.signal },
|
|
78
96
|
);
|
|
79
97
|
if (
|
|
@@ -84,7 +102,32 @@ export const runProductionConfinementProbe = async (
|
|
|
84
102
|
) {
|
|
85
103
|
throw new BrowserRuntimeError('CONFINEMENT_FAILED');
|
|
86
104
|
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
probeFailed = true;
|
|
107
|
+
probeError = error instanceof Error ? error : new BrowserRuntimeError('CONFINEMENT_FAILED');
|
|
87
108
|
} finally {
|
|
88
|
-
|
|
109
|
+
const cleanupErrors: unknown[] = [];
|
|
110
|
+
if (probeTabId) {
|
|
111
|
+
await context.session.closeTab(probeTabId).catch((error) => cleanupErrors.push(error));
|
|
112
|
+
}
|
|
113
|
+
await closeFixture().catch((error) => cleanupErrors.push(error));
|
|
114
|
+
try {
|
|
115
|
+
if (JSON.stringify(context.session.getState()) !== initialStateJson) {
|
|
116
|
+
cleanupErrors.push(new BrowserRuntimeError('CONFINEMENT_FAILED'));
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
cleanupErrors.push(error);
|
|
120
|
+
}
|
|
121
|
+
if (probeFailed && cleanupErrors.length > 0) {
|
|
122
|
+
throw new AggregateError(
|
|
123
|
+
[probeError, ...cleanupErrors],
|
|
124
|
+
'Browser confinement probe and cleanup failed.',
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (cleanupErrors.length === 1) throw cleanupErrors[0];
|
|
128
|
+
if (cleanupErrors.length > 1) {
|
|
129
|
+
throw new AggregateError(cleanupErrors, 'Browser confinement probe cleanup failed.');
|
|
130
|
+
}
|
|
89
131
|
}
|
|
132
|
+
if (probeFailed) throw probeError;
|
|
90
133
|
};
|
package/ts/index.ts
CHANGED
package/ts/interfaces.ts
CHANGED
|
@@ -104,15 +104,27 @@ export interface IResolvedBrowserFlexCapability {
|
|
|
104
104
|
capabilityToken: string;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
type TReadonlyBrowserCapabilityAuthorizationRequest =
|
|
108
|
+
| Readonly<IBrowserHumanCapabilityBinding>
|
|
109
|
+
| (Omit<Readonly<IBrowserFlexCapabilityBinding>, 'sessionId'> & {
|
|
110
|
+
readonly sessionId: Readonly<IBrowserFlexSessionId>;
|
|
111
|
+
})
|
|
112
|
+
| (Omit<Readonly<IBrowserMcpCapabilityBinding>, 'sessionId'> & {
|
|
113
|
+
readonly sessionId: Readonly<IBrowserOpenCodeSessionId>;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export type TBrowserRuntimeOperationIdentity = TReadonlyBrowserCapabilityAuthorizationRequest & {
|
|
117
|
+
readonly operationId: string;
|
|
118
|
+
readonly capabilityId: string;
|
|
119
|
+
readonly leaseId: string;
|
|
120
|
+
readonly action: string;
|
|
121
|
+
readonly startedAt: number;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type TBrowserRuntimeAuditEvent = TBrowserRuntimeOperationIdentity & {
|
|
125
|
+
readonly phase: 'completed' | 'failed';
|
|
126
|
+
readonly finishedAt: number;
|
|
127
|
+
readonly errorCode?: string;
|
|
116
128
|
};
|
|
117
129
|
|
|
118
130
|
export interface ILiveBrowserSessionLike {
|
|
@@ -207,6 +219,10 @@ export interface IBrowserRuntimeOptions {
|
|
|
207
219
|
request: TBrowserCapabilityAuthorizationRequest,
|
|
208
220
|
signal: AbortSignal,
|
|
209
221
|
): Promise<boolean> | boolean;
|
|
222
|
+
beforeOperation?(
|
|
223
|
+
operation: TBrowserRuntimeOperationIdentity,
|
|
224
|
+
signal: AbortSignal,
|
|
225
|
+
): Promise<void> | void;
|
|
210
226
|
audit?(event: TBrowserRuntimeAuditEvent, signal: AbortSignal): Promise<void> | void;
|
|
211
227
|
maxResources?: number;
|
|
212
228
|
maxResourcesPerProject?: number;
|
|
@@ -218,6 +234,7 @@ export interface IBrowserRuntimeOptions {
|
|
|
218
234
|
capabilityDefaultTtlMs?: number;
|
|
219
235
|
capabilityMaximumTtlMs?: number;
|
|
220
236
|
authorizationTimeoutMs?: number;
|
|
237
|
+
beforeOperationTimeoutMs?: number;
|
|
221
238
|
maxTabsPerResource?: number;
|
|
222
239
|
operationTimeoutMs?: number;
|
|
223
240
|
quiescenceTimeoutMs?: number;
|