@modelprofile.com/browser-runtime 3.1.1 → 4.0.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/changelog.md +27 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/actions.js +5 -2
- package/dist_ts/classes.artifactstore.d.ts +4 -1
- package/dist_ts/classes.artifactstore.js +42 -11
- package/dist_ts/classes.runtime.d.ts +8 -5
- package/dist_ts/classes.runtime.js +172 -64
- package/dist_ts/classes.runtimeownership.d.ts +102 -0
- package/dist_ts/classes.runtimeownership.js +1230 -0
- package/dist_ts/interfaces.d.ts +2 -0
- package/dist_ts/internal.testing.d.ts +2 -0
- package/dist_ts/internal.testing.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +4 -3
- package/readme.hints.md +8 -0
- package/readme.md +25 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/actions.ts +9 -1
- package/ts/classes.artifactstore.ts +51 -14
- package/ts/classes.runtime.ts +172 -61
- package/ts/classes.runtimeownership.ts +1393 -0
- package/ts/interfaces.ts +5 -1
- package/ts/internal.testing.ts +2 -0
- package/ts/plugins.ts +2 -1
package/ts/classes.runtime.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as plugins from './plugins.js';
|
|
|
2
2
|
import { validateAgentAction } from './actions.js';
|
|
3
3
|
import { BrowserArtifactStore } from './classes.artifactstore.js';
|
|
4
4
|
import { BrowserEgressProxy } from './classes.egressproxy.js';
|
|
5
|
+
import { BrowserRuntimeOwnership } from './classes.runtimeownership.js';
|
|
5
6
|
import { runProductionConfinementProbe } from './confinement.js';
|
|
6
7
|
import { BrowserRuntimeError } from './errors.js';
|
|
7
8
|
import {
|
|
@@ -89,6 +90,7 @@ interface INormalizedRuntimeOptions {
|
|
|
89
90
|
maxOutstandingFrames: number;
|
|
90
91
|
frameAcknowledgementTimeoutMs: number;
|
|
91
92
|
egress: NonNullable<IBrowserRuntimeOptions['egress']>;
|
|
93
|
+
ownership?: IBrowserRuntimeTestingOptions['ownership'];
|
|
92
94
|
beforeLeasePublication?: IBrowserRuntimeTestingOptions['beforeLeasePublication'];
|
|
93
95
|
beforeIdleTermination?: IBrowserRuntimeTestingOptions['beforeIdleTermination'];
|
|
94
96
|
beforeFrameFailureTermination?: IBrowserRuntimeTestingOptions['beforeFrameFailureTermination'];
|
|
@@ -233,9 +235,8 @@ const identifierMaximum = 256;
|
|
|
233
235
|
|
|
234
236
|
export class BrowserRuntime {
|
|
235
237
|
private readonly options: INormalizedRuntimeOptions;
|
|
236
|
-
private readonly
|
|
237
|
-
private
|
|
238
|
-
private readonly lockPath: string;
|
|
238
|
+
private readonly ownership: BrowserRuntimeOwnership;
|
|
239
|
+
private profileRoot?: string;
|
|
239
240
|
private readonly artifactStore: BrowserArtifactStore;
|
|
240
241
|
private readonly runtimeAuthorityId = randomId(18);
|
|
241
242
|
private readonly slots = new Map<string, IResourceSlot>();
|
|
@@ -247,11 +248,10 @@ export class BrowserRuntime {
|
|
|
247
248
|
private readonly capabilityMutex = new TransitionMutex();
|
|
248
249
|
private readonly cleanupOperations = new Set<Promise<void>>();
|
|
249
250
|
private readonly framedPeers = new Set<BrowserRuntimeFramedServerPeer>();
|
|
250
|
-
private lockHandle?: plugins.fsPromises.FileHandle;
|
|
251
|
-
private lockHandleClosed = false;
|
|
252
251
|
private startPromise?: Promise<void>;
|
|
253
252
|
private stopPromise?: Promise<void>;
|
|
254
253
|
private stopCleanupPromise?: Promise<void>;
|
|
254
|
+
private localCleanupPending = false;
|
|
255
255
|
private lifecycleState: 'stopped' | 'starting' | 'running' | 'stopping' = 'stopped';
|
|
256
256
|
private lifecycleEpoch = 0;
|
|
257
257
|
private lifecycleController = new AbortController();
|
|
@@ -473,17 +473,21 @@ export class BrowserRuntime {
|
|
|
473
473
|
10_000,
|
|
474
474
|
),
|
|
475
475
|
egress: options.egress ?? {},
|
|
476
|
+
ownership: testingOptions?.ownership,
|
|
476
477
|
beforeLeasePublication: testingOptions?.beforeLeasePublication,
|
|
477
478
|
beforeIdleTermination: testingOptions?.beforeIdleTermination,
|
|
478
479
|
beforeFrameFailureTermination: testingOptions?.beforeFrameFailureTermination,
|
|
479
480
|
afterFrameFailureFence: testingOptions?.afterFrameFailureFence,
|
|
480
481
|
beforeFramedPeerLeasePublication: testingOptions?.beforeFramedPeerLeasePublication,
|
|
481
482
|
};
|
|
482
|
-
this.
|
|
483
|
-
|
|
484
|
-
|
|
483
|
+
this.ownership = new BrowserRuntimeOwnership({
|
|
484
|
+
runtimeDirectory: options.runtimeDirectory,
|
|
485
|
+
uid: environment.uid ?? -1,
|
|
486
|
+
testing: testingOptions?.ownership,
|
|
487
|
+
onOwnershipLost: () => this.handleOwnershipLost(),
|
|
488
|
+
});
|
|
485
489
|
this.artifactStore = new BrowserArtifactStore({
|
|
486
|
-
rootDirectory:
|
|
490
|
+
rootDirectory: plugins.path.join(options.runtimeDirectory, 'artifacts'),
|
|
487
491
|
...(options.artifacts ?? {}),
|
|
488
492
|
});
|
|
489
493
|
}
|
|
@@ -492,6 +496,13 @@ export class BrowserRuntime {
|
|
|
492
496
|
if (this.lifecycleState === 'running') return Promise.resolve();
|
|
493
497
|
if (this.startPromise) return this.startPromise;
|
|
494
498
|
if (this.stopCleanupPromise) return this.stopCleanupPromise.then(() => this.start());
|
|
499
|
+
if (
|
|
500
|
+
this.lifecycleState === 'stopping'
|
|
501
|
+
|| this.localCleanupPending
|
|
502
|
+
|| this.ownership.cleanupPending
|
|
503
|
+
) {
|
|
504
|
+
return this.stop().then(() => this.start());
|
|
505
|
+
}
|
|
495
506
|
this.lifecycleState = 'starting';
|
|
496
507
|
const epoch = ++this.lifecycleEpoch;
|
|
497
508
|
this.lifecycleController = new AbortController();
|
|
@@ -515,7 +526,8 @@ export class BrowserRuntime {
|
|
|
515
526
|
if (this.stopPromise) return this.stopPromise;
|
|
516
527
|
if (
|
|
517
528
|
this.lifecycleState === 'stopped'
|
|
518
|
-
&& !this.
|
|
529
|
+
&& !this.localCleanupPending
|
|
530
|
+
&& !this.ownership.cleanupPending
|
|
519
531
|
&& !this.startPromise
|
|
520
532
|
&& !this.stopCleanupPromise
|
|
521
533
|
) {
|
|
@@ -528,8 +540,13 @@ export class BrowserRuntime {
|
|
|
528
540
|
const startup = this.startPromise;
|
|
529
541
|
const cleanup = (async () => {
|
|
530
542
|
await startup?.catch(() => undefined);
|
|
531
|
-
|
|
532
|
-
|
|
543
|
+
try {
|
|
544
|
+
await this.stopInternal();
|
|
545
|
+
} finally {
|
|
546
|
+
if (!this.localCleanupPending && !this.ownership.cleanupPending) {
|
|
547
|
+
this.lifecycleState = 'stopped';
|
|
548
|
+
}
|
|
549
|
+
}
|
|
533
550
|
})();
|
|
534
551
|
this.stopCleanupPromise = cleanup;
|
|
535
552
|
void cleanup.then(
|
|
@@ -1321,7 +1338,7 @@ export class BrowserRuntime {
|
|
|
1321
1338
|
): Promise<Uint8Array> {
|
|
1322
1339
|
this.requireHuman(lease);
|
|
1323
1340
|
this.requireValidLease(lease);
|
|
1324
|
-
return this.
|
|
1341
|
+
return this.requireArtifactStore().read(
|
|
1325
1342
|
lease.capability.projectId,
|
|
1326
1343
|
lease.capability.browserResourceId,
|
|
1327
1344
|
artifactId,
|
|
@@ -1332,7 +1349,7 @@ export class BrowserRuntime {
|
|
|
1332
1349
|
public async deleteLeaseArtifact(lease: ILeaseRecord, artifactId: string): Promise<void> {
|
|
1333
1350
|
this.requireHuman(lease);
|
|
1334
1351
|
this.requireValidLease(lease);
|
|
1335
|
-
await this.
|
|
1352
|
+
await this.requireArtifactStore().delete(
|
|
1336
1353
|
lease.capability.projectId,
|
|
1337
1354
|
lease.capability.browserResourceId,
|
|
1338
1355
|
artifactId,
|
|
@@ -1360,30 +1377,31 @@ export class BrowserRuntime {
|
|
|
1360
1377
|
if (this.options.environment.uid === undefined || this.options.environment.uid === 0) {
|
|
1361
1378
|
throw new BrowserRuntimeError('FENCED', 'browser runtime requires a non-root uid');
|
|
1362
1379
|
}
|
|
1363
|
-
await
|
|
1364
|
-
|
|
1365
|
-
|
|
1380
|
+
const generation = await this.ownership.acquire();
|
|
1381
|
+
this.profileRoot = generation.profileRoot;
|
|
1382
|
+
try {
|
|
1383
|
+
this.artifactStore.setRootDirectoryForRuntime(generation.artifactRoot);
|
|
1384
|
+
await plugins.fsPromises.mkdir(this.profileRoot, { recursive: false, mode: 0o700 });
|
|
1385
|
+
await this.secureNewPrivateDirectory(this.profileRoot);
|
|
1386
|
+
await this.artifactStore.start();
|
|
1387
|
+
} catch (error) {
|
|
1366
1388
|
try {
|
|
1367
|
-
|
|
1368
|
-
this.
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
|
|
1372
|
-
throw new BrowserRuntimeError('LOCKED');
|
|
1373
|
-
}
|
|
1374
|
-
throw error;
|
|
1389
|
+
await this.ownership.release('startup-failure');
|
|
1390
|
+
this.profileRoot = undefined;
|
|
1391
|
+
} catch {
|
|
1392
|
+
throw new BrowserRuntimeError('FENCED', 'runtime startup cleanup is incomplete');
|
|
1375
1393
|
}
|
|
1394
|
+
throw error;
|
|
1376
1395
|
}
|
|
1377
|
-
await plugins.fsPromises.mkdir(this.profileRoot, { recursive: true, mode: 0o700 });
|
|
1378
|
-
await plugins.fsPromises.chmod(this.profileRoot, 0o700);
|
|
1379
|
-
const profileEntries = await plugins.fsPromises.readdir(this.profileRoot);
|
|
1380
|
-
if (profileEntries.length > 0) {
|
|
1381
|
-
throw new BrowserRuntimeError('FENCED', 'profile root is not empty');
|
|
1382
|
-
}
|
|
1383
|
-
await this.artifactStore.start();
|
|
1384
1396
|
}
|
|
1385
1397
|
|
|
1386
1398
|
private async stopInternal(): Promise<void> {
|
|
1399
|
+
this.localCleanupPending = true;
|
|
1400
|
+
const errors: unknown[] = [];
|
|
1401
|
+
const localErrors: unknown[] = [];
|
|
1402
|
+
if (this.ownership.owned) {
|
|
1403
|
+
await this.ownership.assertCurrentOwnership().catch((error) => errors.push(error));
|
|
1404
|
+
}
|
|
1387
1405
|
const peerResults = await Promise.allSettled(
|
|
1388
1406
|
[...this.framedPeers].map((peer) => peer.close()),
|
|
1389
1407
|
);
|
|
@@ -1402,34 +1420,98 @@ export class BrowserRuntime {
|
|
|
1402
1420
|
while (this.cleanupOperations.size > 0) {
|
|
1403
1421
|
await Promise.allSettled([...this.cleanupOperations]);
|
|
1404
1422
|
}
|
|
1405
|
-
|
|
1423
|
+
localErrors.push(...[...peerResults, ...revokeResults, ...retirementResults]
|
|
1406
1424
|
.filter((result): result is PromiseRejectedResult => result.status === 'rejected')
|
|
1407
|
-
.map((result) => result.reason);
|
|
1425
|
+
.map((result) => result.reason));
|
|
1426
|
+
await this.artifactStore.stop().catch((error) => localErrors.push(error));
|
|
1427
|
+
errors.push(...localErrors);
|
|
1428
|
+
await this.ownership.release('stop').catch((error) => errors.push(error));
|
|
1429
|
+
if (localErrors.length === 0) {
|
|
1430
|
+
this.localCleanupPending = false;
|
|
1431
|
+
this.clearStoppedRuntimeState();
|
|
1432
|
+
}
|
|
1408
1433
|
if (errors.length > 0) {
|
|
1409
1434
|
throw new AggregateError(errors, 'Browser runtime cleanup is incomplete');
|
|
1410
1435
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
this.lockHandleClosed = true;
|
|
1416
|
-
}
|
|
1417
|
-
await plugins.fsPromises.rm(this.lockPath, { force: true });
|
|
1418
|
-
this.lockHandle = undefined;
|
|
1419
|
-
this.lockHandleClosed = false;
|
|
1420
|
-
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
private clearStoppedRuntimeState(): void {
|
|
1439
|
+
this.profileRoot = undefined;
|
|
1421
1440
|
this.slots.clear();
|
|
1441
|
+
this.capabilitiesByDigest.clear();
|
|
1442
|
+
this.capabilitiesById.clear();
|
|
1443
|
+
this.failedRevocations.clear();
|
|
1444
|
+
this.framedPeers.clear();
|
|
1422
1445
|
this.retiredResourceIds.fill(0);
|
|
1423
1446
|
this.retiredResourceKeys.fill(0);
|
|
1424
1447
|
this.startPromise = undefined;
|
|
1425
1448
|
}
|
|
1426
1449
|
|
|
1450
|
+
private async secureNewPrivateDirectory(pathArg: string): Promise<void> {
|
|
1451
|
+
const uid = this.options.environment.uid;
|
|
1452
|
+
if (uid === undefined) throw new BrowserRuntimeError('FENCED');
|
|
1453
|
+
await plugins.fsPromises.chmod(pathArg, 0o700);
|
|
1454
|
+
const handle = await plugins.fsPromises.open(
|
|
1455
|
+
pathArg,
|
|
1456
|
+
plugins.fs.constants.O_RDONLY
|
|
1457
|
+
| plugins.fs.constants.O_DIRECTORY
|
|
1458
|
+
| plugins.fs.constants.O_NOFOLLOW,
|
|
1459
|
+
);
|
|
1460
|
+
try {
|
|
1461
|
+
await handle.chmod(0o700);
|
|
1462
|
+
await plugins.fsPromises.chmod(pathArg, 0o700);
|
|
1463
|
+
const [pathStat, handleStat] = await Promise.all([
|
|
1464
|
+
plugins.fsPromises.lstat(pathArg, { bigint: true }),
|
|
1465
|
+
handle.stat({ bigint: true }),
|
|
1466
|
+
]);
|
|
1467
|
+
if (
|
|
1468
|
+
!pathStat.isDirectory()
|
|
1469
|
+
|| pathStat.isSymbolicLink()
|
|
1470
|
+
|| pathStat.uid !== BigInt(uid)
|
|
1471
|
+
|| (pathStat.mode & 0o777n) !== 0o700n
|
|
1472
|
+
|| pathStat.dev !== handleStat.dev
|
|
1473
|
+
|| pathStat.ino !== handleStat.ino
|
|
1474
|
+
) throw new BrowserRuntimeError('FENCED', 'runtime directory is not private');
|
|
1475
|
+
} finally {
|
|
1476
|
+
await handle.close();
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1427
1480
|
private requireRunning(): void {
|
|
1428
|
-
if (
|
|
1481
|
+
if (
|
|
1482
|
+
this.lifecycleState !== 'running'
|
|
1483
|
+
|| this.lifecycleController.signal.aborted
|
|
1484
|
+
|| !this.ownership.owned
|
|
1485
|
+
) {
|
|
1429
1486
|
throw new BrowserRuntimeError('NOT_RUNNING');
|
|
1430
1487
|
}
|
|
1431
1488
|
}
|
|
1432
1489
|
|
|
1490
|
+
private requireArtifactStore(): BrowserArtifactStore {
|
|
1491
|
+
this.requireRunning();
|
|
1492
|
+
return this.artifactStore;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
private handleOwnershipLost(): void {
|
|
1496
|
+
if (this.lifecycleState === 'stopped' || this.lifecycleState === 'stopping') return;
|
|
1497
|
+
this.lifecycleEpoch += 1;
|
|
1498
|
+
this.lifecycleController.abort(new BrowserRuntimeError('FENCED'));
|
|
1499
|
+
this.lifecycleState = 'stopping';
|
|
1500
|
+
void this.stopAfterOwnershipLoss();
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
private async stopAfterOwnershipLoss(): Promise<void> {
|
|
1504
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1505
|
+
try {
|
|
1506
|
+
await this.stop();
|
|
1507
|
+
return;
|
|
1508
|
+
} catch {
|
|
1509
|
+
if (!this.localCleanupPending && !this.ownership.cleanupPending) return;
|
|
1510
|
+
await Promise.resolve();
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1433
1515
|
private async acquireAgentLease(
|
|
1434
1516
|
slot: IResourceSlot,
|
|
1435
1517
|
capability: TCapabilityRecord,
|
|
@@ -1570,7 +1652,9 @@ export class BrowserRuntime {
|
|
|
1570
1652
|
if (slot.session) return;
|
|
1571
1653
|
this.assertSlotAvailable(slot);
|
|
1572
1654
|
this.reserveLaunch(slot);
|
|
1573
|
-
const
|
|
1655
|
+
const profileRoot = this.profileRoot;
|
|
1656
|
+
if (!profileRoot) throw new BrowserRuntimeError('FENCED');
|
|
1657
|
+
const profileDirectory = plugins.path.join(profileRoot, randomId(24));
|
|
1574
1658
|
let profileCreated = false;
|
|
1575
1659
|
let proxy: BrowserEgressProxy | undefined;
|
|
1576
1660
|
let session: ILiveBrowserSessionLike | undefined;
|
|
@@ -1578,7 +1662,7 @@ export class BrowserRuntime {
|
|
|
1578
1662
|
try {
|
|
1579
1663
|
await plugins.fsPromises.mkdir(profileDirectory, { mode: 0o700 });
|
|
1580
1664
|
profileCreated = true;
|
|
1581
|
-
await
|
|
1665
|
+
await this.secureNewPrivateDirectory(profileDirectory);
|
|
1582
1666
|
proxy = new BrowserEgressProxy({
|
|
1583
1667
|
projectId: slot.projectId,
|
|
1584
1668
|
browserResourceId: slot.browserResourceId,
|
|
@@ -2199,7 +2283,7 @@ export class BrowserRuntime {
|
|
|
2199
2283
|
signal.throwIfAborted();
|
|
2200
2284
|
this.requireValidLease(lease);
|
|
2201
2285
|
if (lease.slot.session !== session) throw new BrowserRuntimeError('ABORTED');
|
|
2202
|
-
const artifact = await this.
|
|
2286
|
+
const artifact = await this.requireArtifactStore().store(
|
|
2203
2287
|
lease.capability.projectId,
|
|
2204
2288
|
lease.capability.browserResourceId,
|
|
2205
2289
|
snapshot.mimeType,
|
|
@@ -2277,6 +2361,7 @@ export class BrowserRuntime {
|
|
|
2277
2361
|
lastError: {
|
|
2278
2362
|
code: truncateString(state.lastError.code, 128),
|
|
2279
2363
|
fatal: state.lastError.fatal,
|
|
2364
|
+
message: truncateString(state.lastError.message, 2048),
|
|
2280
2365
|
...(state.lastError.tabId
|
|
2281
2366
|
? { tabId: truncateString(state.lastError.tabId, 128) }
|
|
2282
2367
|
: {}),
|
|
@@ -2712,26 +2797,52 @@ export class BrowserRuntime {
|
|
|
2712
2797
|
}
|
|
2713
2798
|
return;
|
|
2714
2799
|
}
|
|
2715
|
-
if (event.type === 'error'
|
|
2716
|
-
|
|
2717
|
-
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2718
|
-
} else {
|
|
2719
|
-
this.trackCleanup(this.revokeCapabilityRecord(slot.lease.capability));
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
if (!subscription || subscription.closed) return;
|
|
2723
|
-
if (event.type === 'state') {
|
|
2724
|
-
this.pushToSubscription(subscription, { type: 'state', state: this.resourceState(event.state) });
|
|
2725
|
-
} else {
|
|
2726
|
-
this.pushToSubscription(subscription, {
|
|
2800
|
+
if (event.type === 'error') {
|
|
2801
|
+
const runtimeEvent = {
|
|
2727
2802
|
type: 'error',
|
|
2728
2803
|
error: {
|
|
2729
2804
|
code: truncateString(event.error.code, 128),
|
|
2730
2805
|
fatal: event.error.fatal,
|
|
2806
|
+
message: truncateString(event.error.message, 2048),
|
|
2731
2807
|
...(event.error.tabId ? { tabId: truncateString(event.error.tabId, 128) } : {}),
|
|
2732
2808
|
},
|
|
2733
|
-
}
|
|
2809
|
+
} satisfies TBrowserRuntimeEvent;
|
|
2810
|
+
let fatalLease = event.error.fatal && slot.lease?.capability.state === 'active'
|
|
2811
|
+
? slot.lease
|
|
2812
|
+
: undefined;
|
|
2813
|
+
if (fatalLease) {
|
|
2814
|
+
try {
|
|
2815
|
+
this.requireValidLease(fatalLease);
|
|
2816
|
+
} catch {
|
|
2817
|
+
fatalLease = undefined;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
if (event.error.fatal && !fatalLease) return;
|
|
2821
|
+
if (fatalLease) {
|
|
2822
|
+
const fatalSubscription = subscription
|
|
2823
|
+
&& !subscription.closed
|
|
2824
|
+
&& subscription.lease === fatalLease
|
|
2825
|
+
? subscription
|
|
2826
|
+
: undefined;
|
|
2827
|
+
if (refreshForSession?.lease === fatalLease) {
|
|
2828
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2829
|
+
}
|
|
2830
|
+
this.invalidateCapabilityRecord(fatalLease.capability);
|
|
2831
|
+
try {
|
|
2832
|
+
fatalSubscription?.listener(runtimeEvent);
|
|
2833
|
+
} catch {
|
|
2834
|
+
// Fatal revocation already owns cleanup for a failing listener.
|
|
2835
|
+
} finally {
|
|
2836
|
+
this.trackCleanup(this.revokeCapabilityRecord(fatalLease.capability));
|
|
2837
|
+
}
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2840
|
+
if (!subscription || subscription.closed) return;
|
|
2841
|
+
this.pushToSubscription(subscription, runtimeEvent);
|
|
2842
|
+
return;
|
|
2734
2843
|
}
|
|
2844
|
+
if (!subscription || subscription.closed) return;
|
|
2845
|
+
this.pushToSubscription(subscription, { type: 'state', state: this.resourceState(event.state) });
|
|
2735
2846
|
}
|
|
2736
2847
|
|
|
2737
2848
|
private pushToSubscription(
|