@modelprofile.com/browser-runtime 1.0.1 → 2.1.0
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/.smartconfig.json +1 -1
- package/changelog.md +19 -0
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/actions.js +12 -3
- package/dist_ts/classes.artifactstore.d.ts +16 -7
- package/dist_ts/classes.artifactstore.js +167 -88
- package/dist_ts/classes.egressproxy.d.ts +2 -0
- package/dist_ts/classes.egressproxy.js +7 -2
- package/dist_ts/classes.flexprovider.js +4 -2
- package/dist_ts/classes.framed.d.ts +30 -3
- package/dist_ts/classes.framed.js +205 -30
- package/dist_ts/classes.runtime.d.ts +86 -22
- package/dist_ts/classes.runtime.js +1060 -230
- package/dist_ts/errors.d.ts +1 -1
- package/dist_ts/errors.js +6 -3
- package/dist_ts/index.d.ts +2 -2
- package/dist_ts/interfaces.d.ts +119 -47
- package/dist_ts/internal.testing.d.ts +10 -1
- package/dist_ts/internal.testing.js +1 -1
- package/dist_ts/mcp.js +45 -11
- package/package.json +2 -2
- package/readme.hints.md +23 -18
- package/readme.md +72 -101
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/actions.ts +11 -2
- package/ts/classes.artifactstore.ts +239 -107
- package/ts/classes.egressproxy.ts +12 -1
- package/ts/classes.flexprovider.ts +5 -1
- package/ts/classes.framed.ts +246 -38
- package/ts/classes.runtime.ts +1350 -320
- package/ts/errors.ts +8 -2
- package/ts/index.ts +19 -6
- package/ts/interfaces.ts +151 -45
- package/ts/internal.testing.ts +12 -0
- package/ts/mcp.ts +67 -16
package/ts/classes.framed.ts
CHANGED
|
@@ -2,8 +2,15 @@ import * as plugins from './plugins.js';
|
|
|
2
2
|
import { validateAgentAction, validateAgentActionResult } from './actions.js';
|
|
3
3
|
import type { BrowserRuntime, BrowserRuntimeLease } from './classes.runtime.js';
|
|
4
4
|
import { BrowserRuntimeError, type TBrowserRuntimeErrorCode } from './errors.js';
|
|
5
|
+
import {
|
|
6
|
+
browserRuntimeTesting,
|
|
7
|
+
type IBrowserRuntimeFramedClientTestingOptions,
|
|
8
|
+
} from './internal.testing.js';
|
|
5
9
|
import type {
|
|
6
10
|
IAttachTrustedFramedPeerOptions,
|
|
11
|
+
TBrowserCapabilityAuthorizationRequest,
|
|
12
|
+
IBrowserFlexCapabilityBinding,
|
|
13
|
+
IBrowserFlexSessionId,
|
|
7
14
|
IBrowserRuntimeFramedClientOptions,
|
|
8
15
|
IBrowserRuntimeOperationOptions,
|
|
9
16
|
TBrowserAgentAction,
|
|
@@ -190,12 +197,24 @@ class JsonFramedChannel {
|
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
export class BrowserRuntimeFramedServerPeer {
|
|
200
|
+
private readonly trustedProjectId: string;
|
|
201
|
+
private readonly trustedBrowserResourceId: string;
|
|
202
|
+
private readonly trustedAttachmentAuthorityId: string;
|
|
203
|
+
private readonly trustedAttachmentRevision: number;
|
|
204
|
+
private readonly trustedActorId: string;
|
|
193
205
|
private readonly trustedPeerId: string;
|
|
206
|
+
private readonly trustedRole: 'agent';
|
|
207
|
+
private readonly trustedSource: 'flex';
|
|
194
208
|
private readonly trustedScopeId: string;
|
|
195
|
-
private readonly
|
|
209
|
+
private readonly trustedChannelId: string;
|
|
210
|
+
private readonly trustedSessionId: IBrowserFlexSessionId;
|
|
196
211
|
private readonly channel: JsonFramedChannel;
|
|
197
212
|
private readonly activeRequests = new Map<string, AbortController>();
|
|
198
213
|
private lease?: BrowserRuntimeLease;
|
|
214
|
+
private capabilityId?: string;
|
|
215
|
+
private pendingCapabilityId?: string;
|
|
216
|
+
private pendingReleasedLeaseId?: string;
|
|
217
|
+
private runtimeReleasedLease = false;
|
|
199
218
|
private started = false;
|
|
200
219
|
private closePromise?: Promise<void>;
|
|
201
220
|
private closing = false;
|
|
@@ -206,9 +225,39 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
206
225
|
options: IAttachTrustedFramedPeerOptions,
|
|
207
226
|
private readonly onClosed: () => void = () => undefined,
|
|
208
227
|
) {
|
|
209
|
-
this.
|
|
210
|
-
this.
|
|
211
|
-
|
|
228
|
+
this.trustedProjectId = validateBoundedString(options.projectId, 'projectId', 1, 256);
|
|
229
|
+
this.trustedBrowserResourceId = validateBoundedString(
|
|
230
|
+
options.browserResourceId,
|
|
231
|
+
'browserResourceId',
|
|
232
|
+
16,
|
|
233
|
+
256,
|
|
234
|
+
);
|
|
235
|
+
this.trustedAttachmentAuthorityId = validateBoundedString(
|
|
236
|
+
options.attachmentAuthorityId,
|
|
237
|
+
'attachmentAuthorityId',
|
|
238
|
+
1,
|
|
239
|
+
256,
|
|
240
|
+
);
|
|
241
|
+
this.trustedAttachmentRevision = validateInteger(
|
|
242
|
+
options.attachmentRevision,
|
|
243
|
+
'attachmentRevision',
|
|
244
|
+
0,
|
|
245
|
+
Number.MAX_SAFE_INTEGER,
|
|
246
|
+
);
|
|
247
|
+
this.trustedActorId = validateBoundedString(options.actorId, 'actorId', 1, 256);
|
|
248
|
+
this.trustedPeerId = validateBoundedString(options.peerId, 'peerId', 1, 256);
|
|
249
|
+
if (options.role !== 'agent' || options.source !== 'flex') {
|
|
250
|
+
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
251
|
+
}
|
|
252
|
+
this.trustedRole = options.role;
|
|
253
|
+
this.trustedSource = options.source;
|
|
254
|
+
this.trustedScopeId = validateBoundedString(options.scopeId, 'scopeId', 1, 256);
|
|
255
|
+
this.trustedChannelId = validateBoundedString(options.channelId, 'channelId', 1, 256);
|
|
256
|
+
if (options.sessionId?.harnessId !== 'flex') throw new BrowserRuntimeError('INVALID_INPUT');
|
|
257
|
+
this.trustedSessionId = {
|
|
258
|
+
harnessId: 'flex',
|
|
259
|
+
nativeId: validateBoundedString(options.sessionId.nativeId, 'sessionId.nativeId', 1, 256),
|
|
260
|
+
};
|
|
212
261
|
if (
|
|
213
262
|
!(options.readable instanceof plugins.stream.Readable)
|
|
214
263
|
|| !(options.writable instanceof plugins.stream.Writable)
|
|
@@ -229,6 +278,42 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
229
278
|
return this.trustedPeerId;
|
|
230
279
|
}
|
|
231
280
|
|
|
281
|
+
public get projectId(): string {
|
|
282
|
+
return this.trustedProjectId;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public get browserResourceId(): string {
|
|
286
|
+
return this.trustedBrowserResourceId;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public get channelId(): string {
|
|
290
|
+
return this.trustedChannelId;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** @internal */
|
|
294
|
+
public get activeCapabilityId(): string | undefined {
|
|
295
|
+
return this.capabilityId ?? this.pendingCapabilityId;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** @internal */
|
|
299
|
+
public releaseLeaseFromRuntime(capabilityId: string, leaseId: string): void {
|
|
300
|
+
if (
|
|
301
|
+
this.pendingCapabilityId === capabilityId
|
|
302
|
+
&& !this.lease
|
|
303
|
+
) {
|
|
304
|
+
this.pendingReleasedLeaseId = leaseId;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (
|
|
308
|
+
this.capabilityId !== capabilityId
|
|
309
|
+
|| this.lease?.capabilityId !== capabilityId
|
|
310
|
+
|| this.lease.leaseId !== leaseId
|
|
311
|
+
) return;
|
|
312
|
+
this.lease = undefined;
|
|
313
|
+
this.capabilityId = undefined;
|
|
314
|
+
this.runtimeReleasedLease = true;
|
|
315
|
+
}
|
|
316
|
+
|
|
232
317
|
public start(): void {
|
|
233
318
|
if (this.started) return;
|
|
234
319
|
this.started = true;
|
|
@@ -237,7 +322,17 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
237
322
|
|
|
238
323
|
public close(): Promise<void> {
|
|
239
324
|
if (this.closePromise) return this.closePromise;
|
|
240
|
-
this.closePromise = this.closeInternal().catch((error) => {
|
|
325
|
+
this.closePromise = this.closeInternal(true).catch((error) => {
|
|
326
|
+
this.closePromise = undefined;
|
|
327
|
+
throw error;
|
|
328
|
+
});
|
|
329
|
+
return this.closePromise;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** @internal */
|
|
333
|
+
public closeFromRuntime(): Promise<void> {
|
|
334
|
+
if (this.closePromise) return this.closePromise;
|
|
335
|
+
this.closePromise = this.closeInternal(false).catch((error) => {
|
|
241
336
|
this.closePromise = undefined;
|
|
242
337
|
throw error;
|
|
243
338
|
});
|
|
@@ -281,36 +376,77 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
281
376
|
request: Record<string, unknown>,
|
|
282
377
|
controller: AbortController,
|
|
283
378
|
): Promise<void> {
|
|
379
|
+
let acquisitionCapabilityId: string | undefined;
|
|
284
380
|
try {
|
|
285
381
|
let result: unknown;
|
|
286
382
|
if (method === 'acquire') {
|
|
287
383
|
validateExactKeys(request, ['version', 'id', 'method', 'capabilityToken'], 'acquire request');
|
|
288
|
-
if (this.lease) throw new BrowserRuntimeError('BUSY');
|
|
384
|
+
if (this.lease || this.pendingCapabilityId) throw new BrowserRuntimeError('BUSY');
|
|
289
385
|
const capabilityToken = validateBoundedString(
|
|
290
386
|
request.capabilityToken,
|
|
291
387
|
'capabilityToken',
|
|
292
388
|
16,
|
|
293
389
|
512,
|
|
294
390
|
);
|
|
295
|
-
|
|
296
|
-
|
|
391
|
+
const authority = {
|
|
392
|
+
projectId: this.trustedProjectId,
|
|
393
|
+
browserResourceId: this.trustedBrowserResourceId,
|
|
394
|
+
attachmentAuthorityId: this.trustedAttachmentAuthorityId,
|
|
395
|
+
attachmentRevision: this.trustedAttachmentRevision,
|
|
396
|
+
actorId: this.trustedActorId,
|
|
297
397
|
peerId: this.trustedPeerId,
|
|
298
|
-
|
|
299
|
-
|
|
398
|
+
role: this.trustedRole,
|
|
399
|
+
source: this.trustedSource,
|
|
300
400
|
scopeId: this.trustedScopeId,
|
|
401
|
+
channelId: this.trustedChannelId,
|
|
301
402
|
sessionId: this.trustedSessionId,
|
|
403
|
+
} as const;
|
|
404
|
+
const descriptor = this.runtime.authorizeCapability(capabilityToken, authority);
|
|
405
|
+
this.pendingCapabilityId = descriptor.capabilityId;
|
|
406
|
+
acquisitionCapabilityId = descriptor.capabilityId;
|
|
407
|
+
this.pendingReleasedLeaseId = undefined;
|
|
408
|
+
const acquiredLease = await this.runtime.acquireLease({
|
|
409
|
+
capabilityToken,
|
|
410
|
+
...authority,
|
|
302
411
|
signal: controller.signal,
|
|
303
412
|
});
|
|
413
|
+
await this.runtime.beforeFramedPeerLeasePublication();
|
|
414
|
+
this.lease = acquiredLease;
|
|
415
|
+
this.capabilityId = acquiredLease.capabilityId;
|
|
416
|
+
this.runtimeReleasedLease = false;
|
|
417
|
+
const releasedBeforePublication = this.pendingReleasedLeaseId === acquiredLease.leaseId;
|
|
418
|
+
this.pendingCapabilityId = undefined;
|
|
419
|
+
this.pendingReleasedLeaseId = undefined;
|
|
420
|
+
if (releasedBeforePublication) {
|
|
421
|
+
this.lease = undefined;
|
|
422
|
+
this.capabilityId = undefined;
|
|
423
|
+
this.runtimeReleasedLease = true;
|
|
424
|
+
throw new BrowserRuntimeError('CAPABILITY_REVOKED');
|
|
425
|
+
}
|
|
304
426
|
if (this.closing || controller.signal.aborted) {
|
|
305
|
-
await this.
|
|
427
|
+
await this.runtime.revokeFramedCapability(acquiredLease.capabilityId);
|
|
306
428
|
this.lease = undefined;
|
|
429
|
+
this.capabilityId = undefined;
|
|
307
430
|
throw new BrowserRuntimeError('ABORTED');
|
|
308
431
|
}
|
|
432
|
+
try {
|
|
433
|
+
acquiredLease.getState();
|
|
434
|
+
} catch {
|
|
435
|
+
this.lease = undefined;
|
|
436
|
+
this.capabilityId = undefined;
|
|
437
|
+
this.runtimeReleasedLease = true;
|
|
438
|
+
throw new BrowserRuntimeError('CAPABILITY_REVOKED');
|
|
439
|
+
}
|
|
309
440
|
result = {
|
|
310
|
-
leaseId:
|
|
311
|
-
role:
|
|
441
|
+
leaseId: acquiredLease.leaseId,
|
|
442
|
+
role: acquiredLease.role,
|
|
443
|
+
projectId: this.trustedProjectId,
|
|
444
|
+
browserResourceId: this.trustedBrowserResourceId,
|
|
445
|
+
attachmentAuthorityId: this.trustedAttachmentAuthorityId,
|
|
446
|
+
attachmentRevision: this.trustedAttachmentRevision,
|
|
312
447
|
scopeId: this.trustedScopeId,
|
|
313
|
-
|
|
448
|
+
channelId: this.trustedChannelId,
|
|
449
|
+
sessionId: { ...this.trustedSessionId },
|
|
314
450
|
};
|
|
315
451
|
} else if (method === 'execute') {
|
|
316
452
|
validateExactKeys(request, ['version', 'id', 'method', 'action'], 'execute request');
|
|
@@ -335,9 +471,14 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
335
471
|
result = { cancelled };
|
|
336
472
|
} else if (method === 'release') {
|
|
337
473
|
validateExactKeys(request, ['version', 'id', 'method'], 'release request');
|
|
338
|
-
|
|
339
|
-
|
|
474
|
+
if (!this.lease) {
|
|
475
|
+
if (!this.runtimeReleasedLease) throw new BrowserRuntimeError('CAPABILITY_INVALID');
|
|
476
|
+
} else {
|
|
477
|
+
await this.runtime.revokeFramedCapability(this.capabilityId!);
|
|
478
|
+
}
|
|
340
479
|
this.lease = undefined;
|
|
480
|
+
this.capabilityId = undefined;
|
|
481
|
+
this.runtimeReleasedLease = false;
|
|
341
482
|
result = { released: true };
|
|
342
483
|
} else {
|
|
343
484
|
throw new BrowserRuntimeError('PROTOCOL_ERROR');
|
|
@@ -345,6 +486,10 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
345
486
|
assertJsonWithoutBytes(result, hardMaxFrameBytes);
|
|
346
487
|
await this.channel.send({ version: protocolVersion, id, ok: true, result });
|
|
347
488
|
} catch (error) {
|
|
489
|
+
if (acquisitionCapabilityId && this.pendingCapabilityId === acquisitionCapabilityId) {
|
|
490
|
+
this.pendingCapabilityId = undefined;
|
|
491
|
+
this.pendingReleasedLeaseId = undefined;
|
|
492
|
+
}
|
|
348
493
|
await this.sendError(id, error);
|
|
349
494
|
}
|
|
350
495
|
}
|
|
@@ -366,21 +511,23 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
366
511
|
} satisfies IFramedErrorResponse).catch(() => undefined);
|
|
367
512
|
}
|
|
368
513
|
|
|
369
|
-
private async closeInternal(): Promise<void> {
|
|
514
|
+
private async closeInternal(revokeCapability: boolean): Promise<void> {
|
|
370
515
|
this.closing = true;
|
|
371
516
|
for (const controller of this.activeRequests.values()) {
|
|
372
517
|
controller.abort(new BrowserRuntimeError('ABORTED'));
|
|
373
518
|
}
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
);
|
|
378
|
-
if (!dispatchResult.settled) {
|
|
379
|
-
await this.runtime.disconnectPeer(this.trustedPeerId);
|
|
519
|
+
const capabilityId = this.capabilityId;
|
|
520
|
+
if (revokeCapability && capabilityId) {
|
|
521
|
+
await this.runtime.revokeFramedCapability(capabilityId);
|
|
380
522
|
}
|
|
381
|
-
await this.lease?.release();
|
|
382
523
|
this.lease = undefined;
|
|
383
|
-
|
|
524
|
+
this.capabilityId = undefined;
|
|
525
|
+
if (revokeCapability) {
|
|
526
|
+
await waitBounded(
|
|
527
|
+
Promise.allSettled([...this.dispatches]).then(() => undefined),
|
|
528
|
+
hardRequestTimeoutMs,
|
|
529
|
+
);
|
|
530
|
+
}
|
|
384
531
|
await this.channel.close();
|
|
385
532
|
this.onClosed();
|
|
386
533
|
}
|
|
@@ -392,13 +539,10 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
392
539
|
for (const controller of this.activeRequests.values()) {
|
|
393
540
|
controller.abort(new BrowserRuntimeError('ABORTED'));
|
|
394
541
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
hardRequestTimeoutMs,
|
|
398
|
-
);
|
|
399
|
-
await this.lease?.release();
|
|
542
|
+
const capabilityId = this.capabilityId;
|
|
543
|
+
if (capabilityId) await this.runtime.revokeFramedCapability(capabilityId);
|
|
400
544
|
this.lease = undefined;
|
|
401
|
-
|
|
545
|
+
this.capabilityId = undefined;
|
|
402
546
|
this.onClosed();
|
|
403
547
|
})().catch((error) => {
|
|
404
548
|
this.closePromise = undefined;
|
|
@@ -417,11 +561,48 @@ export class BrowserRuntimeFramedClient {
|
|
|
417
561
|
private started = false;
|
|
418
562
|
private closePromise?: Promise<void>;
|
|
419
563
|
private readonly scopeIdValue: string;
|
|
420
|
-
private readonly
|
|
564
|
+
private readonly channelIdValue: string;
|
|
565
|
+
private readonly sessionIdValue: IBrowserFlexSessionId;
|
|
566
|
+
private readonly authority: TBrowserCapabilityAuthorizationRequest;
|
|
567
|
+
private readonly afterAcquireResponse?: IBrowserRuntimeFramedClientTestingOptions['afterAcquireResponse'];
|
|
421
568
|
|
|
422
|
-
constructor(options: IBrowserRuntimeFramedClientOptions) {
|
|
423
|
-
|
|
424
|
-
|
|
569
|
+
constructor(options: IBrowserRuntimeFramedClientOptions | IBrowserRuntimeFramedClientTestingOptions) {
|
|
570
|
+
if (options.role !== 'agent' || options.source !== 'flex' || options.sessionId?.harnessId !== 'flex') {
|
|
571
|
+
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
572
|
+
}
|
|
573
|
+
this.scopeIdValue = validateBoundedString(options.scopeId, 'scopeId', 1, 256);
|
|
574
|
+
this.channelIdValue = validateBoundedString(options.channelId, 'channelId', 1, 256);
|
|
575
|
+
this.sessionIdValue = {
|
|
576
|
+
harnessId: 'flex',
|
|
577
|
+
nativeId: validateBoundedString(options.sessionId.nativeId, 'sessionId.nativeId', 1, 256),
|
|
578
|
+
};
|
|
579
|
+
this.authority = {
|
|
580
|
+
projectId: validateBoundedString(options.projectId, 'projectId', 1, 256),
|
|
581
|
+
browserResourceId: validateBoundedString(options.browserResourceId, 'browserResourceId', 16, 256),
|
|
582
|
+
attachmentAuthorityId: validateBoundedString(
|
|
583
|
+
options.attachmentAuthorityId,
|
|
584
|
+
'attachmentAuthorityId',
|
|
585
|
+
1,
|
|
586
|
+
256,
|
|
587
|
+
),
|
|
588
|
+
attachmentRevision: validateInteger(
|
|
589
|
+
options.attachmentRevision,
|
|
590
|
+
'attachmentRevision',
|
|
591
|
+
0,
|
|
592
|
+
Number.MAX_SAFE_INTEGER,
|
|
593
|
+
),
|
|
594
|
+
actorId: validateBoundedString(options.actorId, 'actorId', 1, 256),
|
|
595
|
+
role: 'agent',
|
|
596
|
+
peerId: validateBoundedString(options.peerId, 'peerId', 1, 256),
|
|
597
|
+
source: 'flex',
|
|
598
|
+
scopeId: this.scopeIdValue,
|
|
599
|
+
channelId: this.channelIdValue,
|
|
600
|
+
sessionId: this.sessionIdValue,
|
|
601
|
+
};
|
|
602
|
+
this.afterAcquireResponse = browserRuntimeTesting in options
|
|
603
|
+
&& options[browserRuntimeTesting] === true
|
|
604
|
+
? options.afterAcquireResponse
|
|
605
|
+
: undefined;
|
|
425
606
|
if (
|
|
426
607
|
!(options.readable instanceof plugins.stream.Readable)
|
|
427
608
|
|| !(options.writable instanceof plugins.stream.Writable)
|
|
@@ -462,12 +643,22 @@ export class BrowserRuntimeFramedClient {
|
|
|
462
643
|
return this.scopeIdValue;
|
|
463
644
|
}
|
|
464
645
|
|
|
465
|
-
public get
|
|
466
|
-
return this.
|
|
646
|
+
public get channelId(): string {
|
|
647
|
+
return this.channelIdValue;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
public get sessionId(): IBrowserFlexSessionId {
|
|
651
|
+
return { ...this.sessionIdValue };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
public get authorityBinding(): TBrowserCapabilityAuthorizationRequest {
|
|
655
|
+
return {
|
|
656
|
+
...(this.authority as IBrowserFlexCapabilityBinding),
|
|
657
|
+
sessionId: { ...this.sessionIdValue },
|
|
658
|
+
};
|
|
467
659
|
}
|
|
468
660
|
|
|
469
661
|
public async acquire(capabilityTokenArg: string, signal?: AbortSignal): Promise<void> {
|
|
470
|
-
if (this.acquired) throw new BrowserRuntimeError('BUSY');
|
|
471
662
|
signal?.throwIfAborted();
|
|
472
663
|
const capabilityToken = validateBoundedString(
|
|
473
664
|
capabilityTokenArg,
|
|
@@ -482,10 +673,27 @@ export class BrowserRuntimeFramedClient {
|
|
|
482
673
|
void this.request('cancel', { targetRequestId: id }).catch(() => undefined);
|
|
483
674
|
};
|
|
484
675
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
676
|
+
let acquiredRemotely = false;
|
|
485
677
|
try {
|
|
486
678
|
await acquisition;
|
|
679
|
+
acquiredRemotely = true;
|
|
680
|
+
await this.afterAcquireResponse?.();
|
|
487
681
|
signal?.throwIfAborted();
|
|
488
682
|
this.acquired = true;
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (acquiredRemotely) {
|
|
685
|
+
let cleanupError: unknown;
|
|
686
|
+
await this.request('release', {}).catch(async (releaseError) => {
|
|
687
|
+
cleanupError = releaseError;
|
|
688
|
+
await this.channel.close().catch(() => undefined);
|
|
689
|
+
this.handleClose();
|
|
690
|
+
});
|
|
691
|
+
this.acquired = false;
|
|
692
|
+
if (cleanupError) {
|
|
693
|
+
throw new AggregateError([error, cleanupError], 'Framed acquisition cleanup is incomplete');
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
throw error;
|
|
489
697
|
} finally {
|
|
490
698
|
signal?.removeEventListener('abort', onAbort);
|
|
491
699
|
}
|