@aperture-browser/api-client 0.0.0-init.0 → 0.0.0-nightly.100.78b4e7ef6edc
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 +636 -404
- package/dist/index.js +533 -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,98 @@ 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 BrowserPage = Schema.Struct({
|
|
552
|
+
targetId: Schema.String,
|
|
553
|
+
state: Schema.Literals([
|
|
554
|
+
"pending",
|
|
555
|
+
"ready",
|
|
556
|
+
"unavailable",
|
|
557
|
+
"closed"
|
|
558
|
+
]),
|
|
559
|
+
title: Schema.String,
|
|
560
|
+
url: Schema.String,
|
|
561
|
+
viewport: Schema.optionalKey(Schema.Struct({
|
|
562
|
+
width: positiveInt,
|
|
563
|
+
height: positiveInt,
|
|
564
|
+
deviceScaleFactor: Schema.Number.check(Schema.isGreaterThan(0)),
|
|
565
|
+
contentWidth: positiveInt,
|
|
566
|
+
contentHeight: positiveInt,
|
|
567
|
+
canvasWidth: positiveInt,
|
|
568
|
+
canvasHeight: positiveInt
|
|
569
|
+
})),
|
|
570
|
+
thumbnailAvailable: Schema.Boolean
|
|
571
|
+
});
|
|
572
|
+
const browserStatusCommon = {
|
|
402
573
|
sessionId: Schema.String,
|
|
403
574
|
cdpUrl: Schema.String,
|
|
404
575
|
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
|
-
|
|
576
|
+
thumbnailAvailable: Schema.Boolean,
|
|
577
|
+
pages: Schema.Array(BrowserPage).pipe(Schema.withDecodingDefaultKey(emptyArray))
|
|
578
|
+
};
|
|
579
|
+
const availableBrowserStatus = {
|
|
580
|
+
...browserStatusCommon,
|
|
581
|
+
capturedAt: Schema.String,
|
|
582
|
+
representativeTargetId: Schema.optionalKey(Schema.String)
|
|
583
|
+
};
|
|
584
|
+
/** Passive page discovery. Reading it never wakes or keeps a browser session alive. */
|
|
585
|
+
const BrowserStatus = Schema.Union([
|
|
586
|
+
Schema.Struct({
|
|
587
|
+
...availableBrowserStatus,
|
|
588
|
+
status: Schema.Literal("running"),
|
|
589
|
+
source: Schema.Literal("live")
|
|
590
|
+
}),
|
|
591
|
+
Schema.Struct({
|
|
592
|
+
...availableBrowserStatus,
|
|
593
|
+
status: Schema.Literal("suspended"),
|
|
594
|
+
source: Schema.Literal("persisted")
|
|
595
|
+
}),
|
|
596
|
+
Schema.Struct({
|
|
597
|
+
...browserStatusCommon,
|
|
598
|
+
status: Schema.Literal("suspended"),
|
|
599
|
+
source: Schema.Literal("unavailable")
|
|
600
|
+
})
|
|
601
|
+
]);
|
|
602
|
+
const recordingFields = {
|
|
429
603
|
recordingId: Schema.String,
|
|
430
604
|
mode: Schema.Literals(["tab", "viewer"]),
|
|
431
605
|
targetId: Schema.String,
|
|
@@ -437,21 +611,81 @@ const Recording = Schema.Struct({
|
|
|
437
611
|
"failed"
|
|
438
612
|
]),
|
|
439
613
|
stopReason: Schema.optionalKey(Schema.String),
|
|
440
|
-
|
|
614
|
+
sandboxPath: Schema.optionalKey(Schema.String),
|
|
615
|
+
/** @deprecated Read `relativePath`. Sessions started before it existed send only this. */
|
|
616
|
+
path: Schema.optionalKey(Schema.String),
|
|
441
617
|
startedAt: Schema.String,
|
|
442
618
|
stoppedAt: Schema.optionalKey(Schema.String),
|
|
443
619
|
sizeBytes: Schema.optionalKey(Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))),
|
|
444
620
|
fps: positiveInt,
|
|
445
621
|
bitrateKbps: positiveInt,
|
|
446
|
-
codec: Schema.String
|
|
447
|
-
|
|
622
|
+
codec: Schema.String,
|
|
623
|
+
/** The stop returned and the edit is still running; `editedRelativePath` or `editError` follows. */
|
|
624
|
+
editing: Schema.Boolean,
|
|
625
|
+
editedRelativePath: Schema.optionalKey(Schema.String),
|
|
626
|
+
timelineRelativePath: Schema.optionalKey(Schema.String),
|
|
627
|
+
editError: Schema.optionalKey(Api.RecordingEditError)
|
|
628
|
+
};
|
|
629
|
+
/**
|
|
630
|
+
* A recording as the live session reports it. Sessions that are still running an
|
|
631
|
+
* older wrapper send only `path`, which decoding carries over into `relativePath`.
|
|
632
|
+
*/
|
|
633
|
+
const Recording = Schema.Struct({
|
|
634
|
+
...recordingFields,
|
|
635
|
+
relativePath: Schema.optionalKey(Schema.String)
|
|
636
|
+
}).pipe(Schema.decodeTo(Schema.Struct({
|
|
637
|
+
...recordingFields,
|
|
638
|
+
relativePath: Schema.String
|
|
639
|
+
}), SchemaTransformation.transform({
|
|
640
|
+
decode: ({ relativePath, ...recording }) => ({
|
|
641
|
+
...recording,
|
|
642
|
+
relativePath: relativePath ?? recording.path ?? ""
|
|
643
|
+
}),
|
|
644
|
+
encode: (recording) => recording
|
|
645
|
+
})));
|
|
448
646
|
//#endregion
|
|
449
647
|
//#region src/sessions/service.ts
|
|
450
648
|
/** Browser sessions, their live browser, and their recordings. */
|
|
451
649
|
var SessionsApi = class extends Context.Service()("@aperture-browser/api-client/SessionsApi") {};
|
|
452
650
|
//#endregion
|
|
651
|
+
//#region src/sessions/uploads.ts
|
|
652
|
+
const encoder = new TextEncoder();
|
|
653
|
+
const quotedFilename = (name) => name.replaceAll("\"", "%22").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
|
|
654
|
+
const contentStream = (content) => {
|
|
655
|
+
if (content instanceof Blob) return Stream.fromReadableStream({
|
|
656
|
+
evaluate: () => content.stream(),
|
|
657
|
+
onError: (cause) => cause
|
|
658
|
+
});
|
|
659
|
+
if (content instanceof Uint8Array) return Stream.succeed(content);
|
|
660
|
+
return content;
|
|
661
|
+
};
|
|
662
|
+
const streamedMultipart = Effect.fn("SessionsApi.streamedMultipart")(function* (files) {
|
|
663
|
+
const boundary = `aperture-upload-${(yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER)).toString(36)}`;
|
|
664
|
+
const parts = files.map((file) => {
|
|
665
|
+
const header = `--${boundary}\r\nContent-Disposition: form-data; name="files"; filename="${quotedFilename(file.name)}"\r\nContent-Type: application/octet-stream\r
|
|
666
|
+
\r
|
|
667
|
+
`;
|
|
668
|
+
return Stream.succeed(encoder.encode(header)).pipe(Stream.concat(contentStream(file.content)), Stream.concat(Stream.succeed(encoder.encode("\r\n"))));
|
|
669
|
+
});
|
|
670
|
+
const body = Stream.fromIterable(parts).pipe(Stream.flatten(), Stream.concat(Stream.succeed(encoder.encode(`--${boundary}--\r\n`))));
|
|
671
|
+
return HttpBody.stream(body, `multipart/form-data; boundary=${boundary}`);
|
|
672
|
+
});
|
|
673
|
+
/**
|
|
674
|
+
* The multipart body of an upload. FormData works with every fetch, so it is used unless a
|
|
675
|
+
* content is a stream, which needs the body itself to be streamed.
|
|
676
|
+
*/
|
|
677
|
+
const uploadBody = Effect.fn("SessionsApi.uploadBody")(function* (files) {
|
|
678
|
+
const form = new FormData();
|
|
679
|
+
for (const file of files) if (file.content instanceof Blob) form.append("files", file.content, file.name);
|
|
680
|
+
else if (file.content instanceof Uint8Array) form.append("files", new Blob([new Uint8Array(file.content)]), file.name);
|
|
681
|
+
else return yield* streamedMultipart(files);
|
|
682
|
+
return HttpBody.formData(form);
|
|
683
|
+
});
|
|
684
|
+
//#endregion
|
|
453
685
|
//#region src/sessions/layer.ts
|
|
454
686
|
const contentDispositionFilename = (header) => header?.match(/filename="([^"]+)"/)?.[1] ?? null;
|
|
687
|
+
const recordingContentPath = (sessionId, recordingId) => `/sessions/${encodeURIComponent(sessionId)}/recordings/${encodeURIComponent(recordingId)}/content`;
|
|
688
|
+
const BULK_SESSION_IDS_LIMIT = 100;
|
|
455
689
|
const makeSessionsApi = Effect.gen(function* () {
|
|
456
690
|
const httpClient = yield* HttpClient.HttpClient;
|
|
457
691
|
const { authorize } = yield* ApiAuthorization;
|
|
@@ -464,19 +698,24 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
464
698
|
cursor: params.cursor,
|
|
465
699
|
includeDeleted: params.includeDeleted || void 0,
|
|
466
700
|
status: params.status,
|
|
701
|
+
query: params.query,
|
|
467
702
|
...tagQuery(params.tags)
|
|
468
703
|
}) }).pipe(tenantScoped(credentials));
|
|
469
704
|
});
|
|
705
|
+
const sessions = paginated(listSessions);
|
|
706
|
+
const exportSessionStorageState = Effect.fn("SessionsApi.exportSessionStorageState")(function* (credentials, sessionId, input) {
|
|
707
|
+
return yield* api.exportSessionStorageState(sessionId, { payload: input }).pipe(tenantScoped(credentials));
|
|
708
|
+
});
|
|
470
709
|
const getSession = Effect.fn("SessionsApi.getSession")(function* (credentials, sessionId) {
|
|
471
710
|
return yield* api.getSession(sessionId, void 0).pipe(tenantScoped(credentials));
|
|
472
711
|
});
|
|
473
712
|
const getSessionsBulk = Effect.fn("SessionsApi.getSessionsBulk")(function* (credentials, sessionIds) {
|
|
474
|
-
return yield* api.getSessionsBulk({ payload: { ids
|
|
713
|
+
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
714
|
});
|
|
476
715
|
const createSession = Effect.fn("SessionsApi.createSession")(function* (credentials, input, options = {}) {
|
|
477
716
|
return yield* api.createSession({
|
|
478
717
|
params: compactQuery({ waitForReady: options.waitForReady }),
|
|
479
|
-
payload: {
|
|
718
|
+
payload: Schema.encodeSync(Api.CreateSessionRequestJson)({
|
|
480
719
|
baseSnapshotName: input.baseSnapshotName ?? null,
|
|
481
720
|
label: input.label ?? null,
|
|
482
721
|
browser: {
|
|
@@ -485,8 +724,9 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
485
724
|
},
|
|
486
725
|
initialTargets: input.initialTargets ?? [],
|
|
487
726
|
...input.storageState === void 0 ? {} : { storageState: input.storageState },
|
|
488
|
-
tags: input.tags ?? {}
|
|
489
|
-
|
|
727
|
+
tags: input.tags ?? {},
|
|
728
|
+
...input.proxy === void 0 ? {} : { proxy: input.proxy }
|
|
729
|
+
})
|
|
490
730
|
}).pipe(tenantScoped(credentials));
|
|
491
731
|
});
|
|
492
732
|
const deleteSession = Effect.fn("SessionsApi.deleteSession")(function* (credentials, sessionId) {
|
|
@@ -507,6 +747,9 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
507
747
|
const replaceSessionTags = Effect.fn("SessionsApi.replaceSessionTags")(function* (credentials, sessionId, tags) {
|
|
508
748
|
return yield* api.replaceSessionTags(sessionId, { payload: { tags } }).pipe(tenantScoped(credentials));
|
|
509
749
|
});
|
|
750
|
+
const updateSessionProxy = Effect.fn("SessionsApi.updateSessionProxy")(function* (credentials, sessionId, proxy) {
|
|
751
|
+
return yield* api.updateSessionProxy(sessionId, { payload: Schema.encodeSync(Api.UpdateSessionProxyRequestJson)(proxy) }).pipe(tenantScoped(credentials));
|
|
752
|
+
});
|
|
510
753
|
const promoteSession = Effect.fn("SessionsApi.promoteSession")(function* (credentials, sessionId, input) {
|
|
511
754
|
return yield* api.promoteSession(sessionId, { payload: {
|
|
512
755
|
name: input.name,
|
|
@@ -515,17 +758,45 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
515
758
|
tags: input.tags ?? {}
|
|
516
759
|
} }).pipe(tenantScoped(credentials));
|
|
517
760
|
});
|
|
761
|
+
const getSessionCursor = Effect.fn("SessionsApi.getSessionCursor")(function* (credentials, sessionId) {
|
|
762
|
+
return yield* api.getSessionCursor(sessionId, void 0).pipe(tenantScoped(credentials));
|
|
763
|
+
});
|
|
764
|
+
const setSessionCursor = Effect.fn("SessionsApi.setSessionCursor")(function* (credentials, sessionId, visible) {
|
|
765
|
+
return yield* api.setSessionCursor(sessionId, { payload: { visible } }).pipe(tenantScoped(credentials));
|
|
766
|
+
});
|
|
767
|
+
const listSessionRecordings = Effect.fn("SessionsApi.listSessionRecordings")(function* (credentials, sessionId) {
|
|
768
|
+
return yield* api.listSessionRecordings(sessionId, void 0).pipe(tenantScoped(credentials));
|
|
769
|
+
});
|
|
770
|
+
const createSessionRecording = Effect.fn("SessionsApi.createSessionRecording")(function* (credentials, sessionId, input) {
|
|
771
|
+
return yield* api.createSessionRecording(sessionId, { payload: input }).pipe(tenantScoped(credentials));
|
|
772
|
+
});
|
|
773
|
+
const getSessionRecording = Effect.fn("SessionsApi.getSessionRecording")(function* (credentials, sessionId, recordingId) {
|
|
774
|
+
return yield* api.getSessionRecording(sessionId, recordingId, void 0).pipe(tenantScoped(credentials));
|
|
775
|
+
});
|
|
776
|
+
const retargetSessionRecording = Effect.fn("SessionsApi.retargetSessionRecording")(function* (credentials, sessionId, recordingId, targetId) {
|
|
777
|
+
return yield* api.retargetSessionRecording(sessionId, recordingId, { payload: { targetId } }).pipe(tenantScoped(credentials));
|
|
778
|
+
});
|
|
779
|
+
const stopSessionRecording = Effect.fn("SessionsApi.stopSessionRecording")(function* (credentials, sessionId, recordingId) {
|
|
780
|
+
return yield* api.stopSessionRecording(sessionId, recordingId, void 0).pipe(tenantScoped(credentials));
|
|
781
|
+
});
|
|
782
|
+
const listSessionFiles = Effect.fn("SessionsApi.listSessionFiles")(function* (credentials, sessionId) {
|
|
783
|
+
return yield* api.listSessionFiles(sessionId, void 0).pipe(tenantScoped(credentials));
|
|
784
|
+
});
|
|
785
|
+
const createSessionFileDownloadURL = Effect.fn("SessionsApi.createSessionFileDownloadURL")(function* (credentials, sessionId, input) {
|
|
786
|
+
return yield* api.createSessionFileDownloadURL(sessionId, { payload: input }).pipe(tenantScoped(credentials));
|
|
787
|
+
});
|
|
518
788
|
const getBrowserChannels = Effect.fn("SessionsApi.getBrowserChannels")(function* (credentials) {
|
|
519
789
|
return yield* api.listBrowserChannels(void 0).pipe(tenantScoped(credentials));
|
|
520
790
|
});
|
|
521
791
|
const getBrowserStatus = Effect.fn("SessionsApi.getBrowserStatus")(function* (credentials, sessionId, sessionToken) {
|
|
522
792
|
return yield* http.get(`/sessions/${encodeURIComponent(sessionId)}/browser/status`).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(BrowserStatus)), authorize({
|
|
523
793
|
credentials,
|
|
524
|
-
bearerToken: sessionToken
|
|
794
|
+
bearerToken: sessionToken,
|
|
795
|
+
tenantHeader: "tenant-scoped"
|
|
525
796
|
}));
|
|
526
797
|
});
|
|
527
798
|
const downloadSessionRecording = Effect.fn("SessionsApi.downloadSessionRecording")(function* (credentials, sessionId, recordingId, sessionToken) {
|
|
528
|
-
return yield* http.get(
|
|
799
|
+
return yield* http.get(recordingContentPath(sessionId, recordingId)).pipe(Effect.flatMap((response) => Effect.map(response.arrayBuffer, (body) => ({
|
|
529
800
|
blob: new Blob([body], { type: response.headers["content-type"] ?? "" }),
|
|
530
801
|
filename: contentDispositionFilename(response.headers["content-disposition"])
|
|
531
802
|
}))), authorize({
|
|
@@ -534,9 +805,59 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
534
805
|
tenantHeader: "tenant-scoped"
|
|
535
806
|
}));
|
|
536
807
|
});
|
|
808
|
+
const getSessionThumbnail = Effect.fn("SessionsApi.getSessionThumbnail")(function* (credentials, sessionId, targetId) {
|
|
809
|
+
const session = `/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
810
|
+
const path = targetId === void 0 ? `${session}/thumbnail` : `${session}/targets/${encodeURIComponent(targetId)}/thumbnail`;
|
|
811
|
+
return yield* http.get(path).pipe(Effect.flatMap((response) => Effect.map(response.arrayBuffer, (body) => new Blob([body], { type: "image/jpeg" }))), authorize({
|
|
812
|
+
credentials,
|
|
813
|
+
tenantHeader: "tenant-scoped"
|
|
814
|
+
}));
|
|
815
|
+
});
|
|
816
|
+
const streamSessionRecording = (credentials, sessionId, recordingId, sessionToken) => http.get(recordingContentPath(sessionId, recordingId)).pipe(authorize({
|
|
817
|
+
credentials,
|
|
818
|
+
bearerToken: sessionToken,
|
|
819
|
+
tenantHeader: "tenant-scoped"
|
|
820
|
+
}), Effect.map((response) => response.stream.pipe(Stream.catchTag("HttpClientError", (error) => Stream.fromEffect(toApiRequestError(Effect.fail(error)))))), Stream.unwrap);
|
|
821
|
+
const uploadSessionFiles = Effect.fn("SessionsApi.uploadSessionFiles")(function* (credentials, sessionId, files, options = {}) {
|
|
822
|
+
const body = yield* uploadBody(files);
|
|
823
|
+
return (yield* http.post(`/api/sessions/${encodeURIComponent(sessionId)}/files`, {
|
|
824
|
+
body,
|
|
825
|
+
urlParams: compactQuery({ directory: options.directory })
|
|
826
|
+
}).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Api.UploadSessionFiles201)), tenantScoped(credentials))).files;
|
|
827
|
+
});
|
|
828
|
+
const deleteSessionFile = Effect.fn("SessionsApi.deleteSessionFile")(function* (credentials, sessionId, relativePath, options = {}) {
|
|
829
|
+
yield* api.deleteSessionFile(sessionId, { params: compactQuery({
|
|
830
|
+
relativePath,
|
|
831
|
+
recursive: options.recursive
|
|
832
|
+
}) }).pipe(tenantScoped(credentials));
|
|
833
|
+
});
|
|
834
|
+
const createSessionDirectory = Effect.fn("SessionsApi.createSessionDirectory")(function* (credentials, sessionId, relativePath) {
|
|
835
|
+
return yield* api.createSessionDirectory(sessionId, { payload: { relativePath } }).pipe(tenantScoped(credentials));
|
|
836
|
+
});
|
|
837
|
+
const moveSessionFile = Effect.fn("SessionsApi.moveSessionFile")(function* (credentials, sessionId, input) {
|
|
838
|
+
return yield* api.moveSessionFile(sessionId, { payload: input }).pipe(tenantScoped(credentials));
|
|
839
|
+
});
|
|
840
|
+
const uploadLiveSessionFiles = Effect.fn("SessionsApi.uploadLiveSessionFiles")(function* (credentials, sessionId, files, sessionToken) {
|
|
841
|
+
const body = yield* uploadBody(files);
|
|
842
|
+
return (yield* http.post(`/sessions/${encodeURIComponent(sessionId)}/uploads`, { body }).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Api.UploadLiveSessionFiles201)), authorize({
|
|
843
|
+
credentials,
|
|
844
|
+
bearerToken: sessionToken,
|
|
845
|
+
tenantHeader: "tenant-scoped"
|
|
846
|
+
}))).files;
|
|
847
|
+
});
|
|
848
|
+
const setSessionViewport = Effect.fn("SessionsApi.setSessionViewport")(function* (credentials, sessionId, input, sessionToken) {
|
|
849
|
+
return yield* api.setSessionViewport(sessionId, { payload: input }).pipe(authorize({
|
|
850
|
+
credentials,
|
|
851
|
+
bearerToken: sessionToken,
|
|
852
|
+
tenantHeader: "tenant-scoped"
|
|
853
|
+
}));
|
|
854
|
+
});
|
|
537
855
|
return SessionsApi.of({
|
|
538
856
|
listSessions,
|
|
857
|
+
streamSessions: sessions.stream,
|
|
858
|
+
listAllSessions: sessions.listAll,
|
|
539
859
|
getSession,
|
|
860
|
+
exportSessionStorageState,
|
|
540
861
|
getSessionsBulk,
|
|
541
862
|
createSession,
|
|
542
863
|
deleteSession,
|
|
@@ -545,10 +866,28 @@ const makeSessionsApi = Effect.gen(function* () {
|
|
|
545
866
|
rotateSessionToken,
|
|
546
867
|
rotateCollaborationCapability,
|
|
547
868
|
replaceSessionTags,
|
|
869
|
+
updateSessionProxy,
|
|
548
870
|
promoteSession,
|
|
871
|
+
getSessionCursor,
|
|
872
|
+
setSessionCursor,
|
|
873
|
+
listSessionRecordings,
|
|
874
|
+
createSessionRecording,
|
|
875
|
+
getSessionRecording,
|
|
876
|
+
retargetSessionRecording,
|
|
877
|
+
stopSessionRecording,
|
|
878
|
+
listSessionFiles,
|
|
879
|
+
createSessionFileDownloadURL,
|
|
549
880
|
getBrowserChannels,
|
|
550
881
|
getBrowserStatus,
|
|
551
|
-
downloadSessionRecording
|
|
882
|
+
downloadSessionRecording,
|
|
883
|
+
getSessionThumbnail,
|
|
884
|
+
streamSessionRecording,
|
|
885
|
+
uploadSessionFiles,
|
|
886
|
+
uploadLiveSessionFiles,
|
|
887
|
+
deleteSessionFile,
|
|
888
|
+
moveSessionFile,
|
|
889
|
+
createSessionDirectory,
|
|
890
|
+
setSessionViewport
|
|
552
891
|
});
|
|
553
892
|
});
|
|
554
893
|
const sessionsApiLayer = Layer.effect(SessionsApi, makeSessionsApi);
|
|
@@ -573,6 +912,13 @@ const makeSnapshotsApi = Effect.gen(function* () {
|
|
|
573
912
|
...tagQuery(params.tags)
|
|
574
913
|
}) }).pipe(tenantScoped(credentials));
|
|
575
914
|
});
|
|
915
|
+
const snapshots = paginated(listSnapshots);
|
|
916
|
+
const getSnapshotByName = Effect.fn("SnapshotsApi.getSnapshotByName")(function* (credentials, name) {
|
|
917
|
+
return yield* snapshots.stream(credentials, {
|
|
918
|
+
name,
|
|
919
|
+
limit: 100
|
|
920
|
+
}).pipe(Stream.filter((candidate) => candidate.name === name), Stream.runHead);
|
|
921
|
+
});
|
|
576
922
|
const updateSnapshot = Effect.fn("SnapshotsApi.updateSnapshot")(function* (credentials, name, input) {
|
|
577
923
|
return yield* api.updateSnapshot(name, { payload: input }).pipe(tenantScoped(credentials));
|
|
578
924
|
});
|
|
@@ -587,6 +933,9 @@ const makeSnapshotsApi = Effect.gen(function* () {
|
|
|
587
933
|
});
|
|
588
934
|
return SnapshotsApi.of({
|
|
589
935
|
listSnapshots,
|
|
936
|
+
streamSnapshots: snapshots.stream,
|
|
937
|
+
listAllSnapshots: snapshots.listAll,
|
|
938
|
+
getSnapshotByName,
|
|
590
939
|
updateSnapshot,
|
|
591
940
|
replaceSnapshotTags,
|
|
592
941
|
deleteSnapshot,
|
|
@@ -596,7 +945,7 @@ const makeSnapshotsApi = Effect.gen(function* () {
|
|
|
596
945
|
const snapshotsApiLayer = Layer.effect(SnapshotsApi, makeSnapshotsApi);
|
|
597
946
|
//#endregion
|
|
598
947
|
//#region src/tenants/service.ts
|
|
599
|
-
/** Tenant administration. */
|
|
948
|
+
/** Tenant administration, and the tenant a tenant token belongs to. */
|
|
600
949
|
var TenantsApi = class extends Context.Service()("@aperture-browser/api-client/TenantsApi") {};
|
|
601
950
|
//#endregion
|
|
602
951
|
//#region src/tenants/layer.ts
|
|
@@ -609,9 +958,17 @@ const makeTenantsApi = Effect.gen(function* () {
|
|
|
609
958
|
limit: params.limit,
|
|
610
959
|
cursor: params.cursor,
|
|
611
960
|
includeDeleted: params.includeDeleted || void 0,
|
|
612
|
-
deleted: params.deleted
|
|
961
|
+
deleted: params.deleted,
|
|
962
|
+
query: params.query
|
|
613
963
|
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
614
964
|
});
|
|
965
|
+
const tenants = paginated(listTenants);
|
|
966
|
+
const getCurrentTenant = Effect.fn("TenantsApi.getCurrentTenant")(function* (credentials) {
|
|
967
|
+
return yield* api.getTenant(void 0).pipe(authorize(Authorization.of(credentials)));
|
|
968
|
+
});
|
|
969
|
+
const updateCurrentTenant = Effect.fn("TenantsApi.updateCurrentTenant")(function* (credentials, input) {
|
|
970
|
+
return yield* api.updateSelectedTenant({ payload: input }).pipe(authorize(Authorization.of(credentials)));
|
|
971
|
+
});
|
|
615
972
|
const createTenant = Effect.fn("TenantsApi.createTenant")(function* (credentials, input) {
|
|
616
973
|
return yield* api.createTenant({ payload: input }).pipe(authorize(Authorization.of(credentials)));
|
|
617
974
|
});
|
|
@@ -626,10 +983,14 @@ const makeTenantsApi = Effect.gen(function* () {
|
|
|
626
983
|
});
|
|
627
984
|
return TenantsApi.of({
|
|
628
985
|
listTenants,
|
|
986
|
+
streamTenants: tenants.stream,
|
|
987
|
+
listAllTenants: tenants.listAll,
|
|
629
988
|
createTenant,
|
|
630
989
|
updateTenant,
|
|
631
990
|
deleteTenant,
|
|
632
|
-
restoreTenant
|
|
991
|
+
restoreTenant,
|
|
992
|
+
getCurrentTenant,
|
|
993
|
+
updateCurrentTenant
|
|
633
994
|
});
|
|
634
995
|
});
|
|
635
996
|
const tenantsApiLayer = Layer.effect(TenantsApi, makeTenantsApi);
|
|
@@ -654,6 +1015,7 @@ const makeTokensApi = Effect.gen(function* () {
|
|
|
654
1015
|
scope: params.scope
|
|
655
1016
|
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
656
1017
|
});
|
|
1018
|
+
const adminTokens = paginated(listAdminTokens);
|
|
657
1019
|
const createAdminToken = Effect.fn("TokensApi.createAdminToken")(function* (credentials, input) {
|
|
658
1020
|
const systemAdmin = input.authorityType === "system_admin";
|
|
659
1021
|
return yield* api.createAdminToken({ payload: {
|
|
@@ -678,6 +1040,7 @@ const makeTokensApi = Effect.gen(function* () {
|
|
|
678
1040
|
scope: params.scope
|
|
679
1041
|
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
680
1042
|
});
|
|
1043
|
+
const tenantTokens = paginated(listTenantTokens);
|
|
681
1044
|
const createTenantToken = Effect.fn("TokensApi.createTenantToken")(function* (credentials, input) {
|
|
682
1045
|
return yield* api.createTenantToken({ payload: {
|
|
683
1046
|
name: input.name,
|
|
@@ -692,9 +1055,13 @@ const makeTokensApi = Effect.gen(function* () {
|
|
|
692
1055
|
});
|
|
693
1056
|
return TokensApi.of({
|
|
694
1057
|
listAdminTokens,
|
|
1058
|
+
streamAdminTokens: adminTokens.stream,
|
|
1059
|
+
listAllAdminTokens: adminTokens.listAll,
|
|
695
1060
|
createAdminToken,
|
|
696
1061
|
revokeAdminToken,
|
|
697
1062
|
listTenantTokens,
|
|
1063
|
+
streamTenantTokens: tenantTokens.stream,
|
|
1064
|
+
listAllTenantTokens: tenantTokens.listAll,
|
|
698
1065
|
createTenantToken,
|
|
699
1066
|
revokeTenantToken
|
|
700
1067
|
});
|
|
@@ -718,6 +1085,7 @@ const makeUsersApi = Effect.gen(function* () {
|
|
|
718
1085
|
disabled: params.disabled
|
|
719
1086
|
}) }).pipe(authorize(Authorization.of(credentials)));
|
|
720
1087
|
});
|
|
1088
|
+
const users = paginated(listUsers);
|
|
721
1089
|
const createUser = Effect.fn("UsersApi.createUser")(function* (credentials, input) {
|
|
722
1090
|
return yield* api.createUser({ payload: input }).pipe(authorize(Authorization.of(credentials)));
|
|
723
1091
|
});
|
|
@@ -739,6 +1107,9 @@ const makeUsersApi = Effect.gen(function* () {
|
|
|
739
1107
|
const listUserMemberships = Effect.fn("UsersApi.listUserMemberships")(function* (credentials, userId) {
|
|
740
1108
|
return yield* api.listUserMemberships(userId, void 0).pipe(authorize(Authorization.of(credentials)));
|
|
741
1109
|
});
|
|
1110
|
+
const listTenantMemberships = Effect.fn("UsersApi.listTenantMemberships")(function* (credentials, tenantId) {
|
|
1111
|
+
return yield* api.listTenantMemberships(tenantId, void 0).pipe(authorize(Authorization.of(credentials)));
|
|
1112
|
+
});
|
|
742
1113
|
const upsertTenantMembership = Effect.fn("UsersApi.upsertTenantMembership")(function* (credentials, tenantId, userId, scopes) {
|
|
743
1114
|
const payload = { scopes };
|
|
744
1115
|
return yield* api.upsertTenantMembership(tenantId, userId, { payload }).pipe(authorize(Authorization.of(credentials)));
|
|
@@ -748,6 +1119,8 @@ const makeUsersApi = Effect.gen(function* () {
|
|
|
748
1119
|
});
|
|
749
1120
|
return UsersApi.of({
|
|
750
1121
|
listUsers,
|
|
1122
|
+
streamUsers: users.stream,
|
|
1123
|
+
listAllUsers: users.listAll,
|
|
751
1124
|
createUser,
|
|
752
1125
|
getUser,
|
|
753
1126
|
updateUser,
|
|
@@ -755,6 +1128,7 @@ const makeUsersApi = Effect.gen(function* () {
|
|
|
755
1128
|
disableUser,
|
|
756
1129
|
restoreUser,
|
|
757
1130
|
listUserMemberships,
|
|
1131
|
+
listTenantMemberships,
|
|
758
1132
|
upsertTenantMembership,
|
|
759
1133
|
deleteTenantMembership
|
|
760
1134
|
});
|
|
@@ -766,22 +1140,104 @@ const usersApiLayer = Layer.effect(UsersApi, makeUsersApi);
|
|
|
766
1140
|
* Every API service, sharing one ApiAuthorization. The services send requests through the
|
|
767
1141
|
* HttpClient the app provides, decorated with each call's authorization.
|
|
768
1142
|
*/
|
|
769
|
-
const apiClientLayer = Layer.mergeAll(authApiLayer, tenantsApiLayer, usersApiLayer, sessionsApiLayer, snapshotsApiLayer, tokensApiLayer, eventsApiLayer).pipe(Layer.provide(authorizedHttpClientLayer), Layer.provideMerge(apiAuthorizationLayer));
|
|
1143
|
+
const apiClientLayer = Layer.mergeAll(authApiLayer, tenantsApiLayer, usersApiLayer, sessionsApiLayer, snapshotsApiLayer, tokensApiLayer, eventsApiLayer, healthApiLayer).pipe(Layer.provide(authorizedHttpClientLayer), Layer.provideMerge(apiAuthorizationLayer));
|
|
770
1144
|
const baseUrlLayer = (baseUrl) => Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, HttpClient.mapRequest(HttpClientRequest.prependUrl(baseUrl))));
|
|
771
1145
|
//#endregion
|
|
772
|
-
//#region src/
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
return pages?.flatMap((page) => page.data) ?? [];
|
|
1146
|
+
//#region src/client.ts
|
|
1147
|
+
/** Binds `credentials` as the first argument of every method of `service`. */
|
|
1148
|
+
function withCredentials(service, credentials) {
|
|
1149
|
+
const methods = Object.entries(service);
|
|
1150
|
+
return Object.fromEntries(methods.map(([name, method]) => [name, (...args) => method(credentials, ...args)]));
|
|
778
1151
|
}
|
|
779
|
-
|
|
780
|
-
|
|
1152
|
+
/**
|
|
1153
|
+
* The credentialed API services with one set of credentials bound, for callers that always
|
|
1154
|
+
* act as the same principal, such as a server holding one API token.
|
|
1155
|
+
*/
|
|
1156
|
+
var ApertureClient = class extends Context.Service()("@aperture-browser/api-client/ApertureClient") {};
|
|
1157
|
+
const makeApertureClient = Effect.fn("makeApertureClient")(function* (credentials) {
|
|
1158
|
+
const auth = yield* AuthApi;
|
|
1159
|
+
return ApertureClient.of({
|
|
1160
|
+
sessions: withCredentials(yield* SessionsApi, credentials),
|
|
1161
|
+
snapshots: withCredentials(yield* SnapshotsApi, credentials),
|
|
1162
|
+
tenants: withCredentials(yield* TenantsApi, credentials),
|
|
1163
|
+
users: withCredentials(yield* UsersApi, credentials),
|
|
1164
|
+
tokens: withCredentials(yield* TokensApi, credentials),
|
|
1165
|
+
events: withCredentials(yield* EventsApi, credentials),
|
|
1166
|
+
auth: { getAuthMe: () => auth.getAuthMe(credentials.selectedTenantId, credentials) },
|
|
1167
|
+
health: yield* HealthApi
|
|
1168
|
+
});
|
|
1169
|
+
});
|
|
1170
|
+
/** ApertureClient acting with `credentials`, over the HttpClient the app provides. */
|
|
1171
|
+
const apertureClientLayer = (credentials) => Layer.effect(ApertureClient, makeApertureClient(credentials)).pipe(Layer.provide(apiClientLayer));
|
|
781
1172
|
//#endregion
|
|
782
1173
|
//#region src/schemas.ts
|
|
783
1174
|
const SessionStatus = Api.SessionStatus;
|
|
784
1175
|
//#endregion
|
|
785
|
-
|
|
1176
|
+
//#region src/sessions/tree.ts
|
|
1177
|
+
/**
|
|
1178
|
+
* The top-level directories the session writes into. They always exist and cannot be
|
|
1179
|
+
* deleted, moved or renamed.
|
|
1180
|
+
*/
|
|
1181
|
+
const sessionFileRootDirectories = [
|
|
1182
|
+
"downloads",
|
|
1183
|
+
"recordings",
|
|
1184
|
+
"uploads",
|
|
1185
|
+
"outputs"
|
|
1186
|
+
];
|
|
1187
|
+
const emptyDirectory = () => ({
|
|
1188
|
+
directory: void 0,
|
|
1189
|
+
directories: /* @__PURE__ */ new Map(),
|
|
1190
|
+
files: []
|
|
1191
|
+
});
|
|
1192
|
+
function directoryAt(root, segments) {
|
|
1193
|
+
let directory = root;
|
|
1194
|
+
for (const segment of segments) {
|
|
1195
|
+
let child = directory.directories.get(segment);
|
|
1196
|
+
if (child === void 0) {
|
|
1197
|
+
child = emptyDirectory();
|
|
1198
|
+
directory.directories.set(segment, child);
|
|
1199
|
+
}
|
|
1200
|
+
directory = child;
|
|
1201
|
+
}
|
|
1202
|
+
return directory;
|
|
1203
|
+
}
|
|
1204
|
+
function toNodes(directory, parentPath) {
|
|
1205
|
+
const directories = [...directory.directories.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([name, child]) => {
|
|
1206
|
+
const relativePath = parentPath === "" ? name : `${parentPath}/${name}`;
|
|
1207
|
+
return {
|
|
1208
|
+
kind: "directory",
|
|
1209
|
+
name,
|
|
1210
|
+
relativePath,
|
|
1211
|
+
directory: child.directory,
|
|
1212
|
+
children: toNodes(child, relativePath)
|
|
1213
|
+
};
|
|
1214
|
+
});
|
|
1215
|
+
const files = [...directory.files].sort((left, right) => left.name.localeCompare(right.name));
|
|
1216
|
+
return [...directories, ...files];
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Arranges `listSessionFiles` results into a tree: directories first, then files, each
|
|
1220
|
+
* sorted by name. Empty directories appear because the listing includes them.
|
|
1221
|
+
*/
|
|
1222
|
+
function sessionFileTree(entries) {
|
|
1223
|
+
const root = emptyDirectory();
|
|
1224
|
+
for (const entry of entries) {
|
|
1225
|
+
const segments = entry.relativePath.split("/");
|
|
1226
|
+
if (entry.type === "directory") {
|
|
1227
|
+
directoryAt(root, segments).directory = entry;
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
const name = segments.pop() ?? entry.relativePath;
|
|
1231
|
+
directoryAt(root, segments).files.push({
|
|
1232
|
+
kind: "file",
|
|
1233
|
+
name,
|
|
1234
|
+
relativePath: entry.relativePath,
|
|
1235
|
+
file: entry
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
return toNodes(root, "");
|
|
1239
|
+
}
|
|
1240
|
+
//#endregion
|
|
1241
|
+
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, 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
1242
|
|
|
787
1243
|
//# sourceMappingURL=index.js.map
|