@aperture-browser/api-client 0.0.0-init.0 → 0.0.0-nightly.101.c63f90ec6164
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 +113 -12
- package/dist/index.d.ts +658 -404
- package/dist/index.js +552 -77
- package/dist/index.js.map +1 -1
- package/dist/passkeys.d.ts +30 -0
- package/dist/passkeys.js +54 -0
- package/dist/passkeys.js.map +1 -0
- package/dist/service-DbyEmdKS.d.ts +406 -0
- package/dist/service-riqZPQHq.js +8 -0
- package/dist/service-riqZPQHq.js.map +1 -0
- package/package.json +15 -5
package/dist/index.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
import { t as AuthApi } from "./service-riqZPQHq.js";
|
|
1
2
|
import * as Effect from "effect/Effect";
|
|
2
3
|
import * as Layer from "effect/Layer";
|
|
3
|
-
import * as HttpBody from "effect/
|
|
4
|
-
import * as HttpClient from "effect/
|
|
5
|
-
import * as HttpClientResponse from "effect/
|
|
4
|
+
import * as HttpBody from "effect/http/HttpBody";
|
|
5
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
6
|
+
import * as HttpClientResponse from "effect/http/HttpClientResponse";
|
|
6
7
|
import * as Api from "@aperture-browser/api-schema";
|
|
7
8
|
import * as Context from "effect/Context";
|
|
8
9
|
import * as Schema from "effect/Schema";
|
|
9
10
|
import * as PubSub from "effect/PubSub";
|
|
11
|
+
import * as Redacted from "effect/Redacted";
|
|
10
12
|
import * as Stream from "effect/Stream";
|
|
11
13
|
import * as Function from "effect/Function";
|
|
12
|
-
import * as HttpClientRequest from "effect/
|
|
14
|
+
import * as HttpClientRequest from "effect/http/HttpClientRequest";
|
|
15
|
+
import * as HttpClientError from "effect/http/HttpClientError";
|
|
16
|
+
import * as Option from "effect/Option";
|
|
17
|
+
import * as Arr from "effect/Array";
|
|
18
|
+
import * as SchemaTransformation from "effect/SchemaTransformation";
|
|
19
|
+
import * as Random from "effect/Random";
|
|
13
20
|
//#region src/authorization/service.ts
|
|
14
21
|
const TENANT_HEADER = "X-Aperture-Tenant-Id";
|
|
15
22
|
/** The browser's own login session, sent as a cookie. */
|
|
@@ -172,10 +179,51 @@ const TOTPEnrollment = Schema.Struct({
|
|
|
172
179
|
qrCodeDataUrl: Schema.String
|
|
173
180
|
});
|
|
174
181
|
const RecoveryCodes = Schema.Struct({ recoveryCodes: Schema.Array(Schema.String) });
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
const OAuthClient = Schema.Struct({
|
|
183
|
+
id: Schema.String,
|
|
184
|
+
name: Schema.String,
|
|
185
|
+
uri: Schema.NullOr(Schema.String),
|
|
186
|
+
logoUri: Schema.NullOr(Schema.String),
|
|
187
|
+
kind: Schema.Literals(["registered", "metadata_document"])
|
|
188
|
+
});
|
|
189
|
+
/** A pending OAuth authorization request, as the consent page presents it. */
|
|
190
|
+
const OAuthAuthorizationRequest = Schema.Struct({
|
|
191
|
+
client: OAuthClient,
|
|
192
|
+
redirectUri: Schema.String,
|
|
193
|
+
requestedScopes: Schema.Array(Schema.String),
|
|
194
|
+
user: Schema.Struct({
|
|
195
|
+
id: Schema.String,
|
|
196
|
+
displayName: Schema.String,
|
|
197
|
+
isSystemAdmin: Schema.Boolean
|
|
198
|
+
}),
|
|
199
|
+
tenants: Schema.Array(Schema.Struct({
|
|
200
|
+
id: Schema.String,
|
|
201
|
+
displayName: Schema.String,
|
|
202
|
+
scopes: Schema.Array(Api.TenantScope)
|
|
203
|
+
})),
|
|
204
|
+
availableScopes: Schema.Array(Api.TenantScope),
|
|
205
|
+
canGrantSystemAdmin: Schema.Boolean
|
|
206
|
+
});
|
|
207
|
+
/** Where the browser goes after the user approves or denies an authorization request. */
|
|
208
|
+
const OAuthRedirect = Schema.Struct({ redirectUrl: Schema.String });
|
|
209
|
+
const OAuthGrant = Schema.Struct({
|
|
210
|
+
id: Schema.String,
|
|
211
|
+
client: OAuthClient,
|
|
212
|
+
authorityType: Api.AuthorityType,
|
|
213
|
+
tenants: Schema.Array(Schema.Struct({
|
|
214
|
+
id: Schema.String,
|
|
215
|
+
displayName: Schema.String
|
|
216
|
+
})),
|
|
217
|
+
scopes: Schema.Array(Schema.String),
|
|
218
|
+
resourceMode: Api.ResourceMode,
|
|
219
|
+
resourceGrants: Schema.Array(Schema.Struct({
|
|
220
|
+
resourceType: Api.ResourceType,
|
|
221
|
+
resourceId: Schema.String
|
|
222
|
+
})),
|
|
223
|
+
createdAt: Schema.String,
|
|
224
|
+
lastUsedAt: Schema.NullOr(Schema.String)
|
|
225
|
+
});
|
|
226
|
+
const OAuthGrants = Schema.Struct({ grants: Schema.Array(OAuthGrant) });
|
|
179
227
|
//#endregion
|
|
180
228
|
//#region src/auth/layer.ts
|
|
181
229
|
const jsonBody = (body) => ({ body: HttpBody.jsonUnsafe(body) });
|
|
@@ -261,6 +309,21 @@ const makeAuthApi = Effect.gen(function* () {
|
|
|
261
309
|
const disableTOTP = Effect.fn("AuthApi.disableTOTP")(function* (code) {
|
|
262
310
|
yield* http.post("/auth/totp/disable", jsonBody({ code })).pipe(webSession);
|
|
263
311
|
});
|
|
312
|
+
const getOAuthAuthorization = Effect.fn("AuthApi.getOAuthAuthorization")(function* (query) {
|
|
313
|
+
return yield* http.get(`/auth/oauth/authorization?${query}`).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(OAuthAuthorizationRequest)), webSession);
|
|
314
|
+
});
|
|
315
|
+
const approveOAuthAuthorization = Effect.fn("AuthApi.approveOAuthAuthorization")(function* (approval) {
|
|
316
|
+
return yield* http.post("/auth/oauth/authorization/approve", jsonBody(approval)).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(OAuthRedirect)), webSession);
|
|
317
|
+
});
|
|
318
|
+
const denyOAuthAuthorization = Effect.fn("AuthApi.denyOAuthAuthorization")(function* (query) {
|
|
319
|
+
return yield* http.post("/auth/oauth/authorization/deny", jsonBody({ query })).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(OAuthRedirect)), webSession);
|
|
320
|
+
});
|
|
321
|
+
const listOAuthGrants = Effect.fn("AuthApi.listOAuthGrants")(function* () {
|
|
322
|
+
return yield* http.get("/auth/oauth/grants").pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(OAuthGrants)), webSession);
|
|
323
|
+
});
|
|
324
|
+
const revokeOAuthGrant = Effect.fn("AuthApi.revokeOAuthGrant")(function* (grantId) {
|
|
325
|
+
yield* http.del(`/auth/oauth/grants/${encodeURIComponent(grantId)}`).pipe(webSession);
|
|
326
|
+
});
|
|
264
327
|
return AuthApi.of({
|
|
265
328
|
listLoginMethods,
|
|
266
329
|
loginWithPassword,
|
|
@@ -281,7 +344,12 @@ const makeAuthApi = Effect.gen(function* () {
|
|
|
281
344
|
beginTOTPEnrollment,
|
|
282
345
|
completeTOTPEnrollment,
|
|
283
346
|
regenerateRecoveryCodes,
|
|
284
|
-
disableTOTP
|
|
347
|
+
disableTOTP,
|
|
348
|
+
getOAuthAuthorization,
|
|
349
|
+
approveOAuthAuthorization,
|
|
350
|
+
denyOAuthAuthorization,
|
|
351
|
+
listOAuthGrants,
|
|
352
|
+
revokeOAuthGrant
|
|
285
353
|
});
|
|
286
354
|
});
|
|
287
355
|
const authApiLayer = Layer.effect(AuthApi, makeAuthApi);
|
|
@@ -292,48 +360,64 @@ const ApiErrorBody = Schema.Struct({ error: Schema.Struct({
|
|
|
292
360
|
message: Schema.String
|
|
293
361
|
}) });
|
|
294
362
|
/**
|
|
363
|
+
* The error body of a live-session route that failed inside the running session. It carries
|
|
364
|
+
* no stable code, so the resulting ApiRequestError uses `live_session_error` and callers
|
|
365
|
+
* branch on its status.
|
|
366
|
+
*/
|
|
367
|
+
const LiveSessionErrorBody = Schema.Struct({ error: Schema.String });
|
|
368
|
+
const ErrorResponseBody = Schema.Union([ApiErrorBody, LiveSessionErrorBody]);
|
|
369
|
+
/**
|
|
295
370
|
* A failed API request. `code` and `message` come from the server's error body when it
|
|
296
|
-
* sent one; `status` is 0 when no HTTP status applies.
|
|
371
|
+
* sent one; `status` is 0 when no HTTP status applies. `cause` is the underlying HTTP or
|
|
372
|
+
* decoding failure when the server's error body alone does not explain it.
|
|
297
373
|
*/
|
|
298
374
|
var ApiRequestError = class extends Schema.TaggedError()("ApiRequestError", {
|
|
299
375
|
code: Schema.String,
|
|
300
376
|
message: Schema.String,
|
|
301
|
-
status: Schema.Number
|
|
377
|
+
status: Schema.Number,
|
|
378
|
+
cause: Schema.optional(Schema.Union([Schema.instanceOf(HttpClientError.HttpClientError), Schema.instanceOf(Schema.SchemaError)]))
|
|
302
379
|
}) {};
|
|
303
|
-
const invalidResponse = (status) => new ApiRequestError({
|
|
380
|
+
const invalidResponse = (status, cause) => new ApiRequestError({
|
|
304
381
|
code: "internal_error",
|
|
305
382
|
message: "Invalid response",
|
|
306
|
-
status
|
|
307
|
-
|
|
308
|
-
const networkError = new ApiRequestError({
|
|
309
|
-
code: "network_error",
|
|
310
|
-
message: "The server could not be reached",
|
|
311
|
-
status: 0
|
|
383
|
+
status,
|
|
384
|
+
cause
|
|
312
385
|
});
|
|
313
386
|
/**
|
|
314
387
|
* Maps HTTP and decoding failures to ApiRequestError. Error responses keep the code and
|
|
315
388
|
* message of the server's error body.
|
|
316
389
|
*/
|
|
317
390
|
const toApiRequestError = (self) => self.pipe(Effect.catchReasons("HttpClientError", {
|
|
318
|
-
StatusCodeError: ({ response }) => HttpClientResponse.schemaBodyJson(
|
|
391
|
+
StatusCodeError: ({ response }, cause) => HttpClientResponse.schemaBodyJson(ErrorResponseBody)(response).pipe(Effect.map(({ error }) => typeof error === "string" ? new ApiRequestError({
|
|
392
|
+
code: "live_session_error",
|
|
393
|
+
message: error,
|
|
394
|
+
status: response.status
|
|
395
|
+
}) : new ApiRequestError({
|
|
319
396
|
...error,
|
|
320
397
|
status: response.status
|
|
321
398
|
})), Effect.orElseSucceed(() => new ApiRequestError({
|
|
322
399
|
code: "internal_error",
|
|
323
400
|
message: "Request failed",
|
|
324
|
-
status: response.status
|
|
401
|
+
status: response.status,
|
|
402
|
+
cause
|
|
325
403
|
})), Effect.flatMap(Effect.fail)),
|
|
326
|
-
DecodeError: ({ response }) => Effect.fail(invalidResponse(response.status))
|
|
404
|
+
DecodeError: ({ response }, cause) => Effect.fail(invalidResponse(response.status, cause))
|
|
327
405
|
}), Effect.catchTags({
|
|
328
|
-
HttpClientError: () => Effect.fail(
|
|
329
|
-
|
|
406
|
+
HttpClientError: (cause) => Effect.fail(new ApiRequestError({
|
|
407
|
+
code: "network_error",
|
|
408
|
+
message: "The server could not be reached",
|
|
409
|
+
status: 0,
|
|
410
|
+
cause
|
|
411
|
+
})),
|
|
412
|
+
SchemaError: (cause) => Effect.fail(invalidResponse(0, cause))
|
|
330
413
|
}));
|
|
331
414
|
//#endregion
|
|
332
415
|
//#region src/authorization/layer.ts
|
|
333
416
|
/** The authorization of the call a request belongs to; set by `authorize`. */
|
|
334
417
|
const CurrentAuthorization = Context.Reference("@aperture-browser/api-client/CurrentAuthorization", { defaultValue: () => ({}) });
|
|
335
418
|
const authorizeRequest = (request) => CurrentAuthorization.useSync(({ credentials, bearerToken, tenantHeader = "none" }) => {
|
|
336
|
-
const
|
|
419
|
+
const secret = bearerToken ?? (credentials?.kind === "bearer" ? credentials.token : void 0);
|
|
420
|
+
const token = secret === void 0 ? void 0 : Redacted.value(secret).trim();
|
|
337
421
|
const tenantId = credentials ? resolveTenantHeader(credentials, tenantHeader) : void 0;
|
|
338
422
|
return request.pipe(HttpClientRequest.acceptJson, token ? HttpClientRequest.bearerToken(token) : Function.identity, tenantId ? HttpClientRequest.setHeader(TENANT_HEADER, tenantId) : HttpClientRequest.removeHeader(TENANT_HEADER));
|
|
339
423
|
});
|
|
@@ -359,6 +443,43 @@ const makeApiAuthorization = Effect.gen(function* () {
|
|
|
359
443
|
});
|
|
360
444
|
const apiAuthorizationLayer = Layer.effect(ApiAuthorization, makeApiAuthorization);
|
|
361
445
|
//#endregion
|
|
446
|
+
//#region src/events/service.ts
|
|
447
|
+
/** The tenant's resource event log, and the deployment-wide audit log. */
|
|
448
|
+
var EventsApi = class extends Context.Service()("@aperture-browser/api-client/EventsApi") {};
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/health/service.ts
|
|
451
|
+
/** The instance's unauthenticated health check. */
|
|
452
|
+
var HealthApi = class extends Context.Service()("@aperture-browser/api-client/HealthApi") {};
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/pagination.ts
|
|
455
|
+
function getNextPageParam(page) {
|
|
456
|
+
return page.meta.hasMore ? page.meta.nextCursor : void 0;
|
|
457
|
+
}
|
|
458
|
+
function flattenInfinitePages(pages) {
|
|
459
|
+
return pages?.flatMap((page) => page.data) ?? [];
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Derives `stream` and `listAll` from the call that fetches one page. That call must also
|
|
463
|
+
* accept a bare cursor, which every filter allows because all its fields are optional.
|
|
464
|
+
*/
|
|
465
|
+
function paginated(list) {
|
|
466
|
+
const stream = (credentials, filter) => Stream.paginate(void 0, (cursor) => list(credentials, filter === void 0 ? { cursor } : {
|
|
467
|
+
...filter,
|
|
468
|
+
cursor
|
|
469
|
+
}).pipe(Effect.map((page) => {
|
|
470
|
+
const next = getNextPageParam(page);
|
|
471
|
+
return [page.data, next === void 0 ? Option.none() : Option.some(next)];
|
|
472
|
+
})));
|
|
473
|
+
const listAll = (credentials, filter) => Stream.runCollect(stream(credentials, filter));
|
|
474
|
+
return {
|
|
475
|
+
list,
|
|
476
|
+
stream,
|
|
477
|
+
listAll
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const defaultListLimit = 50;
|
|
481
|
+
const listQueryDefaults = { refetchOnWindowFocus: true };
|
|
482
|
+
//#endregion
|
|
362
483
|
//#region src/query.ts
|
|
363
484
|
/** Encodes tag filters as the parallel tagKey, tagOperator and tagValue query lists. */
|
|
364
485
|
const tagQuery = (tags) => ({
|
|
@@ -374,10 +495,6 @@ const compactQuery = (query) => Object.fromEntries(Object.entries(query).flatMap
|
|
|
374
495
|
return items.length > 0 ? [[key, items]] : [];
|
|
375
496
|
}));
|
|
376
497
|
//#endregion
|
|
377
|
-
//#region src/events/service.ts
|
|
378
|
-
/** The tenant's resource event log. */
|
|
379
|
-
var EventsApi = class extends Context.Service()("@aperture-browser/api-client/EventsApi") {};
|
|
380
|
-
//#endregion
|
|
381
498
|
//#region src/events/layer.ts
|
|
382
499
|
const makeEventsApi = Effect.gen(function* () {
|
|
383
500
|
const httpClient = yield* HttpClient.HttpClient;
|
|
@@ -391,41 +508,105 @@ const makeEventsApi = Effect.gen(function* () {
|
|
|
391
508
|
resourceId: params.resourceId
|
|
392
509
|
}) }).pipe(authorize(Authorization.tenantScoped(credentials)));
|
|
393
510
|
});
|
|
394
|
-
|
|
511
|
+
const listAuditEvents = Effect.fn("EventsApi.listAuditEvents")(function* (credentials, params = {}) {
|
|
512
|
+
return yield* api.listAuditEvents({ params: compactQuery({
|
|
513
|
+
limit: params.limit,
|
|
514
|
+
cursor: params.cursor,
|
|
515
|
+
tenantId: params.tenantId,
|
|
516
|
+
actorType: params.actorType,
|
|
517
|
+
actorId: params.actorId,
|
|
518
|
+
action: params.action,
|
|
519
|
+
resourceType: params.resourceType,
|
|
520
|
+
resourceId: params.resourceId
|
|
521
|
+
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
522
|
+
});
|
|
523
|
+
const events = paginated(listEvents);
|
|
524
|
+
const auditEvents = paginated(listAuditEvents);
|
|
525
|
+
return EventsApi.of({
|
|
526
|
+
listEvents,
|
|
527
|
+
streamEvents: events.stream,
|
|
528
|
+
listAllEvents: events.listAll,
|
|
529
|
+
listAuditEvents,
|
|
530
|
+
streamAuditEvents: auditEvents.stream,
|
|
531
|
+
listAllAuditEvents: auditEvents.listAll
|
|
532
|
+
});
|
|
395
533
|
});
|
|
396
534
|
const eventsApiLayer = Layer.effect(EventsApi, makeEventsApi);
|
|
397
535
|
//#endregion
|
|
536
|
+
//#region src/health/layer.ts
|
|
537
|
+
const makeHealthApi = Effect.gen(function* () {
|
|
538
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
539
|
+
const { authorize } = yield* ApiAuthorization;
|
|
540
|
+
const api = Api.make(httpClient);
|
|
541
|
+
const getHealth = Effect.fn("HealthApi.getHealth")(function* () {
|
|
542
|
+
return yield* api.getHealth(void 0).pipe(authorize(Authorization.anonymous));
|
|
543
|
+
});
|
|
544
|
+
return HealthApi.of({ getHealth });
|
|
545
|
+
});
|
|
546
|
+
const healthApiLayer = Layer.effect(HealthApi, makeHealthApi);
|
|
547
|
+
//#endregion
|
|
398
548
|
//#region src/sessions/schemas.ts
|
|
399
549
|
const positiveInt = Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0));
|
|
400
550
|
const emptyArray = Effect.succeed([]);
|
|
401
|
-
const
|
|
551
|
+
const RecordingSettings = Schema.Struct({
|
|
552
|
+
capture: Api.CreateSessionRecordingInput.fields.capture,
|
|
553
|
+
presentation: Api.CreateSessionRecordingInput.fields.presentation,
|
|
554
|
+
idle: Api.CreateSessionRecordingInput.fields.idle,
|
|
555
|
+
ripple: Api.CreateSessionRecordingInput.fields.ripple,
|
|
556
|
+
burst: Api.CreateSessionRecordingInput.fields.burst
|
|
557
|
+
});
|
|
558
|
+
const BrowserPage = Schema.Struct({
|
|
559
|
+
targetId: Schema.String,
|
|
560
|
+
state: Schema.Literals([
|
|
561
|
+
"pending",
|
|
562
|
+
"ready",
|
|
563
|
+
"unavailable",
|
|
564
|
+
"closed"
|
|
565
|
+
]),
|
|
566
|
+
title: Schema.String,
|
|
567
|
+
url: Schema.String,
|
|
568
|
+
viewport: Schema.optionalKey(Schema.Struct({
|
|
569
|
+
width: positiveInt,
|
|
570
|
+
height: positiveInt,
|
|
571
|
+
deviceScaleFactor: Schema.Number.check(Schema.isGreaterThan(0)),
|
|
572
|
+
contentWidth: positiveInt,
|
|
573
|
+
contentHeight: positiveInt,
|
|
574
|
+
canvasWidth: positiveInt,
|
|
575
|
+
canvasHeight: positiveInt
|
|
576
|
+
})),
|
|
577
|
+
thumbnailAvailable: Schema.Boolean
|
|
578
|
+
});
|
|
579
|
+
const browserStatusCommon = {
|
|
402
580
|
sessionId: Schema.String,
|
|
403
581
|
cdpUrl: Schema.String,
|
|
404
582
|
media: Api.SessionMedia,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
583
|
+
thumbnailAvailable: Schema.Boolean,
|
|
584
|
+
pages: Schema.Array(BrowserPage).pipe(Schema.withDecodingDefaultKey(emptyArray))
|
|
585
|
+
};
|
|
586
|
+
const availableBrowserStatus = {
|
|
587
|
+
...browserStatusCommon,
|
|
588
|
+
capturedAt: Schema.String,
|
|
589
|
+
representativeTargetId: Schema.optionalKey(Schema.String)
|
|
590
|
+
};
|
|
591
|
+
/** Passive page discovery. Reading it never wakes or keeps a browser session alive. */
|
|
592
|
+
const BrowserStatus = Schema.Union([
|
|
593
|
+
Schema.Struct({
|
|
594
|
+
...availableBrowserStatus,
|
|
595
|
+
status: Schema.Literal("running"),
|
|
596
|
+
source: Schema.Literal("live")
|
|
597
|
+
}),
|
|
598
|
+
Schema.Struct({
|
|
599
|
+
...availableBrowserStatus,
|
|
600
|
+
status: Schema.Literal("suspended"),
|
|
601
|
+
source: Schema.Literal("persisted")
|
|
602
|
+
}),
|
|
603
|
+
Schema.Struct({
|
|
604
|
+
...browserStatusCommon,
|
|
605
|
+
status: Schema.Literal("suspended"),
|
|
606
|
+
source: Schema.Literal("unavailable")
|
|
607
|
+
})
|
|
608
|
+
]);
|
|
609
|
+
const recordingFields = {
|
|
429
610
|
recordingId: Schema.String,
|
|
430
611
|
mode: Schema.Literals(["tab", "viewer"]),
|
|
431
612
|
targetId: Schema.String,
|
|
@@ -437,21 +618,81 @@ const Recording = Schema.Struct({
|
|
|
437
618
|
"failed"
|
|
438
619
|
]),
|
|
439
620
|
stopReason: Schema.optionalKey(Schema.String),
|
|
440
|
-
|
|
621
|
+
sandboxPath: Schema.optionalKey(Schema.String),
|
|
622
|
+
/** @deprecated Read `relativePath`. Sessions started before it existed send only this. */
|
|
623
|
+
path: Schema.optionalKey(Schema.String),
|
|
441
624
|
startedAt: Schema.String,
|
|
442
625
|
stoppedAt: Schema.optionalKey(Schema.String),
|
|
443
626
|
sizeBytes: Schema.optionalKey(Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))),
|
|
444
627
|
fps: positiveInt,
|
|
445
628
|
bitrateKbps: positiveInt,
|
|
446
|
-
codec: Schema.String
|
|
447
|
-
|
|
629
|
+
codec: Schema.String,
|
|
630
|
+
/** The stop returned and the edit is still running; `editedRelativePath` or `editError` follows. */
|
|
631
|
+
editing: Schema.Boolean,
|
|
632
|
+
editedRelativePath: Schema.optionalKey(Schema.String),
|
|
633
|
+
timelineRelativePath: Schema.optionalKey(Schema.String),
|
|
634
|
+
editError: Schema.optionalKey(Api.RecordingEditError)
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* A recording as the live session reports it. Sessions that are still running an
|
|
638
|
+
* older wrapper send only `path`, which decoding carries over into `relativePath`.
|
|
639
|
+
*/
|
|
640
|
+
const Recording = Schema.Struct({
|
|
641
|
+
...recordingFields,
|
|
642
|
+
relativePath: Schema.optionalKey(Schema.String)
|
|
643
|
+
}).pipe(Schema.decodeTo(Schema.Struct({
|
|
644
|
+
...recordingFields,
|
|
645
|
+
relativePath: Schema.String
|
|
646
|
+
}), SchemaTransformation.transform({
|
|
647
|
+
decode: ({ relativePath, ...recording }) => ({
|
|
648
|
+
...recording,
|
|
649
|
+
relativePath: relativePath ?? recording.path ?? ""
|
|
650
|
+
}),
|
|
651
|
+
encode: (recording) => recording
|
|
652
|
+
})));
|
|
448
653
|
//#endregion
|
|
449
654
|
//#region src/sessions/service.ts
|
|
450
655
|
/** Browser sessions, their live browser, and their recordings. */
|
|
451
656
|
var SessionsApi = class extends Context.Service()("@aperture-browser/api-client/SessionsApi") {};
|
|
452
657
|
//#endregion
|
|
658
|
+
//#region src/sessions/uploads.ts
|
|
659
|
+
const encoder = new TextEncoder();
|
|
660
|
+
const quotedFilename = (name) => name.replaceAll("\"", "%22").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
|
|
661
|
+
const contentStream = (content) => {
|
|
662
|
+
if (content instanceof Blob) return Stream.fromReadableStream({
|
|
663
|
+
evaluate: () => content.stream(),
|
|
664
|
+
onError: (cause) => cause
|
|
665
|
+
});
|
|
666
|
+
if (content instanceof Uint8Array) return Stream.succeed(content);
|
|
667
|
+
return content;
|
|
668
|
+
};
|
|
669
|
+
const streamedMultipart = Effect.fn("SessionsApi.streamedMultipart")(function* (files) {
|
|
670
|
+
const boundary = `aperture-upload-${(yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER)).toString(36)}`;
|
|
671
|
+
const parts = files.map((file) => {
|
|
672
|
+
const header = `--${boundary}\r\nContent-Disposition: form-data; name="files"; filename="${quotedFilename(file.name)}"\r\nContent-Type: application/octet-stream\r
|
|
673
|
+
\r
|
|
674
|
+
`;
|
|
675
|
+
return Stream.succeed(encoder.encode(header)).pipe(Stream.concat(contentStream(file.content)), Stream.concat(Stream.succeed(encoder.encode("\r\n"))));
|
|
676
|
+
});
|
|
677
|
+
const body = Stream.fromIterable(parts).pipe(Stream.flatten(), Stream.concat(Stream.succeed(encoder.encode(`--${boundary}--\r\n`))));
|
|
678
|
+
return HttpBody.stream(body, `multipart/form-data; boundary=${boundary}`);
|
|
679
|
+
});
|
|
680
|
+
/**
|
|
681
|
+
* The multipart body of an upload. FormData works with every fetch, so it is used unless a
|
|
682
|
+
* content is a stream, which needs the body itself to be streamed.
|
|
683
|
+
*/
|
|
684
|
+
const uploadBody = Effect.fn("SessionsApi.uploadBody")(function* (files) {
|
|
685
|
+
const form = new FormData();
|
|
686
|
+
for (const file of files) if (file.content instanceof Blob) form.append("files", file.content, file.name);
|
|
687
|
+
else if (file.content instanceof Uint8Array) form.append("files", new Blob([new Uint8Array(file.content)]), file.name);
|
|
688
|
+
else return yield* streamedMultipart(files);
|
|
689
|
+
return HttpBody.formData(form);
|
|
690
|
+
});
|
|
691
|
+
//#endregion
|
|
453
692
|
//#region src/sessions/layer.ts
|
|
454
693
|
const contentDispositionFilename = (header) => header?.match(/filename="([^"]+)"/)?.[1] ?? null;
|
|
694
|
+
const recordingContentPath = (sessionId, recordingId) => `/sessions/${encodeURIComponent(sessionId)}/recordings/${encodeURIComponent(recordingId)}/content`;
|
|
695
|
+
const BULK_SESSION_IDS_LIMIT = 100;
|
|
455
696
|
const makeSessionsApi = Effect.gen(function* () {
|
|
456
697
|
const httpClient = yield* HttpClient.HttpClient;
|
|
457
698
|
const { authorize } = yield* ApiAuthorization;
|
|
@@ -464,19 +705,24 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
464
705
|
cursor: params.cursor,
|
|
465
706
|
includeDeleted: params.includeDeleted || void 0,
|
|
466
707
|
status: params.status,
|
|
708
|
+
query: params.query,
|
|
467
709
|
...tagQuery(params.tags)
|
|
468
710
|
}) }).pipe(tenantScoped(credentials));
|
|
469
711
|
});
|
|
712
|
+
const sessions = paginated(listSessions);
|
|
713
|
+
const exportSessionStorageState = Effect.fn("SessionsApi.exportSessionStorageState")(function* (credentials, sessionId, input) {
|
|
714
|
+
return yield* api.exportSessionStorageState(sessionId, { payload: input }).pipe(tenantScoped(credentials));
|
|
715
|
+
});
|
|
470
716
|
const getSession = Effect.fn("SessionsApi.getSession")(function* (credentials, sessionId) {
|
|
471
717
|
return yield* api.getSession(sessionId, void 0).pipe(tenantScoped(credentials));
|
|
472
718
|
});
|
|
473
719
|
const getSessionsBulk = Effect.fn("SessionsApi.getSessionsBulk")(function* (credentials, sessionIds) {
|
|
474
|
-
return yield* api.getSessionsBulk({ payload: { ids
|
|
720
|
+
return { sessions: (yield* Effect.forEach(Arr.chunksOf(sessionIds, BULK_SESSION_IDS_LIMIT), (ids) => api.getSessionsBulk({ payload: { ids } }).pipe(tenantScoped(credentials)))).flatMap((response) => response.sessions) };
|
|
475
721
|
});
|
|
476
722
|
const createSession = Effect.fn("SessionsApi.createSession")(function* (credentials, input, options = {}) {
|
|
477
723
|
return yield* api.createSession({
|
|
478
724
|
params: compactQuery({ waitForReady: options.waitForReady }),
|
|
479
|
-
payload: {
|
|
725
|
+
payload: Schema.encodeSync(Api.CreateSessionRequestJson)({
|
|
480
726
|
baseSnapshotName: input.baseSnapshotName ?? null,
|
|
481
727
|
label: input.label ?? null,
|
|
482
728
|
browser: {
|
|
@@ -485,8 +731,9 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
485
731
|
},
|
|
486
732
|
initialTargets: input.initialTargets ?? [],
|
|
487
733
|
...input.storageState === void 0 ? {} : { storageState: input.storageState },
|
|
488
|
-
tags: input.tags ?? {}
|
|
489
|
-
|
|
734
|
+
tags: input.tags ?? {},
|
|
735
|
+
...input.proxy === void 0 ? {} : { proxy: input.proxy }
|
|
736
|
+
})
|
|
490
737
|
}).pipe(tenantScoped(credentials));
|
|
491
738
|
});
|
|
492
739
|
const deleteSession = Effect.fn("SessionsApi.deleteSession")(function* (credentials, sessionId) {
|
|
@@ -507,6 +754,9 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
507
754
|
const replaceSessionTags = Effect.fn("SessionsApi.replaceSessionTags")(function* (credentials, sessionId, tags) {
|
|
508
755
|
return yield* api.replaceSessionTags(sessionId, { payload: { tags } }).pipe(tenantScoped(credentials));
|
|
509
756
|
});
|
|
757
|
+
const updateSessionProxy = Effect.fn("SessionsApi.updateSessionProxy")(function* (credentials, sessionId, proxy) {
|
|
758
|
+
return yield* api.updateSessionProxy(sessionId, { payload: Schema.encodeSync(Api.UpdateSessionProxyRequestJson)(proxy) }).pipe(tenantScoped(credentials));
|
|
759
|
+
});
|
|
510
760
|
const promoteSession = Effect.fn("SessionsApi.promoteSession")(function* (credentials, sessionId, input) {
|
|
511
761
|
return yield* api.promoteSession(sessionId, { payload: {
|
|
512
762
|
name: input.name,
|
|
@@ -515,17 +765,54 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
515
765
|
tags: input.tags ?? {}
|
|
516
766
|
} }).pipe(tenantScoped(credentials));
|
|
517
767
|
});
|
|
768
|
+
const getSessionCursor = Effect.fn("SessionsApi.getSessionCursor")(function* (credentials, sessionId) {
|
|
769
|
+
return yield* api.getSessionCursor(sessionId, void 0).pipe(tenantScoped(credentials));
|
|
770
|
+
});
|
|
771
|
+
const setSessionCursor = Effect.fn("SessionsApi.setSessionCursor")(function* (credentials, sessionId, visible) {
|
|
772
|
+
return yield* api.setSessionCursor(sessionId, { payload: { visible } }).pipe(tenantScoped(credentials));
|
|
773
|
+
});
|
|
774
|
+
const listSessionRecordings = Effect.fn("SessionsApi.listSessionRecordings")(function* (credentials, sessionId) {
|
|
775
|
+
return yield* api.listSessionRecordings(sessionId, void 0).pipe(tenantScoped(credentials));
|
|
776
|
+
});
|
|
777
|
+
const createSessionRecording = Effect.fn("SessionsApi.createSessionRecording")(function* (credentials, sessionId, input) {
|
|
778
|
+
return yield* api.createSessionRecording(sessionId, { payload: input }).pipe(tenantScoped(credentials));
|
|
779
|
+
});
|
|
780
|
+
const getSessionRecording = Effect.fn("SessionsApi.getSessionRecording")(function* (credentials, sessionId, recordingId) {
|
|
781
|
+
return yield* api.getSessionRecording(sessionId, recordingId, void 0).pipe(tenantScoped(credentials));
|
|
782
|
+
});
|
|
783
|
+
const retargetSessionRecording = Effect.fn("SessionsApi.retargetSessionRecording")(function* (credentials, sessionId, recordingId, targetId) {
|
|
784
|
+
return yield* api.retargetSessionRecording(sessionId, recordingId, { payload: { targetId } }).pipe(tenantScoped(credentials));
|
|
785
|
+
});
|
|
786
|
+
const stopSessionRecording = Effect.fn("SessionsApi.stopSessionRecording")(function* (credentials, sessionId, recordingId) {
|
|
787
|
+
return yield* api.stopSessionRecording(sessionId, recordingId, void 0).pipe(tenantScoped(credentials));
|
|
788
|
+
});
|
|
789
|
+
const listSessionFiles = Effect.fn("SessionsApi.listSessionFiles")(function* (credentials, sessionId) {
|
|
790
|
+
return yield* api.listSessionFiles(sessionId, void 0).pipe(tenantScoped(credentials));
|
|
791
|
+
});
|
|
792
|
+
const captionSessionRecording = Effect.fn("SessionsApi.captionSessionRecording")(function* (credentials, sessionId, recordingId, input) {
|
|
793
|
+
yield* api.captionSessionRecording(sessionId, recordingId, { payload: input }).pipe(tenantScoped(credentials));
|
|
794
|
+
});
|
|
795
|
+
const focusSessionRecording = Effect.fn("SessionsApi.focusSessionRecording")(function* (credentials, sessionId, recordingId, input) {
|
|
796
|
+
yield* api.focusSessionRecording(sessionId, recordingId, { payload: input }).pipe(tenantScoped(credentials));
|
|
797
|
+
});
|
|
798
|
+
const attentionSessionRecording = Effect.fn("SessionsApi.attentionSessionRecording")(function* (credentials, sessionId, recordingId, input) {
|
|
799
|
+
yield* api.attentionSessionRecording(sessionId, recordingId, { payload: input }).pipe(tenantScoped(credentials));
|
|
800
|
+
});
|
|
801
|
+
const createSessionFileDownloadURL = Effect.fn("SessionsApi.createSessionFileDownloadURL")(function* (credentials, sessionId, input) {
|
|
802
|
+
return yield* api.createSessionFileDownloadURL(sessionId, { payload: input }).pipe(tenantScoped(credentials));
|
|
803
|
+
});
|
|
518
804
|
const getBrowserChannels = Effect.fn("SessionsApi.getBrowserChannels")(function* (credentials) {
|
|
519
805
|
return yield* api.listBrowserChannels(void 0).pipe(tenantScoped(credentials));
|
|
520
806
|
});
|
|
521
807
|
const getBrowserStatus = Effect.fn("SessionsApi.getBrowserStatus")(function* (credentials, sessionId, sessionToken) {
|
|
522
808
|
return yield* http.get(`/sessions/${encodeURIComponent(sessionId)}/browser/status`).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(BrowserStatus)), authorize({
|
|
523
809
|
credentials,
|
|
524
|
-
bearerToken: sessionToken
|
|
810
|
+
bearerToken: sessionToken,
|
|
811
|
+
tenantHeader: "tenant-scoped"
|
|
525
812
|
}));
|
|
526
813
|
});
|
|
527
814
|
const downloadSessionRecording = Effect.fn("SessionsApi.downloadSessionRecording")(function* (credentials, sessionId, recordingId, sessionToken) {
|
|
528
|
-
return yield* http.get(
|
|
815
|
+
return yield* http.get(recordingContentPath(sessionId, recordingId)).pipe(Effect.flatMap((response) => Effect.map(response.arrayBuffer, (body) => ({
|
|
529
816
|
blob: new Blob([body], { type: response.headers["content-type"] ?? "" }),
|
|
530
817
|
filename: contentDispositionFilename(response.headers["content-disposition"])
|
|
531
818
|
}))), authorize({
|
|
@@ -534,9 +821,59 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
534
821
|
tenantHeader: "tenant-scoped"
|
|
535
822
|
}));
|
|
536
823
|
});
|
|
824
|
+
const getSessionThumbnail = Effect.fn("SessionsApi.getSessionThumbnail")(function* (credentials, sessionId, targetId) {
|
|
825
|
+
const session = `/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
826
|
+
const path = targetId === void 0 ? `${session}/thumbnail` : `${session}/targets/${encodeURIComponent(targetId)}/thumbnail`;
|
|
827
|
+
return yield* http.get(path).pipe(Effect.flatMap((response) => Effect.map(response.arrayBuffer, (body) => new Blob([body], { type: "image/jpeg" }))), authorize({
|
|
828
|
+
credentials,
|
|
829
|
+
tenantHeader: "tenant-scoped"
|
|
830
|
+
}));
|
|
831
|
+
});
|
|
832
|
+
const streamSessionRecording = (credentials, sessionId, recordingId, sessionToken) => http.get(recordingContentPath(sessionId, recordingId)).pipe(authorize({
|
|
833
|
+
credentials,
|
|
834
|
+
bearerToken: sessionToken,
|
|
835
|
+
tenantHeader: "tenant-scoped"
|
|
836
|
+
}), Effect.map((response) => response.stream.pipe(Stream.catchTag("HttpClientError", (error) => Stream.fromEffect(toApiRequestError(Effect.fail(error)))))), Stream.unwrap);
|
|
837
|
+
const uploadSessionFiles = Effect.fn("SessionsApi.uploadSessionFiles")(function* (credentials, sessionId, files, options = {}) {
|
|
838
|
+
const body = yield* uploadBody(files);
|
|
839
|
+
return (yield* http.post(`/api/sessions/${encodeURIComponent(sessionId)}/files`, {
|
|
840
|
+
body,
|
|
841
|
+
urlParams: compactQuery({ directory: options.directory })
|
|
842
|
+
}).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Api.UploadSessionFiles201)), tenantScoped(credentials))).files;
|
|
843
|
+
});
|
|
844
|
+
const deleteSessionFile = Effect.fn("SessionsApi.deleteSessionFile")(function* (credentials, sessionId, relativePath, options = {}) {
|
|
845
|
+
yield* api.deleteSessionFile(sessionId, { params: compactQuery({
|
|
846
|
+
relativePath,
|
|
847
|
+
recursive: options.recursive
|
|
848
|
+
}) }).pipe(tenantScoped(credentials));
|
|
849
|
+
});
|
|
850
|
+
const createSessionDirectory = Effect.fn("SessionsApi.createSessionDirectory")(function* (credentials, sessionId, relativePath) {
|
|
851
|
+
return yield* api.createSessionDirectory(sessionId, { payload: { relativePath } }).pipe(tenantScoped(credentials));
|
|
852
|
+
});
|
|
853
|
+
const moveSessionFile = Effect.fn("SessionsApi.moveSessionFile")(function* (credentials, sessionId, input) {
|
|
854
|
+
return yield* api.moveSessionFile(sessionId, { payload: input }).pipe(tenantScoped(credentials));
|
|
855
|
+
});
|
|
856
|
+
const uploadLiveSessionFiles = Effect.fn("SessionsApi.uploadLiveSessionFiles")(function* (credentials, sessionId, files, sessionToken) {
|
|
857
|
+
const body = yield* uploadBody(files);
|
|
858
|
+
return (yield* http.post(`/sessions/${encodeURIComponent(sessionId)}/uploads`, { body }).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Api.UploadLiveSessionFiles201)), authorize({
|
|
859
|
+
credentials,
|
|
860
|
+
bearerToken: sessionToken,
|
|
861
|
+
tenantHeader: "tenant-scoped"
|
|
862
|
+
}))).files;
|
|
863
|
+
});
|
|
864
|
+
const setSessionViewport = Effect.fn("SessionsApi.setSessionViewport")(function* (credentials, sessionId, input, sessionToken) {
|
|
865
|
+
return yield* api.setSessionViewport(sessionId, { payload: input }).pipe(authorize({
|
|
866
|
+
credentials,
|
|
867
|
+
bearerToken: sessionToken,
|
|
868
|
+
tenantHeader: "tenant-scoped"
|
|
869
|
+
}));
|
|
870
|
+
});
|
|
537
871
|
return SessionsApi.of({
|
|
538
872
|
listSessions,
|
|
873
|
+
streamSessions: sessions.stream,
|
|
874
|
+
listAllSessions: sessions.listAll,
|
|
539
875
|
getSession,
|
|
876
|
+
exportSessionStorageState,
|
|
540
877
|
getSessionsBulk,
|
|
541
878
|
createSession,
|
|
542
879
|
deleteSession,
|
|
@@ -545,10 +882,31 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
545
882
|
rotateSessionToken,
|
|
546
883
|
rotateCollaborationCapability,
|
|
547
884
|
replaceSessionTags,
|
|
885
|
+
updateSessionProxy,
|
|
548
886
|
promoteSession,
|
|
887
|
+
getSessionCursor,
|
|
888
|
+
setSessionCursor,
|
|
889
|
+
listSessionRecordings,
|
|
890
|
+
createSessionRecording,
|
|
891
|
+
getSessionRecording,
|
|
892
|
+
retargetSessionRecording,
|
|
893
|
+
stopSessionRecording,
|
|
894
|
+
captionSessionRecording,
|
|
895
|
+
focusSessionRecording,
|
|
896
|
+
attentionSessionRecording,
|
|
897
|
+
listSessionFiles,
|
|
898
|
+
createSessionFileDownloadURL,
|
|
549
899
|
getBrowserChannels,
|
|
550
900
|
getBrowserStatus,
|
|
551
|
-
downloadSessionRecording
|
|
901
|
+
downloadSessionRecording,
|
|
902
|
+
getSessionThumbnail,
|
|
903
|
+
streamSessionRecording,
|
|
904
|
+
uploadSessionFiles,
|
|
905
|
+
uploadLiveSessionFiles,
|
|
906
|
+
deleteSessionFile,
|
|
907
|
+
moveSessionFile,
|
|
908
|
+
createSessionDirectory,
|
|
909
|
+
setSessionViewport
|
|
552
910
|
});
|
|
553
911
|
});
|
|
554
912
|
const sessionsApiLayer = Layer.effect(SessionsApi, makeSessionsApi);
|
|
@@ -573,6 +931,13 @@ const makeSnapshotsApi = Effect.gen(function* () {
|
|
|
573
931
|
...tagQuery(params.tags)
|
|
574
932
|
}) }).pipe(tenantScoped(credentials));
|
|
575
933
|
});
|
|
934
|
+
const snapshots = paginated(listSnapshots);
|
|
935
|
+
const getSnapshotByName = Effect.fn("SnapshotsApi.getSnapshotByName")(function* (credentials, name) {
|
|
936
|
+
return yield* snapshots.stream(credentials, {
|
|
937
|
+
name,
|
|
938
|
+
limit: 100
|
|
939
|
+
}).pipe(Stream.filter((candidate) => candidate.name === name), Stream.runHead);
|
|
940
|
+
});
|
|
576
941
|
const updateSnapshot = Effect.fn("SnapshotsApi.updateSnapshot")(function* (credentials, name, input) {
|
|
577
942
|
return yield* api.updateSnapshot(name, { payload: input }).pipe(tenantScoped(credentials));
|
|
578
943
|
});
|
|
@@ -587,6 +952,9 @@ const makeSnapshotsApi = Effect.gen(function* () {
|
|
|
587
952
|
});
|
|
588
953
|
return SnapshotsApi.of({
|
|
589
954
|
listSnapshots,
|
|
955
|
+
streamSnapshots: snapshots.stream,
|
|
956
|
+
listAllSnapshots: snapshots.listAll,
|
|
957
|
+
getSnapshotByName,
|
|
590
958
|
updateSnapshot,
|
|
591
959
|
replaceSnapshotTags,
|
|
592
960
|
deleteSnapshot,
|
|
@@ -596,7 +964,7 @@ const makeSnapshotsApi = Effect.gen(function* () {
|
|
|
596
964
|
const snapshotsApiLayer = Layer.effect(SnapshotsApi, makeSnapshotsApi);
|
|
597
965
|
//#endregion
|
|
598
966
|
//#region src/tenants/service.ts
|
|
599
|
-
/** Tenant administration. */
|
|
967
|
+
/** Tenant administration, and the tenant a tenant token belongs to. */
|
|
600
968
|
var TenantsApi = class extends Context.Service()("@aperture-browser/api-client/TenantsApi") {};
|
|
601
969
|
//#endregion
|
|
602
970
|
//#region src/tenants/layer.ts
|
|
@@ -609,9 +977,17 @@ const makeTenantsApi = Effect.gen(function* () {
|
|
|
609
977
|
limit: params.limit,
|
|
610
978
|
cursor: params.cursor,
|
|
611
979
|
includeDeleted: params.includeDeleted || void 0,
|
|
612
|
-
deleted: params.deleted
|
|
980
|
+
deleted: params.deleted,
|
|
981
|
+
query: params.query
|
|
613
982
|
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
614
983
|
});
|
|
984
|
+
const tenants = paginated(listTenants);
|
|
985
|
+
const getCurrentTenant = Effect.fn("TenantsApi.getCurrentTenant")(function* (credentials) {
|
|
986
|
+
return yield* api.getTenant(void 0).pipe(authorize(Authorization.of(credentials)));
|
|
987
|
+
});
|
|
988
|
+
const updateCurrentTenant = Effect.fn("TenantsApi.updateCurrentTenant")(function* (credentials, input) {
|
|
989
|
+
return yield* api.updateSelectedTenant({ payload: input }).pipe(authorize(Authorization.of(credentials)));
|
|
990
|
+
});
|
|
615
991
|
const createTenant = Effect.fn("TenantsApi.createTenant")(function* (credentials, input) {
|
|
616
992
|
return yield* api.createTenant({ payload: input }).pipe(authorize(Authorization.of(credentials)));
|
|
617
993
|
});
|
|
@@ -626,10 +1002,14 @@ const makeTenantsApi = Effect.gen(function* () {
|
|
|
626
1002
|
});
|
|
627
1003
|
return TenantsApi.of({
|
|
628
1004
|
listTenants,
|
|
1005
|
+
streamTenants: tenants.stream,
|
|
1006
|
+
listAllTenants: tenants.listAll,
|
|
629
1007
|
createTenant,
|
|
630
1008
|
updateTenant,
|
|
631
1009
|
deleteTenant,
|
|
632
|
-
restoreTenant
|
|
1010
|
+
restoreTenant,
|
|
1011
|
+
getCurrentTenant,
|
|
1012
|
+
updateCurrentTenant
|
|
633
1013
|
});
|
|
634
1014
|
});
|
|
635
1015
|
const tenantsApiLayer = Layer.effect(TenantsApi, makeTenantsApi);
|
|
@@ -654,6 +1034,7 @@ const makeTokensApi = Effect.gen(function* () {
|
|
|
654
1034
|
scope: params.scope
|
|
655
1035
|
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
656
1036
|
});
|
|
1037
|
+
const adminTokens = paginated(listAdminTokens);
|
|
657
1038
|
const createAdminToken = Effect.fn("TokensApi.createAdminToken")(function* (credentials, input) {
|
|
658
1039
|
const systemAdmin = input.authorityType === "system_admin";
|
|
659
1040
|
return yield* api.createAdminToken({ payload: {
|
|
@@ -678,6 +1059,7 @@ const makeTokensApi = Effect.gen(function* () {
|
|
|
678
1059
|
scope: params.scope
|
|
679
1060
|
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
680
1061
|
});
|
|
1062
|
+
const tenantTokens = paginated(listTenantTokens);
|
|
681
1063
|
const createTenantToken = Effect.fn("TokensApi.createTenantToken")(function* (credentials, input) {
|
|
682
1064
|
return yield* api.createTenantToken({ payload: {
|
|
683
1065
|
name: input.name,
|
|
@@ -692,9 +1074,13 @@ const makeTokensApi = Effect.gen(function* () {
|
|
|
692
1074
|
});
|
|
693
1075
|
return TokensApi.of({
|
|
694
1076
|
listAdminTokens,
|
|
1077
|
+
streamAdminTokens: adminTokens.stream,
|
|
1078
|
+
listAllAdminTokens: adminTokens.listAll,
|
|
695
1079
|
createAdminToken,
|
|
696
1080
|
revokeAdminToken,
|
|
697
1081
|
listTenantTokens,
|
|
1082
|
+
streamTenantTokens: tenantTokens.stream,
|
|
1083
|
+
listAllTenantTokens: tenantTokens.listAll,
|
|
698
1084
|
createTenantToken,
|
|
699
1085
|
revokeTenantToken
|
|
700
1086
|
});
|
|
@@ -718,6 +1104,7 @@ const makeUsersApi = Effect.gen(function* () {
|
|
|
718
1104
|
disabled: params.disabled
|
|
719
1105
|
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
720
1106
|
});
|
|
1107
|
+
const users = paginated(listUsers);
|
|
721
1108
|
const createUser = Effect.fn("UsersApi.createUser")(function* (credentials, input) {
|
|
722
1109
|
return yield* api.createUser({ payload: input }).pipe(authorize(Authorization.of(credentials)));
|
|
723
1110
|
});
|
|
@@ -739,6 +1126,9 @@ const makeUsersApi = Effect.gen(function* () {
|
|
|
739
1126
|
const listUserMemberships = Effect.fn("UsersApi.listUserMemberships")(function* (credentials, userId) {
|
|
740
1127
|
return yield* api.listUserMemberships(userId, void 0).pipe(authorize(Authorization.of(credentials)));
|
|
741
1128
|
});
|
|
1129
|
+
const listTenantMemberships = Effect.fn("UsersApi.listTenantMemberships")(function* (credentials, tenantId) {
|
|
1130
|
+
return yield* api.listTenantMemberships(tenantId, void 0).pipe(authorize(Authorization.of(credentials)));
|
|
1131
|
+
});
|
|
742
1132
|
const upsertTenantMembership = Effect.fn("UsersApi.upsertTenantMembership")(function* (credentials, tenantId, userId, scopes) {
|
|
743
1133
|
const payload = { scopes };
|
|
744
1134
|
return yield* api.upsertTenantMembership(tenantId, userId, { payload }).pipe(authorize(Authorization.of(credentials)));
|
|
@@ -748,6 +1138,8 @@ const makeUsersApi = Effect.gen(function* () {
|
|
|
748
1138
|
});
|
|
749
1139
|
return UsersApi.of({
|
|
750
1140
|
listUsers,
|
|
1141
|
+
streamUsers: users.stream,
|
|
1142
|
+
listAllUsers: users.listAll,
|
|
751
1143
|
createUser,
|
|
752
1144
|
getUser,
|
|
753
1145
|
updateUser,
|
|
@@ -755,6 +1147,7 @@ const makeUsersApi = Effect.gen(function* () {
|
|
|
755
1147
|
disableUser,
|
|
756
1148
|
restoreUser,
|
|
757
1149
|
listUserMemberships,
|
|
1150
|
+
listTenantMemberships,
|
|
758
1151
|
upsertTenantMembership,
|
|
759
1152
|
deleteTenantMembership
|
|
760
1153
|
});
|
|
@@ -766,22 +1159,104 @@ const usersApiLayer = Layer.effect(UsersApi, makeUsersApi);
|
|
|
766
1159
|
* Every API service, sharing one ApiAuthorization. The services send requests through the
|
|
767
1160
|
* HttpClient the app provides, decorated with each call's authorization.
|
|
768
1161
|
*/
|
|
769
|
-
const apiClientLayer = Layer.mergeAll(authApiLayer, tenantsApiLayer, usersApiLayer, sessionsApiLayer, snapshotsApiLayer, tokensApiLayer, eventsApiLayer).pipe(Layer.provide(authorizedHttpClientLayer), Layer.provideMerge(apiAuthorizationLayer));
|
|
1162
|
+
const apiClientLayer = Layer.mergeAll(authApiLayer, tenantsApiLayer, usersApiLayer, sessionsApiLayer, snapshotsApiLayer, tokensApiLayer, eventsApiLayer, healthApiLayer).pipe(Layer.provide(authorizedHttpClientLayer), Layer.provideMerge(apiAuthorizationLayer));
|
|
770
1163
|
const baseUrlLayer = (baseUrl) => Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, HttpClient.mapRequest(HttpClientRequest.prependUrl(baseUrl))));
|
|
771
1164
|
//#endregion
|
|
772
|
-
//#region src/
|
|
773
|
-
|
|
774
|
-
|
|
1165
|
+
//#region src/client.ts
|
|
1166
|
+
/** Binds `credentials` as the first argument of every method of `service`. */
|
|
1167
|
+
function withCredentials(service, credentials) {
|
|
1168
|
+
const methods = Object.entries(service);
|
|
1169
|
+
return Object.fromEntries(methods.map(([name, method]) => [name, (...args) => method(credentials, ...args)]));
|
|
775
1170
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1171
|
+
/**
|
|
1172
|
+
* The credentialed API services with one set of credentials bound, for callers that always
|
|
1173
|
+
* act as the same principal, such as a server holding one API token.
|
|
1174
|
+
*/
|
|
1175
|
+
var ApertureClient = class extends Context.Service()("@aperture-browser/api-client/ApertureClient") {};
|
|
1176
|
+
const makeApertureClient = Effect.fn("makeApertureClient")(function* (credentials) {
|
|
1177
|
+
const auth = yield* AuthApi;
|
|
1178
|
+
return ApertureClient.of({
|
|
1179
|
+
sessions: withCredentials(yield* SessionsApi, credentials),
|
|
1180
|
+
snapshots: withCredentials(yield* SnapshotsApi, credentials),
|
|
1181
|
+
tenants: withCredentials(yield* TenantsApi, credentials),
|
|
1182
|
+
users: withCredentials(yield* UsersApi, credentials),
|
|
1183
|
+
tokens: withCredentials(yield* TokensApi, credentials),
|
|
1184
|
+
events: withCredentials(yield* EventsApi, credentials),
|
|
1185
|
+
auth: { getAuthMe: () => auth.getAuthMe(credentials.selectedTenantId, credentials) },
|
|
1186
|
+
health: yield* HealthApi
|
|
1187
|
+
});
|
|
1188
|
+
});
|
|
1189
|
+
/** ApertureClient acting with `credentials`, over the HttpClient the app provides. */
|
|
1190
|
+
const apertureClientLayer = (credentials) => Layer.effect(ApertureClient, makeApertureClient(credentials)).pipe(Layer.provide(apiClientLayer));
|
|
781
1191
|
//#endregion
|
|
782
1192
|
//#region src/schemas.ts
|
|
783
1193
|
const SessionStatus = Api.SessionStatus;
|
|
784
1194
|
//#endregion
|
|
785
|
-
|
|
1195
|
+
//#region src/sessions/tree.ts
|
|
1196
|
+
/**
|
|
1197
|
+
* The top-level directories the session writes into. They always exist and cannot be
|
|
1198
|
+
* deleted, moved or renamed.
|
|
1199
|
+
*/
|
|
1200
|
+
const sessionFileRootDirectories = [
|
|
1201
|
+
"downloads",
|
|
1202
|
+
"recordings",
|
|
1203
|
+
"uploads",
|
|
1204
|
+
"outputs"
|
|
1205
|
+
];
|
|
1206
|
+
const emptyDirectory = () => ({
|
|
1207
|
+
directory: void 0,
|
|
1208
|
+
directories: /* @__PURE__ */ new Map(),
|
|
1209
|
+
files: []
|
|
1210
|
+
});
|
|
1211
|
+
function directoryAt(root, segments) {
|
|
1212
|
+
let directory = root;
|
|
1213
|
+
for (const segment of segments) {
|
|
1214
|
+
let child = directory.directories.get(segment);
|
|
1215
|
+
if (child === void 0) {
|
|
1216
|
+
child = emptyDirectory();
|
|
1217
|
+
directory.directories.set(segment, child);
|
|
1218
|
+
}
|
|
1219
|
+
directory = child;
|
|
1220
|
+
}
|
|
1221
|
+
return directory;
|
|
1222
|
+
}
|
|
1223
|
+
function toNodes(directory, parentPath) {
|
|
1224
|
+
const directories = [...directory.directories.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([name, child]) => {
|
|
1225
|
+
const relativePath = parentPath === "" ? name : `${parentPath}/${name}`;
|
|
1226
|
+
return {
|
|
1227
|
+
kind: "directory",
|
|
1228
|
+
name,
|
|
1229
|
+
relativePath,
|
|
1230
|
+
directory: child.directory,
|
|
1231
|
+
children: toNodes(child, relativePath)
|
|
1232
|
+
};
|
|
1233
|
+
});
|
|
1234
|
+
const files = [...directory.files].sort((left, right) => left.name.localeCompare(right.name));
|
|
1235
|
+
return [...directories, ...files];
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Arranges `listSessionFiles` results into a tree: directories first, then files, each
|
|
1239
|
+
* sorted by name. Empty directories appear because the listing includes them.
|
|
1240
|
+
*/
|
|
1241
|
+
function sessionFileTree(entries) {
|
|
1242
|
+
const root = emptyDirectory();
|
|
1243
|
+
for (const entry of entries) {
|
|
1244
|
+
const segments = entry.relativePath.split("/");
|
|
1245
|
+
if (entry.type === "directory") {
|
|
1246
|
+
directoryAt(root, segments).directory = entry;
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
const name = segments.pop() ?? entry.relativePath;
|
|
1250
|
+
directoryAt(root, segments).files.push({
|
|
1251
|
+
kind: "file",
|
|
1252
|
+
name,
|
|
1253
|
+
relativePath: entry.relativePath,
|
|
1254
|
+
file: entry
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
return toNodes(root, "");
|
|
1258
|
+
}
|
|
1259
|
+
//#endregion
|
|
1260
|
+
export { ApertureClient, ApiAuthorization, ApiErrorBody, ApiRequestError, AuthApi, Authorization, BrowserPage, BrowserStatus, EventsApi, HealthApi, LiveSessionErrorBody, LoginMethods, OAuthAuthorizationRequest, OAuthClient, OAuthGrant, OAuthGrants, OAuthRedirect, Passkey, PasskeyLoginOptions, PasskeyMutation, PasskeyRegistrationOptions, Passkeys, PasswordLoginResponse, Recording, RecordingSettings, RecoveryCodes, SecurityStatus, SessionStatus, SessionsApi, SnapshotsApi, TENANT_HEADER, TOTPEnrollment, TenantsApi, TokensApi, UsersApi, apertureClientLayer, apiAuthorizationLayer, apiClientLayer, authApiLayer, authorizedHttpClientLayer, baseUrlLayer, compactQuery, contentDispositionFilename, defaultListLimit, eventsApiLayer, flattenInfinitePages, getNextPageParam, healthApiLayer, listQueryDefaults, makeApertureClient, makeApiAuthorization, makeAuthApi, makeEventsApi, makeHealthApi, makeSessionsApi, makeSnapshotsApi, makeTenantsApi, makeTokensApi, makeUsersApi, paginated, resolveTenantHeader, sessionFileRootDirectories, sessionFileTree, sessionsApiLayer, snapshotsApiLayer, tagQuery, tenantsApiLayer, toApiRequestError, tokensApiLayer, usersApiLayer, webSessionCredentials, withCredentials };
|
|
786
1261
|
|
|
787
1262
|
//# sourceMappingURL=index.js.map
|