@modelprofile.com/browser-runtime 5.0.0 → 5.2.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 +23 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.artifactstore.d.ts +4 -3
- package/dist_ts/classes.artifactstore.js +19 -5
- package/dist_ts/classes.runtime.d.ts +26 -4
- package/dist_ts/classes.runtime.js +183 -38
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/interfaces.d.ts +14 -1
- package/package.json +4 -5
- package/readme.md +12 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.artifactstore.ts +19 -1
- package/ts/classes.runtime.ts +191 -36
- package/ts/index.ts +2 -0
- package/ts/interfaces.ts +17 -0
- package/readme.hints.md +0 -60
package/dist_ts/index.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export { BrowserRuntimeError } from './errors.js';
|
|
|
8
8
|
export { createBrowserRuntimeMcpHttpHandler } from './mcp.js';
|
|
9
9
|
export type { TAcquireBrowserRuntimeLeaseOptions, IBrowserRuntimeLeaseRenewalOptions, } from './classes.runtime.js';
|
|
10
10
|
export type { TBrowserRuntimeErrorCode } from './errors.js';
|
|
11
|
-
export type { IAttachTrustedFramedPeerOptions, IApplyBrowserAttachmentBindingRequest, IBrowserAttachmentBinding, IBrowserActionStateResult, IBrowserArtifactMetadata, IBrowserArtifactStoreOptions, IBrowserMcpAuthenticatedBinding, TBrowserCapabilityAuthorizationRequest, TBrowserCapabilityDescriptor, IBrowserClickAction, IBrowserEgressConnectionOptions, IBrowserEgressProxyOptions, IBrowserEgressProxyStats, IBrowserFlexCapabilityBinding, IBrowserFlexSessionId, IBrowserCodexSessionId, IBrowserFillAction, IBrowserNavigateAction, IBrowserObservationResult, IBrowserOpenCodeSessionId, IBrowserPressAction, TBrowserRuntimeAuditEvent, TBrowserRuntimeLeaseAuthority, TBrowserRuntimeOperationClassification, TBrowserRuntimeOperationIdentity, IBrowserRuntimeFlexToolProviderOptions, IBrowserRuntimeFrameSubscription, IBrowserRuntimeFramedClientOptions, IBrowserRuntimeMcpOptions, IBrowserRuntimeOperationOptions, IBrowserRuntimeOptions, IBrowserRuntimeScreencastOptions, IBrowserResourceKey, IBrowserResourceRegistration, IBrowserRuntimeState, IBrowserRuntimeViewportResult, IBrowserRuntimeTabState, IBrowserScreenshotAction, IBrowserScreenshotResult, IBrowserSnapshotAction, ICreateBrowserResourceRequest, TIssueBrowserCapabilityRequest, TIssuedBrowserCapability, TQualifiedBrowserSessionId, IRegisterBrowserResourceRequest, IResolvedBrowserFlexCapability, TBrowserActorRole, TBrowserAgentAction, TBrowserAgentActionResult, TBrowserCapabilitySource, TBrowserDnsResolver, TBrowserEgressConnector, TBrowserHarnessId, TBrowserRuntimeEvent, } from './interfaces.js';
|
|
11
|
+
export type { IAttachTrustedFramedPeerOptions, IApplyBrowserAttachmentBindingRequest, IBrowserAttachmentBinding, IBrowserActionStateResult, IBrowserArtifactMetadata, IBrowserArtifactStoreOptions, IBrowserMcpAuthenticatedBinding, TBrowserCapabilityAuthorizationRequest, TBrowserCapabilityDescriptor, IBrowserClickAction, IBrowserEgressConnectionOptions, IBrowserEgressProxyOptions, IBrowserEgressProxyStats, IBrowserFlexCapabilityBinding, IBrowserFlexSessionId, IBrowserCodexSessionId, IBrowserFillAction, IBrowserNavigateAction, IBrowserObservationResult, IBrowserOpenCodeSessionId, IBrowserPressAction, TBrowserRuntimeAuditEvent, TBrowserRuntimeLeaseAuthority, TBrowserRuntimeOperationClassification, TBrowserRuntimeOperationIdentity, IBrowserRuntimeFlexToolProviderOptions, IBrowserRuntimeFrameSubscription, IBrowserRuntimeFramedClientOptions, IBrowserRuntimeMcpOptions, IBrowserRuntimeOperationOptions, IBrowserRuntimeOptions, IBrowserRuntimeScreencastOptions, IBrowserRuntimeEventSubscriptionOptions, TBrowserRuntimeVideoOffer, IBrowserResourceKey, IBrowserResourceRegistration, IBrowserRuntimeState, IBrowserRuntimeViewportResult, IBrowserRuntimeTabState, IBrowserScreenshotAction, IBrowserScreenshotResult, IBrowserSnapshotAction, ICreateBrowserResourceRequest, TIssueBrowserCapabilityRequest, TIssuedBrowserCapability, TQualifiedBrowserSessionId, IRegisterBrowserResourceRequest, IResolvedBrowserFlexCapability, TBrowserActorRole, TBrowserAgentAction, TBrowserAgentActionResult, TBrowserCapabilitySource, TBrowserDnsResolver, TBrowserEgressConnector, TBrowserHarnessId, TBrowserRuntimeEvent, } from './interfaces.js';
|
package/dist_ts/interfaces.d.ts
CHANGED
|
@@ -101,7 +101,7 @@ export type TBrowserRuntimeOperationIdentity = TReadonlyBrowserCapabilityAuthori
|
|
|
101
101
|
readonly classification: TBrowserRuntimeOperationClassification;
|
|
102
102
|
readonly startedAt: number;
|
|
103
103
|
};
|
|
104
|
-
export type TBrowserRuntimeOperationClassification = 'raw-input' | 'frame-stream' | 'viewport' | 'navigation' | 'tab' | 'agent-action';
|
|
104
|
+
export type TBrowserRuntimeOperationClassification = 'raw-input' | 'frame-stream' | 'video-peer' | 'viewport' | 'navigation' | 'tab' | 'agent-action';
|
|
105
105
|
/** The viewport accepted by the shared resource, which may be smaller than one viewer's request. */
|
|
106
106
|
export interface IBrowserRuntimeViewportResult {
|
|
107
107
|
viewport: plugins.smartpuppeteer.ILiveBrowserViewport;
|
|
@@ -126,6 +126,11 @@ export interface ILiveBrowserSessionLike {
|
|
|
126
126
|
getState(): plugins.smartpuppeteer.ILiveBrowserState;
|
|
127
127
|
getProcessState(): plugins.smartpuppeteer.ILiveBrowserProcessState;
|
|
128
128
|
onEvent(listener: plugins.smartpuppeteer.TLiveBrowserEventListener): () => void;
|
|
129
|
+
setFrameCaptureEnabled(enabled: boolean, options?: plugins.smartpuppeteer.ILiveBrowserOperationOptions): Promise<void>;
|
|
130
|
+
openVideoPeer(peerId: string, options?: plugins.smartpuppeteer.ILiveBrowserOperationOptions): Promise<plugins.smartpuppeteer.ILiveVideoOffer>;
|
|
131
|
+
answerVideoPeer(peerId: string, negotiationId: string, description: plugins.smartpuppeteer.ILiveVideoDescription, options?: plugins.smartpuppeteer.ILiveBrowserOperationOptions): Promise<void>;
|
|
132
|
+
closeVideoPeer(peerId: string): Promise<void>;
|
|
133
|
+
getVideoStatistics(peerId: string): Promise<plugins.smartpuppeteer.ILiveVideoStatistics>;
|
|
129
134
|
refreshScreencast(options?: plugins.smartpuppeteer.ILiveBrowserOperationOptions): Promise<plugins.smartpuppeteer.ILiveBrowserFrameIdentity>;
|
|
130
135
|
acknowledgeFrame(request: plugins.smartpuppeteer.ILiveBrowserFrameAcknowledgementRequest): Promise<plugins.smartpuppeteer.ILiveBrowserFrameAcknowledgement>;
|
|
131
136
|
createTab(options?: plugins.smartpuppeteer.ILiveBrowserCreateTabOptions, operationOptions?: plugins.smartpuppeteer.ILiveBrowserOperationOptions): Promise<plugins.smartpuppeteer.ILiveBrowserTabState>;
|
|
@@ -193,6 +198,8 @@ export interface IBrowserRuntimeOptions {
|
|
|
193
198
|
maxOutstandingFrames?: number;
|
|
194
199
|
frameAcknowledgementTimeoutMs?: number;
|
|
195
200
|
screencast?: IBrowserRuntimeScreencastOptions;
|
|
201
|
+
/** Trusted host configuration; defaults to direct ICE and automatic GPU support. */
|
|
202
|
+
video?: plugins.smartpuppeteer.ILiveVideoOptions;
|
|
196
203
|
egress?: Omit<IBrowserEgressProxyOptions, 'projectId' | 'browserResourceId'>;
|
|
197
204
|
artifacts?: Omit<IBrowserArtifactStoreOptions, 'rootDirectory'>;
|
|
198
205
|
}
|
|
@@ -206,7 +213,13 @@ export interface IBrowserRuntimeTabState {
|
|
|
206
213
|
appliedViewportRevision: number;
|
|
207
214
|
streaming: boolean;
|
|
208
215
|
}
|
|
216
|
+
export interface IBrowserRuntimeEventSubscriptionOptions {
|
|
217
|
+
/** Set false for native video viewers. Defaults to true for image-frame consumers. */
|
|
218
|
+
includeFrames?: boolean;
|
|
219
|
+
}
|
|
220
|
+
export type TBrowserRuntimeVideoOffer = Omit<plugins.smartpuppeteer.ILiveVideoOffer, 'peerId'>;
|
|
209
221
|
export interface IBrowserRuntimeState {
|
|
222
|
+
videoAcceleration?: plugins.smartpuppeteer.ILiveVideoAcceleration;
|
|
210
223
|
status: 'stopped' | 'starting' | 'running' | 'stopping';
|
|
211
224
|
activeTabId: string | null;
|
|
212
225
|
viewportRevision: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelprofile.com/browser-runtime",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"@push.rocks/smartagent": "^6.0.0",
|
|
31
31
|
"@push.rocks/smartipc": "^2.6.0",
|
|
32
32
|
"@push.rocks/smartmcp": "^0.3.0",
|
|
33
|
-
"@push.rocks/smartpuppeteer": "^2.
|
|
33
|
+
"@push.rocks/smartpuppeteer": "^2.8.1",
|
|
34
34
|
"ipaddr.js": "^2.5.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@git.zone/tsbuild": "4.4.
|
|
37
|
+
"@git.zone/tsbuild": "4.4.3",
|
|
38
38
|
"@git.zone/tsrun": "2.0.6",
|
|
39
|
-
"@git.zone/tstest": "
|
|
39
|
+
"@git.zone/tstest": "6.0.0",
|
|
40
40
|
"@types/node": "26.4.1"
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"dist_ts/**/*",
|
|
45
45
|
".smartconfig.json",
|
|
46
46
|
"readme.md",
|
|
47
|
-
"readme.hints.md",
|
|
48
47
|
"changelog.md",
|
|
49
48
|
"license.md"
|
|
50
49
|
],
|
package/readme.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.
|
|
4
4
|
|
|
5
|
+
## Native video viewers
|
|
6
|
+
|
|
7
|
+
Human leases expose `openVideoPeer(options?)`, `answerVideoPeer(negotiationId, description, options?)`, `closeVideoPeer(options?)`, and `getVideoStatistics(options?)`. Each operation runs through the resource's bounded, authorized and audited operation queue. Peer IDs are private to the lease; offers expose the negotiation ID and exact tab/generation/viewport source identity. Agent leases cannot open media peers. Releasing or revoking a human lease closes only its peer, preserving other viewers and agent work. Failed peer cleanup remains owned for a subsequent release attempt.
|
|
8
|
+
|
|
9
|
+
Set `video` on `BrowserRuntime` for trusted host limits or ICE configuration. Defaults use direct connections with no external STUN/TURN service and automatic GPU support. The private capture extension is loaded without weakening ordinary page proxy, DNS, permission or WebRTC confinement. `state.videoAcceleration` and peer statistics report actual browser acceleration/encoder support.
|
|
10
|
+
|
|
11
|
+
Native viewers should call `lease.subscribeEvents(listener, { includeFrames: false })`. They receive state and errors without generating JPEG traffic. Existing image consumers retain the default `includeFrames: true`; capture starts for the first such subscriber and stops after the last closes. Agent snapshots remain independent of continuous capture. Image subscription cleanup is serialized, coalesced and retryable; a failed disable is retained until cleanup succeeds or its exact browser incarnation terminates.
|
|
12
|
+
|
|
5
13
|
## Issue Reporting and Security
|
|
6
14
|
|
|
7
15
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
@@ -126,13 +134,13 @@ renewal leaves the original expiry unchanged. Only one authorization callback pe
|
|
|
126
134
|
may remain unsettled, including after timeout; runtime-wide renewal admission is bounded
|
|
127
135
|
by `maxCapabilities`. Hosts must stop scheduling renewal when their viewer disconnects.
|
|
128
136
|
|
|
129
|
-
Agent actions are exactly `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Human leases additionally expose tab lifecycle, viewport, raw input, frame subscription/acknowledgement and refresh, and exact-resource artifact reads/deletes. JavaScript evaluation is not public.
|
|
137
|
+
Agent actions are exactly `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Agents can read or delete screenshots created by their exact lease using `readArtifact()` and `deleteArtifact()`. Human leases additionally expose tab lifecycle, viewport, raw input, frame subscription/acknowledgement and refresh, and exact-resource artifact reads/deletes. JavaScript evaluation is not public.
|
|
130
138
|
|
|
131
139
|
All participants enter one strict resource FIFO. `maxQueuedOperationsPerLease` defaults to 128 and accepts 1 through 1,024; each participant has that bound, and the resource queue is bounded by its product with `maxCapabilitiesPerResource`. Overflow fails with `QUOTA_EXCEEDED`. Releasing a participant cancels only its queued and active work. Resource termination and shutdown cancel everyone. Started uncertain work must quiesce or the exact incarnation is terminated before operation settlement.
|
|
132
140
|
|
|
133
141
|
`setViewport()` stores the viewer's preferred width, height and device scale factor and returns `IBrowserRuntimeViewportResult` with the effective `viewport` and `viewportRevision`. The effective values are the componentwise minima across active viewers that have supplied a preference. Unchanged effective sizes do not restart the stream. Removing a viewer recomputes the viewport in the resource FIFO. Held keys and buttons are tracked per participant; leaving releases only inputs that no other participant holds. Viewport and tab/navigation transitions clear held input before changing the target. Resource-owned departure cleanup is bounded and audited as `releaseParticipant`; it does not call the external `beforeOperation` gate after the participant has lost access.
|
|
134
142
|
|
|
135
|
-
`beforeOperation` is an optional awaited fail-closed gate. It receives the complete immutable authority, operation/capability/lease IDs, action, classification, start time, and an `AbortSignal` after dequeue but before the browser side effect starts. Classifications are `raw-input`, `frame-stream`, `viewport`, `navigation`, `tab`, and `agent-action`. Hosts that require durable attempt-before-side-effect auditing should persist the attempt there. Rejection denies the operation. The default `beforeOperationTimeoutMs` is 10,000 milliseconds and accepts values from 100 through 120,000; timeout fails with `TIMEOUT`. The terminal `audit` callback remains a best-effort `completed`/`failed` notification correlated by the same operation ID and classification and runs after bounded operation cleanup releases or fences the exact reservation.
|
|
143
|
+
`beforeOperation` is an optional awaited fail-closed gate. It receives the complete immutable authority, operation/capability/lease IDs, action, classification, start time, and an `AbortSignal` after dequeue but before the browser side effect starts. Classifications are `raw-input`, `frame-stream`, `video-peer`, `viewport`, `navigation`, `tab`, and `agent-action`. Hosts that require durable attempt-before-side-effect auditing should persist the attempt there. Rejection denies the operation. The default `beforeOperationTimeoutMs` is 10,000 milliseconds and accepts values from 100 through 120,000; timeout fails with `TIMEOUT`. The terminal `audit` callback remains a best-effort `completed`/`failed` notification correlated by the same operation ID and classification and runs after bounded operation cleanup releases or fences the exact reservation.
|
|
136
144
|
|
|
137
145
|
`lease.getAuthority()` returns an immutable process-local lease authority containing the runtime authority ID, exported `authorityGeneration`, incarnation generation, and complete capability binding. Every snapshot also binds the exact `capabilityId` and `leaseId`. `lease.isAuthorityCurrent(authority)` performs an exact synchronous revalidation suitable for a host-owned virtual stream. These values are not durable Controller state and do not replace attachment checks against the Controller database.
|
|
138
146
|
|
|
@@ -142,7 +150,7 @@ Each human participant may have one independent frame subscription with a bounde
|
|
|
142
150
|
|
|
143
151
|
An oversized frame is dropped, not fatal. `maxFrameBytes` defaults to 4 MiB and accepts values from 64 KiB through 16 MiB. A frame above the bound is acknowledged toward SmartPuppeteer in the background, never enters the application window, and is reported once on the subscription as `{ type: 'error', error: { code: 'FRAME_TOO_LARGE', fatal: false, tabId } }`. The sequence watermark advances past the dropped frame so later frames remain valid and continue to be delivered.
|
|
144
152
|
|
|
145
|
-
`screencast` tunes the Chromium
|
|
153
|
+
`screencast` tunes the Chromium image capture that starts when an image-frame subscriber joins. `quality` is the JPEG quality from 0 through 100 and defaults to 70. `maxWidth` and `maxHeight` accept values from 1 through 4,096 each, their product must not exceed 8,294,400 pixels (SmartPuppeteer's ceiling, so 3,840×2,160 is accepted and 4,096×4,096 is rejected with `INVALID_INPUT`), they default to 2,560 and 1,600, and they make Chromium scale captured frames to fit. `everyNthFrame` accepts values from 1 through 60 and defaults to 1. `firstFrameTimeoutMs` accepts values from 1,000 through 60,000 and is left to SmartPuppeteer's default when omitted. Runtime forwards these values together with `maxOutstandingFrames`; the defaults keep frames from device-pixel-ratio 2 viewers well below `maxFrameBytes`.
|
|
146
154
|
|
|
147
155
|
Human state and error events retain an optional diagnostic `message` bounded to 2,048 characters. A fatal producer error synchronously invalidates all current participants before their captured subscribers receive the error. Cleanup is coalesced for that exact incarnation and completes before affected operations settle.
|
|
148
156
|
|
|
@@ -164,7 +172,7 @@ The MCP tool list is exactly `browser_navigate`, `browser_snapshot`, `browser_sc
|
|
|
164
172
|
|
|
165
173
|
Each running resource owns one authenticated loopback `BrowserEgressProxy` carrying immutable `projectId` and `browserResourceId`. HTTP, WebSocket Upgrade, and CONNECT share strict public-unicast DNS/IP validation, numeric dialing, bounded lifetimes, and fail-closed policy.
|
|
166
174
|
|
|
167
|
-
Artifact identity and APIs use `(projectId, browserResourceId, artifactId)`. Keyed project/resource directories prevent caller IDs from entering paths. Admission is serialized across per-resource, per-project, and global count/byte quotas. Reads use no-follow handles and verify size and SHA-256.
|
|
175
|
+
Artifact identity and APIs use `(projectId, browserResourceId, artifactId)`. Keyed project/resource directories prevent caller IDs from entering paths. Admission is serialized across per-resource, per-project, and global count/byte quotas. Reads use no-follow handles and verify size and SHA-256. Screenshot actions return metadata. Direct agent leases can read/delete only artifacts created by that exact lease; other agent and human artifacts remain unavailable. Human artifact access remains scoped to the resource. Read completion revalidates the lease and clears bytes if authority ended during IO. The built-in framed/Flex/MCP action adapters continue to return metadata; hosts can return image content after a scoped lease read. The artifact store accepts an optional opaque owner ID on `store()` and an expected owner ID on `read()`/`delete()`; omitting an expected owner retains its trusted parent API.
|
|
168
176
|
|
|
169
177
|
## Verification
|
|
170
178
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@modelprofile.com/browser-runtime',
|
|
6
|
-
version: '5.
|
|
6
|
+
version: '5.2.0',
|
|
7
7
|
description: 'Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.'
|
|
8
8
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
interface IStoredArtifact {
|
|
16
16
|
metadata: IBrowserArtifactMetadata;
|
|
17
17
|
digest: string;
|
|
18
|
+
ownerId?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
interface IResourceArtifacts {
|
|
@@ -164,11 +165,14 @@ export class BrowserArtifactStore {
|
|
|
164
165
|
browserResourceIdArg: string,
|
|
165
166
|
mimeTypeArg: string,
|
|
166
167
|
data: Uint8Array,
|
|
168
|
+
ownerIdArg?: string,
|
|
167
169
|
): Promise<IBrowserArtifactMetadata> {
|
|
168
170
|
this.requireStarted();
|
|
169
171
|
const projectId = this.validateProjectId(projectIdArg);
|
|
170
172
|
const browserResourceId = this.validateResourceId(browserResourceIdArg);
|
|
171
173
|
const mimeType = validateBoundedString(mimeTypeArg, 'mimeType', 1, 128);
|
|
174
|
+
const ownerId = ownerIdArg === undefined ? undefined
|
|
175
|
+
: validateBoundedString(ownerIdArg, 'ownerId', 1, 128);
|
|
172
176
|
if (!(data instanceof Uint8Array)) {
|
|
173
177
|
throw new BrowserRuntimeError('INVALID_INPUT', 'data must be Uint8Array');
|
|
174
178
|
}
|
|
@@ -212,6 +216,7 @@ export class BrowserArtifactStore {
|
|
|
212
216
|
const stored: IStoredArtifact = {
|
|
213
217
|
metadata,
|
|
214
218
|
digest: plugins.crypto.createHash('sha256').update(bytes).digest('hex'),
|
|
219
|
+
...(ownerId === undefined ? {} : { ownerId }),
|
|
215
220
|
};
|
|
216
221
|
const resourceDirectory = this.resourcePath(project, resource);
|
|
217
222
|
const finalPath = plugins.path.join(resourceDirectory, artifactId);
|
|
@@ -252,6 +257,7 @@ export class BrowserArtifactStore {
|
|
|
252
257
|
projectIdArg: string,
|
|
253
258
|
browserResourceIdArg: string,
|
|
254
259
|
artifactIdArg: string,
|
|
260
|
+
expectedOwnerIdArg?: string,
|
|
255
261
|
): Promise<Uint8Array> {
|
|
256
262
|
this.requireStarted();
|
|
257
263
|
const projectId = this.validateProjectId(projectIdArg);
|
|
@@ -261,6 +267,7 @@ export class BrowserArtifactStore {
|
|
|
261
267
|
let handle: plugins.fsPromises.FileHandle | undefined;
|
|
262
268
|
try {
|
|
263
269
|
const resolved = this.resolveArtifact(projectId, browserResourceId, artifactId);
|
|
270
|
+
this.assertArtifactOwner(resolved.stored, expectedOwnerIdArg);
|
|
264
271
|
if (resolved.stored.metadata.expiresAt <= this.now()) {
|
|
265
272
|
await this.deleteArtifact(projectId, browserResourceId, resolved);
|
|
266
273
|
throw new BrowserRuntimeError('INVALID_INPUT', 'artifact is unavailable');
|
|
@@ -300,6 +307,7 @@ export class BrowserArtifactStore {
|
|
|
300
307
|
projectIdArg: string,
|
|
301
308
|
browserResourceIdArg: string,
|
|
302
309
|
artifactIdArg: string,
|
|
310
|
+
expectedOwnerIdArg?: string,
|
|
303
311
|
): Promise<void> {
|
|
304
312
|
this.requireStarted();
|
|
305
313
|
const projectId = this.validateProjectId(projectIdArg);
|
|
@@ -307,16 +315,26 @@ export class BrowserArtifactStore {
|
|
|
307
315
|
const artifactId = this.validateArtifactId(artifactIdArg);
|
|
308
316
|
const release = await this.mutex.acquire();
|
|
309
317
|
try {
|
|
318
|
+
const resolved = this.resolveArtifact(projectId, browserResourceId, artifactId);
|
|
319
|
+
this.assertArtifactOwner(resolved.stored, expectedOwnerIdArg);
|
|
310
320
|
await this.deleteArtifact(
|
|
311
321
|
projectId,
|
|
312
322
|
browserResourceId,
|
|
313
|
-
|
|
323
|
+
resolved,
|
|
314
324
|
);
|
|
315
325
|
} finally {
|
|
316
326
|
release();
|
|
317
327
|
}
|
|
318
328
|
}
|
|
319
329
|
|
|
330
|
+
private assertArtifactOwner(storedArg: IStoredArtifact, expectedOwnerIdArg?: string): void {
|
|
331
|
+
if (expectedOwnerIdArg === undefined) return;
|
|
332
|
+
const expectedOwnerId = validateBoundedString(expectedOwnerIdArg, 'ownerId', 1, 128);
|
|
333
|
+
if (storedArg.ownerId !== expectedOwnerId) {
|
|
334
|
+
throw new BrowserRuntimeError('INVALID_INPUT', 'artifact is unavailable');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
320
338
|
public async purgeExpired(resource?: IBrowserResourceKey): Promise<number> {
|
|
321
339
|
this.requireStarted();
|
|
322
340
|
const validated = resource === undefined ? undefined : {
|