@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/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/unstable/http/HttpBody";
4
- import * as HttpClient from "effect/unstable/http/HttpClient";
5
- import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
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/unstable/http/HttpClientRequest";
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
- //#endregion
176
- //#region src/auth/service.ts
177
- /** Browser login, account security, and the current principal. */
178
- var AuthApi = class extends Context.Service()("@aperture-browser/api-client/AuthApi") {};
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(ApiErrorBody)(response).pipe(Effect.map(({ error }) => new ApiRequestError({
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(networkError),
329
- SchemaError: () => Effect.fail(invalidResponse(0))
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 token = bearerToken ?? (credentials?.kind === "bearer" ? credentials.token.trim() : void 0);
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
- return EventsApi.of({ listEvents });
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 BrowserStatus = Schema.Struct({
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
- targets: Schema.Array(Schema.Struct({
406
- targetId: Schema.String,
407
- generation: positiveInt,
408
- state: Schema.Literals([
409
- "pending",
410
- "ready",
411
- "unavailable",
412
- "closed"
413
- ]),
414
- title: Schema.String,
415
- url: Schema.String,
416
- viewport: Schema.Struct({
417
- width: positiveInt,
418
- height: positiveInt,
419
- deviceScaleFactor: Schema.Number.check(Schema.isGreaterThan(0)),
420
- contentWidth: positiveInt,
421
- contentHeight: positiveInt,
422
- canvasWidth: positiveInt,
423
- canvasHeight: positiveInt
424
- })
425
- })).pipe(Schema.withDecodingDefaultKey(emptyArray))
426
- });
427
- /** A recording as the live session reports it. */
428
- const Recording = Schema.Struct({
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
- path: Schema.String,
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: sessionIds } }).pipe(tenantScoped(credentials));
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(`/sessions/${encodeURIComponent(sessionId)}/recordings/${encodeURIComponent(recordingId)}/content`).pipe(Effect.flatMap((response) => Effect.map(response.arrayBuffer, (body) => ({
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/pagination.ts
773
- function getNextPageParam(page) {
774
- return page.meta.hasMore ? page.meta.nextCursor : void 0;
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
- function flattenInfinitePages(pages) {
777
- return pages?.flatMap((page) => page.data) ?? [];
778
- }
779
- const defaultListLimit = 50;
780
- const listQueryDefaults = { refetchOnWindowFocus: true };
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
- export { ApiAuthorization, ApiErrorBody, ApiRequestError, AuthApi, Authorization, BrowserStatus, EventsApi, LoginMethods, Passkey, PasskeyLoginOptions, PasskeyMutation, PasskeyRegistrationOptions, Passkeys, PasswordLoginResponse, Recording, RecoveryCodes, SecurityStatus, SessionStatus, SessionsApi, SnapshotsApi, TENANT_HEADER, TOTPEnrollment, TenantsApi, TokensApi, UsersApi, apiAuthorizationLayer, apiClientLayer, authApiLayer, authorizedHttpClientLayer, baseUrlLayer, compactQuery, defaultListLimit, eventsApiLayer, flattenInfinitePages, getNextPageParam, listQueryDefaults, makeApiAuthorization, makeAuthApi, makeEventsApi, makeSessionsApi, makeSnapshotsApi, makeTenantsApi, makeTokensApi, makeUsersApi, resolveTenantHeader, sessionsApiLayer, snapshotsApiLayer, tagQuery, tenantsApiLayer, toApiRequestError, tokensApiLayer, usersApiLayer, webSessionCredentials };
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