@aperture-browser/api-client 0.0.0-init.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,787 @@
1
+ import * as Effect from "effect/Effect";
2
+ 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";
6
+ import * as Api from "@aperture-browser/api-schema";
7
+ import * as Context from "effect/Context";
8
+ import * as Schema from "effect/Schema";
9
+ import * as PubSub from "effect/PubSub";
10
+ import * as Stream from "effect/Stream";
11
+ import * as Function from "effect/Function";
12
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
13
+ //#region src/authorization/service.ts
14
+ const TENANT_HEADER = "X-Aperture-Tenant-Id";
15
+ /** The browser's own login session, sent as a cookie. */
16
+ const webSessionCredentials = {
17
+ kind: "session",
18
+ authorityType: null,
19
+ tenantId: null,
20
+ selectedTenantId: null
21
+ };
22
+ /** Which tenant, if any, a request acts for under the given credentials. */
23
+ function resolveTenantHeader(credentials, mode) {
24
+ if (mode === "none") return;
25
+ if (mode === "optional") return credentials.selectedTenantId ?? void 0;
26
+ if (credentials.authorityType === "tenant") {
27
+ if (credentials.kind === "bearer") return;
28
+ return credentials.tenantId ?? void 0;
29
+ }
30
+ if (credentials.authorityType === "system_admin") return credentials.selectedTenantId ?? void 0;
31
+ }
32
+ const Authorization = {
33
+ anonymous: {},
34
+ webSession: { credentials: webSessionCredentials },
35
+ of: (credentials) => ({ credentials }),
36
+ tenantScoped: (credentials) => ({
37
+ credentials,
38
+ tenantHeader: "tenant-scoped"
39
+ })
40
+ };
41
+ /**
42
+ * Authenticates API calls. Inside `authorize`, every request sent through the HttpClient
43
+ * that `authorizedHttpClientLayer` provides carries that call's credentials and tenant, so
44
+ * callers never touch headers.
45
+ */
46
+ var ApiAuthorization = class extends Context.Service()("@aperture-browser/api-client/ApiAuthorization") {};
47
+ //#endregion
48
+ //#region src/auth/schemas.ts
49
+ const passkeyAuthenticatorTransport = Schema.Literals([
50
+ "ble",
51
+ "cable",
52
+ "hybrid",
53
+ "internal",
54
+ "nfc",
55
+ "smart-card",
56
+ "usb"
57
+ ]);
58
+ const passkeyCredentialDescriptor = Schema.Struct({
59
+ id: Schema.String,
60
+ type: Schema.Literal("public-key"),
61
+ transports: Schema.optionalKey(Schema.mutable(Schema.Array(passkeyAuthenticatorTransport)))
62
+ });
63
+ const passkeyExtensions = Schema.optionalKey(Schema.Struct({
64
+ appid: Schema.optionalKey(Schema.String),
65
+ credProps: Schema.optionalKey(Schema.Boolean),
66
+ hmacCreateSecret: Schema.optionalKey(Schema.Boolean),
67
+ minPinLength: Schema.optionalKey(Schema.Boolean)
68
+ }));
69
+ const passkeyHint = Schema.Literals([
70
+ "hybrid",
71
+ "security-key",
72
+ "client-device"
73
+ ]);
74
+ const passkeyUserVerification = Schema.Literals([
75
+ "discouraged",
76
+ "preferred",
77
+ "required"
78
+ ]);
79
+ const LoginMethods = Schema.Struct({ methods: Schema.Array(Schema.Union([
80
+ Schema.Struct({ type: Schema.Literal("password") }),
81
+ Schema.Struct({ type: Schema.Literal("api_token") }),
82
+ Schema.Struct({ type: Schema.Literal("passkey") }),
83
+ Schema.Struct({
84
+ type: Schema.Literal("oidc"),
85
+ id: Schema.String,
86
+ name: Schema.String,
87
+ loginUrl: Schema.String
88
+ })
89
+ ])) });
90
+ const PasskeyLoginOptions = Schema.Struct({ publicKey: Schema.Struct({
91
+ challenge: Schema.String,
92
+ timeout: Schema.optionalKey(Schema.Number),
93
+ rpId: Schema.optionalKey(Schema.String),
94
+ allowCredentials: Schema.optionalKey(Schema.mutable(Schema.Array(passkeyCredentialDescriptor))),
95
+ userVerification: Schema.optionalKey(passkeyUserVerification),
96
+ hints: Schema.optionalKey(Schema.mutable(Schema.Array(passkeyHint))),
97
+ extensions: passkeyExtensions
98
+ }) });
99
+ const PasskeyRegistrationOptions = Schema.Struct({ publicKey: Schema.Struct({
100
+ rp: Schema.Struct({
101
+ id: Schema.optionalKey(Schema.String),
102
+ name: Schema.String
103
+ }),
104
+ user: Schema.Struct({
105
+ id: Schema.String,
106
+ name: Schema.String,
107
+ displayName: Schema.String
108
+ }),
109
+ challenge: Schema.String,
110
+ pubKeyCredParams: Schema.mutable(Schema.Array(Schema.Struct({
111
+ alg: Schema.Literals([
112
+ -7,
113
+ -8,
114
+ -35,
115
+ -36,
116
+ -37,
117
+ -38,
118
+ -39,
119
+ -257,
120
+ -258,
121
+ -259
122
+ ]),
123
+ type: Schema.Literal("public-key")
124
+ }))),
125
+ timeout: Schema.optionalKey(Schema.Number),
126
+ excludeCredentials: Schema.optionalKey(Schema.mutable(Schema.Array(passkeyCredentialDescriptor))),
127
+ authenticatorSelection: Schema.optionalKey(Schema.Struct({
128
+ authenticatorAttachment: Schema.optionalKey(Schema.Literals(["platform", "cross-platform"])),
129
+ requireResidentKey: Schema.optionalKey(Schema.Boolean),
130
+ residentKey: Schema.optionalKey(Schema.Literals([
131
+ "discouraged",
132
+ "preferred",
133
+ "required"
134
+ ])),
135
+ userVerification: Schema.optionalKey(passkeyUserVerification)
136
+ })),
137
+ hints: Schema.optionalKey(Schema.mutable(Schema.Array(passkeyHint))),
138
+ attestation: Schema.optionalKey(Schema.Literals([
139
+ "direct",
140
+ "enterprise",
141
+ "indirect",
142
+ "none"
143
+ ])),
144
+ attestationFormats: Schema.optionalKey(Schema.mutable(Schema.Array(Schema.Literals([
145
+ "fido-u2f",
146
+ "packed",
147
+ "android-safetynet",
148
+ "android-key",
149
+ "tpm",
150
+ "apple",
151
+ "none"
152
+ ])))),
153
+ extensions: passkeyExtensions
154
+ }) });
155
+ const Passkey = Schema.Struct({
156
+ id: Schema.String,
157
+ name: Schema.String,
158
+ createdAt: Schema.String,
159
+ lastUsedAt: Schema.NullOr(Schema.String)
160
+ });
161
+ const Passkeys = Schema.Struct({ passkeys: Schema.Array(Passkey) });
162
+ const PasskeyMutation = Schema.Struct({ passkey: Passkey });
163
+ const PasswordLoginResponse = Schema.Struct({ mfaRequired: Schema.Boolean });
164
+ const SecurityStatus = Schema.Struct({
165
+ hasPassword: Schema.Boolean,
166
+ totpEnabled: Schema.Boolean,
167
+ recoveryCodesRemaining: Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))
168
+ });
169
+ const TOTPEnrollment = Schema.Struct({
170
+ secret: Schema.String,
171
+ otpauthUrl: Schema.String,
172
+ qrCodeDataUrl: Schema.String
173
+ });
174
+ 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") {};
179
+ //#endregion
180
+ //#region src/auth/layer.ts
181
+ const jsonBody = (body) => ({ body: HttpBody.jsonUnsafe(body) });
182
+ const makeAuthApi = Effect.gen(function* () {
183
+ const httpClient = yield* HttpClient.HttpClient;
184
+ const { authorize } = yield* ApiAuthorization;
185
+ const http = HttpClient.filterStatusOk(httpClient);
186
+ const api = Api.make(httpClient);
187
+ const anonymous = authorize(Authorization.anonymous);
188
+ const webSession = authorize(Authorization.webSession);
189
+ const listLoginMethods = Effect.fn("AuthApi.listLoginMethods")(function* () {
190
+ return yield* http.get("/auth/login-methods").pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(LoginMethods)), anonymous);
191
+ });
192
+ const loginWithPassword = Effect.fn("AuthApi.loginWithPassword")(function* (email, password) {
193
+ return yield* http.post("/auth/password/login", jsonBody({
194
+ email,
195
+ password
196
+ })).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(PasswordLoginResponse)), anonymous);
197
+ });
198
+ const completePasswordMFA = Effect.fn("AuthApi.completePasswordMFA")(function* (code) {
199
+ yield* http.post("/auth/password/login/mfa", jsonBody({ code })).pipe(anonymous);
200
+ });
201
+ const loginWithAPIToken = Effect.fn("AuthApi.loginWithAPIToken")(function* (token) {
202
+ yield* http.post("/auth/token/login", jsonBody({ token })).pipe(anonymous);
203
+ });
204
+ const beginPasskeyLogin = Effect.fn("AuthApi.beginPasskeyLogin")(function* () {
205
+ return yield* http.post("/auth/passkeys/login/options").pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(PasskeyLoginOptions)), anonymous);
206
+ });
207
+ const finishPasskeyLogin = Effect.fn("AuthApi.finishPasskeyLogin")(function* (credential) {
208
+ yield* http.post("/auth/passkeys/login/finish", jsonBody(credential)).pipe(anonymous);
209
+ });
210
+ const acceptUserInvitation = Effect.fn("AuthApi.acceptUserInvitation")(function* (token, password) {
211
+ yield* http.post("/auth/invitations/accept", jsonBody({
212
+ token,
213
+ password
214
+ })).pipe(anonymous);
215
+ });
216
+ const logoutWebSession = Effect.fn("AuthApi.logoutWebSession")(function* () {
217
+ yield* http.post("/auth/logout").pipe(webSession);
218
+ });
219
+ const getAuthMe = Effect.fn("AuthApi.getAuthMe")(function* (selectedTenantId = null, credentials = webSessionCredentials) {
220
+ return yield* api.getCurrentPrincipal(void 0).pipe(authorize({
221
+ credentials: {
222
+ ...credentials,
223
+ selectedTenantId
224
+ },
225
+ tenantHeader: "optional"
226
+ }));
227
+ });
228
+ const getSecurityStatus = Effect.fn("AuthApi.getSecurityStatus")(function* () {
229
+ return yield* http.get("/auth/security").pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(SecurityStatus)), webSession);
230
+ });
231
+ const setPassword = Effect.fn("AuthApi.setPassword")(function* (currentPassword, newPassword) {
232
+ yield* http.put("/auth/password", jsonBody({
233
+ currentPassword,
234
+ newPassword
235
+ })).pipe(webSession);
236
+ });
237
+ const listPasskeys = Effect.fn("AuthApi.listPasskeys")(function* () {
238
+ return yield* http.get("/auth/passkeys").pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Passkeys)), webSession);
239
+ });
240
+ const beginPasskeyRegistration = Effect.fn("AuthApi.beginPasskeyRegistration")(function* (name) {
241
+ return yield* http.post("/auth/passkeys/registration/options", jsonBody({ name })).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(PasskeyRegistrationOptions)), webSession);
242
+ });
243
+ const finishPasskeyRegistration = Effect.fn("AuthApi.finishPasskeyRegistration")(function* (credential) {
244
+ return yield* http.post("/auth/passkeys/registration/finish", jsonBody(credential)).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(PasskeyMutation)), webSession);
245
+ });
246
+ const renamePasskey = Effect.fn("AuthApi.renamePasskey")(function* (passkeyId, name) {
247
+ return yield* http.patch(`/auth/passkeys/${encodeURIComponent(passkeyId)}`, jsonBody({ name })).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(PasskeyMutation)), webSession);
248
+ });
249
+ const deletePasskey = Effect.fn("AuthApi.deletePasskey")(function* (passkeyId) {
250
+ yield* http.del(`/auth/passkeys/${encodeURIComponent(passkeyId)}`).pipe(webSession);
251
+ });
252
+ const beginTOTPEnrollment = Effect.fn("AuthApi.beginTOTPEnrollment")(function* () {
253
+ return yield* http.post("/auth/totp/enrollment/options").pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(TOTPEnrollment)), webSession);
254
+ });
255
+ const completeTOTPEnrollment = Effect.fn("AuthApi.completeTOTPEnrollment")(function* (code) {
256
+ return yield* http.post("/auth/totp/enrollment/finish", jsonBody({ code })).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(RecoveryCodes)), webSession);
257
+ });
258
+ const regenerateRecoveryCodes = Effect.fn("AuthApi.regenerateRecoveryCodes")(function* (code) {
259
+ return yield* http.post("/auth/totp/recovery-codes", jsonBody({ code })).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(RecoveryCodes)), webSession);
260
+ });
261
+ const disableTOTP = Effect.fn("AuthApi.disableTOTP")(function* (code) {
262
+ yield* http.post("/auth/totp/disable", jsonBody({ code })).pipe(webSession);
263
+ });
264
+ return AuthApi.of({
265
+ listLoginMethods,
266
+ loginWithPassword,
267
+ completePasswordMFA,
268
+ loginWithAPIToken,
269
+ beginPasskeyLogin,
270
+ finishPasskeyLogin,
271
+ acceptUserInvitation,
272
+ logoutWebSession,
273
+ getAuthMe,
274
+ getSecurityStatus,
275
+ setPassword,
276
+ listPasskeys,
277
+ beginPasskeyRegistration,
278
+ finishPasskeyRegistration,
279
+ renamePasskey,
280
+ deletePasskey,
281
+ beginTOTPEnrollment,
282
+ completeTOTPEnrollment,
283
+ regenerateRecoveryCodes,
284
+ disableTOTP
285
+ });
286
+ });
287
+ const authApiLayer = Layer.effect(AuthApi, makeAuthApi);
288
+ //#endregion
289
+ //#region src/errors.ts
290
+ const ApiErrorBody = Schema.Struct({ error: Schema.Struct({
291
+ code: Schema.String,
292
+ message: Schema.String
293
+ }) });
294
+ /**
295
+ * 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.
297
+ */
298
+ var ApiRequestError = class extends Schema.TaggedError()("ApiRequestError", {
299
+ code: Schema.String,
300
+ message: Schema.String,
301
+ status: Schema.Number
302
+ }) {};
303
+ const invalidResponse = (status) => new ApiRequestError({
304
+ code: "internal_error",
305
+ 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
312
+ });
313
+ /**
314
+ * Maps HTTP and decoding failures to ApiRequestError. Error responses keep the code and
315
+ * message of the server's error body.
316
+ */
317
+ const toApiRequestError = (self) => self.pipe(Effect.catchReasons("HttpClientError", {
318
+ StatusCodeError: ({ response }) => HttpClientResponse.schemaBodyJson(ApiErrorBody)(response).pipe(Effect.map(({ error }) => new ApiRequestError({
319
+ ...error,
320
+ status: response.status
321
+ })), Effect.orElseSucceed(() => new ApiRequestError({
322
+ code: "internal_error",
323
+ message: "Request failed",
324
+ status: response.status
325
+ })), Effect.flatMap(Effect.fail)),
326
+ DecodeError: ({ response }) => Effect.fail(invalidResponse(response.status))
327
+ }), Effect.catchTags({
328
+ HttpClientError: () => Effect.fail(networkError),
329
+ SchemaError: () => Effect.fail(invalidResponse(0))
330
+ }));
331
+ //#endregion
332
+ //#region src/authorization/layer.ts
333
+ /** The authorization of the call a request belongs to; set by `authorize`. */
334
+ const CurrentAuthorization = Context.Reference("@aperture-browser/api-client/CurrentAuthorization", { defaultValue: () => ({}) });
335
+ const authorizeRequest = (request) => CurrentAuthorization.useSync(({ credentials, bearerToken, tenantHeader = "none" }) => {
336
+ const token = bearerToken ?? (credentials?.kind === "bearer" ? credentials.token.trim() : void 0);
337
+ const tenantId = credentials ? resolveTenantHeader(credentials, tenantHeader) : void 0;
338
+ return request.pipe(HttpClientRequest.acceptJson, token ? HttpClientRequest.bearerToken(token) : Function.identity, tenantId ? HttpClientRequest.setHeader(TENANT_HEADER, tenantId) : HttpClientRequest.removeHeader(TENANT_HEADER));
339
+ });
340
+ const sessionAuthenticationFailureCodes = /* @__PURE__ */ new Set([
341
+ "authentication_required",
342
+ "invalid_authentication_token",
343
+ "authentication_token_expired",
344
+ "authentication_token_revoked",
345
+ "user_disabled"
346
+ ]);
347
+ /**
348
+ * Decorates the HttpClient so each request carries the authorization of the `authorize`
349
+ * call it runs in.
350
+ */
351
+ const authorizedHttpClientLayer = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, HttpClient.mapRequestEffect(authorizeRequest)));
352
+ const makeApiAuthorization = Effect.gen(function* () {
353
+ const failures = yield* PubSub.unbounded();
354
+ const authorize = (authorization) => (self) => self.pipe(toApiRequestError, Effect.tapError((error) => authorization.credentials?.kind === "session" && sessionAuthenticationFailureCodes.has(error.code) ? PubSub.publish(failures, void 0) : Effect.void), Effect.provideService(CurrentAuthorization, authorization));
355
+ return ApiAuthorization.of({
356
+ authorize,
357
+ sessionAuthenticationFailures: Stream.fromPubSub(failures)
358
+ });
359
+ });
360
+ const apiAuthorizationLayer = Layer.effect(ApiAuthorization, makeApiAuthorization);
361
+ //#endregion
362
+ //#region src/query.ts
363
+ /** Encodes tag filters as the parallel tagKey, tagOperator and tagValue query lists. */
364
+ const tagQuery = (tags) => ({
365
+ tagKey: tags?.map((tag) => tag.key),
366
+ tagOperator: tags?.map((tag) => tag.operator),
367
+ tagValue: tags?.map((tag) => tag.values.join(","))
368
+ });
369
+ /** Drops empty strings and empty list items, which mean "no filter". */
370
+ const compactQuery = (query) => Object.fromEntries(Object.entries(query).flatMap(([key, value]) => {
371
+ if (value === void 0 || value === null || value === "") return [];
372
+ if (!Array.isArray(value)) return [[key, value]];
373
+ const items = value.filter((item) => item !== "");
374
+ return items.length > 0 ? [[key, items]] : [];
375
+ }));
376
+ //#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
+ //#region src/events/layer.ts
382
+ const makeEventsApi = Effect.gen(function* () {
383
+ const httpClient = yield* HttpClient.HttpClient;
384
+ const { authorize } = yield* ApiAuthorization;
385
+ const api = Api.make(httpClient);
386
+ const listEvents = Effect.fn("EventsApi.listEvents")(function* (credentials, params = {}) {
387
+ return yield* api.listEvents({ params: compactQuery({
388
+ limit: params.limit,
389
+ cursor: params.cursor,
390
+ resourceType: params.resourceType,
391
+ resourceId: params.resourceId
392
+ }) }).pipe(authorize(Authorization.tenantScoped(credentials)));
393
+ });
394
+ return EventsApi.of({ listEvents });
395
+ });
396
+ const eventsApiLayer = Layer.effect(EventsApi, makeEventsApi);
397
+ //#endregion
398
+ //#region src/sessions/schemas.ts
399
+ const positiveInt = Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0));
400
+ const emptyArray = Effect.succeed([]);
401
+ const BrowserStatus = Schema.Struct({
402
+ sessionId: Schema.String,
403
+ cdpUrl: Schema.String,
404
+ 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({
429
+ recordingId: Schema.String,
430
+ mode: Schema.Literals(["tab", "viewer"]),
431
+ targetId: Schema.String,
432
+ captureGeneration: positiveInt,
433
+ status: Schema.Literals([
434
+ "starting",
435
+ "running",
436
+ "stopped",
437
+ "failed"
438
+ ]),
439
+ stopReason: Schema.optionalKey(Schema.String),
440
+ path: Schema.String,
441
+ startedAt: Schema.String,
442
+ stoppedAt: Schema.optionalKey(Schema.String),
443
+ sizeBytes: Schema.optionalKey(Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))),
444
+ fps: positiveInt,
445
+ bitrateKbps: positiveInt,
446
+ codec: Schema.String
447
+ });
448
+ //#endregion
449
+ //#region src/sessions/service.ts
450
+ /** Browser sessions, their live browser, and their recordings. */
451
+ var SessionsApi = class extends Context.Service()("@aperture-browser/api-client/SessionsApi") {};
452
+ //#endregion
453
+ //#region src/sessions/layer.ts
454
+ const contentDispositionFilename = (header) => header?.match(/filename="([^"]+)"/)?.[1] ?? null;
455
+ const makeSessionsApi = Effect.gen(function* () {
456
+ const httpClient = yield* HttpClient.HttpClient;
457
+ const { authorize } = yield* ApiAuthorization;
458
+ const http = HttpClient.filterStatusOk(httpClient);
459
+ const api = Api.make(httpClient);
460
+ const tenantScoped = (credentials) => authorize(Authorization.tenantScoped(credentials));
461
+ const listSessions = Effect.fn("SessionsApi.listSessions")(function* (credentials, params = {}) {
462
+ return yield* api.listSessions({ params: compactQuery({
463
+ limit: params.limit,
464
+ cursor: params.cursor,
465
+ includeDeleted: params.includeDeleted || void 0,
466
+ status: params.status,
467
+ ...tagQuery(params.tags)
468
+ }) }).pipe(tenantScoped(credentials));
469
+ });
470
+ const getSession = Effect.fn("SessionsApi.getSession")(function* (credentials, sessionId) {
471
+ return yield* api.getSession(sessionId, void 0).pipe(tenantScoped(credentials));
472
+ });
473
+ const getSessionsBulk = Effect.fn("SessionsApi.getSessionsBulk")(function* (credentials, sessionIds) {
474
+ return yield* api.getSessionsBulk({ payload: { ids: sessionIds } }).pipe(tenantScoped(credentials));
475
+ });
476
+ const createSession = Effect.fn("SessionsApi.createSession")(function* (credentials, input, options = {}) {
477
+ return yield* api.createSession({
478
+ params: compactQuery({ waitForReady: options.waitForReady }),
479
+ payload: {
480
+ baseSnapshotName: input.baseSnapshotName ?? null,
481
+ label: input.label ?? null,
482
+ browser: {
483
+ channel: input.browser.channel,
484
+ args: input.browser.args ?? []
485
+ },
486
+ initialTargets: input.initialTargets ?? [],
487
+ ...input.storageState === void 0 ? {} : { storageState: input.storageState },
488
+ tags: input.tags ?? {}
489
+ }
490
+ }).pipe(tenantScoped(credentials));
491
+ });
492
+ const deleteSession = Effect.fn("SessionsApi.deleteSession")(function* (credentials, sessionId) {
493
+ return yield* api.deleteSession(sessionId, void 0).pipe(tenantScoped(credentials));
494
+ });
495
+ const reopenSession = Effect.fn("SessionsApi.reopenSession")(function* (credentials, sessionId) {
496
+ return yield* api.reopenSession(sessionId, void 0).pipe(tenantScoped(credentials));
497
+ });
498
+ const suspendSession = Effect.fn("SessionsApi.suspendSession")(function* (credentials, sessionId) {
499
+ return yield* api.suspendSession(sessionId, void 0).pipe(tenantScoped(credentials));
500
+ });
501
+ const rotateSessionToken = Effect.fn("SessionsApi.rotateSessionToken")(function* (credentials, sessionId) {
502
+ return yield* api.rotateSessionToken(sessionId, void 0).pipe(tenantScoped(credentials));
503
+ });
504
+ const rotateCollaborationCapability = Effect.fn("SessionsApi.rotateCollaborationCapability")(function* (credentials, sessionId, role) {
505
+ return yield* api.rotateCollaborationCapability(sessionId, role, void 0).pipe(tenantScoped(credentials));
506
+ });
507
+ const replaceSessionTags = Effect.fn("SessionsApi.replaceSessionTags")(function* (credentials, sessionId, tags) {
508
+ return yield* api.replaceSessionTags(sessionId, { payload: { tags } }).pipe(tenantScoped(credentials));
509
+ });
510
+ const promoteSession = Effect.fn("SessionsApi.promoteSession")(function* (credentials, sessionId, input) {
511
+ return yield* api.promoteSession(sessionId, { payload: {
512
+ name: input.name,
513
+ description: input.description ?? null,
514
+ force: input.force ?? false,
515
+ tags: input.tags ?? {}
516
+ } }).pipe(tenantScoped(credentials));
517
+ });
518
+ const getBrowserChannels = Effect.fn("SessionsApi.getBrowserChannels")(function* (credentials) {
519
+ return yield* api.listBrowserChannels(void 0).pipe(tenantScoped(credentials));
520
+ });
521
+ const getBrowserStatus = Effect.fn("SessionsApi.getBrowserStatus")(function* (credentials, sessionId, sessionToken) {
522
+ return yield* http.get(`/sessions/${encodeURIComponent(sessionId)}/browser/status`).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(BrowserStatus)), authorize({
523
+ credentials,
524
+ bearerToken: sessionToken
525
+ }));
526
+ });
527
+ 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) => ({
529
+ blob: new Blob([body], { type: response.headers["content-type"] ?? "" }),
530
+ filename: contentDispositionFilename(response.headers["content-disposition"])
531
+ }))), authorize({
532
+ credentials,
533
+ bearerToken: sessionToken,
534
+ tenantHeader: "tenant-scoped"
535
+ }));
536
+ });
537
+ return SessionsApi.of({
538
+ listSessions,
539
+ getSession,
540
+ getSessionsBulk,
541
+ createSession,
542
+ deleteSession,
543
+ reopenSession,
544
+ suspendSession,
545
+ rotateSessionToken,
546
+ rotateCollaborationCapability,
547
+ replaceSessionTags,
548
+ promoteSession,
549
+ getBrowserChannels,
550
+ getBrowserStatus,
551
+ downloadSessionRecording
552
+ });
553
+ });
554
+ const sessionsApiLayer = Layer.effect(SessionsApi, makeSessionsApi);
555
+ //#endregion
556
+ //#region src/snapshots/service.ts
557
+ /** Retained snapshots that new sessions start from. */
558
+ var SnapshotsApi = class extends Context.Service()("@aperture-browser/api-client/SnapshotsApi") {};
559
+ //#endregion
560
+ //#region src/snapshots/layer.ts
561
+ const makeSnapshotsApi = Effect.gen(function* () {
562
+ const httpClient = yield* HttpClient.HttpClient;
563
+ const { authorize } = yield* ApiAuthorization;
564
+ const api = Api.make(httpClient);
565
+ const tenantScoped = (credentials) => authorize(Authorization.tenantScoped(credentials));
566
+ const listSnapshots = Effect.fn("SnapshotsApi.listSnapshots")(function* (credentials, params = {}) {
567
+ return yield* api.listSnapshots({ params: compactQuery({
568
+ limit: params.limit,
569
+ cursor: params.cursor,
570
+ includeDeleted: params.includeDeleted || void 0,
571
+ deleted: params.deleted,
572
+ name: params.name,
573
+ ...tagQuery(params.tags)
574
+ }) }).pipe(tenantScoped(credentials));
575
+ });
576
+ const updateSnapshot = Effect.fn("SnapshotsApi.updateSnapshot")(function* (credentials, name, input) {
577
+ return yield* api.updateSnapshot(name, { payload: input }).pipe(tenantScoped(credentials));
578
+ });
579
+ const replaceSnapshotTags = Effect.fn("SnapshotsApi.replaceSnapshotTags")(function* (credentials, name, tags) {
580
+ return yield* api.replaceSnapshotTags(name, { payload: { tags } }).pipe(tenantScoped(credentials));
581
+ });
582
+ const deleteSnapshot = Effect.fn("SnapshotsApi.deleteSnapshot")(function* (credentials, name) {
583
+ return yield* api.deleteSnapshot(name, void 0).pipe(tenantScoped(credentials));
584
+ });
585
+ const restoreSnapshot = Effect.fn("SnapshotsApi.restoreSnapshot")(function* (credentials, name) {
586
+ return yield* api.restoreSnapshot(name, void 0).pipe(tenantScoped(credentials));
587
+ });
588
+ return SnapshotsApi.of({
589
+ listSnapshots,
590
+ updateSnapshot,
591
+ replaceSnapshotTags,
592
+ deleteSnapshot,
593
+ restoreSnapshot
594
+ });
595
+ });
596
+ const snapshotsApiLayer = Layer.effect(SnapshotsApi, makeSnapshotsApi);
597
+ //#endregion
598
+ //#region src/tenants/service.ts
599
+ /** Tenant administration. */
600
+ var TenantsApi = class extends Context.Service()("@aperture-browser/api-client/TenantsApi") {};
601
+ //#endregion
602
+ //#region src/tenants/layer.ts
603
+ const makeTenantsApi = Effect.gen(function* () {
604
+ const httpClient = yield* HttpClient.HttpClient;
605
+ const { authorize } = yield* ApiAuthorization;
606
+ const api = Api.make(httpClient);
607
+ const listTenants = Effect.fn("TenantsApi.listTenants")(function* (credentials, params = {}) {
608
+ return yield* api.listTenants({ params: compactQuery({
609
+ limit: params.limit,
610
+ cursor: params.cursor,
611
+ includeDeleted: params.includeDeleted || void 0,
612
+ deleted: params.deleted
613
+ }) }).pipe(authorize(Authorization.of(credentials)));
614
+ });
615
+ const createTenant = Effect.fn("TenantsApi.createTenant")(function* (credentials, input) {
616
+ return yield* api.createTenant({ payload: input }).pipe(authorize(Authorization.of(credentials)));
617
+ });
618
+ const updateTenant = Effect.fn("TenantsApi.updateTenant")(function* (credentials, tenantId, input) {
619
+ return yield* api.updateTenant(tenantId, { payload: input }).pipe(authorize(Authorization.of(credentials)));
620
+ });
621
+ const deleteTenant = Effect.fn("TenantsApi.deleteTenant")(function* (credentials, tenantId) {
622
+ return yield* api.deleteTenant(tenantId, void 0).pipe(authorize(Authorization.of(credentials)));
623
+ });
624
+ const restoreTenant = Effect.fn("TenantsApi.restoreTenant")(function* (credentials, tenantId) {
625
+ return yield* api.restoreTenant(tenantId, void 0).pipe(authorize(Authorization.of(credentials)));
626
+ });
627
+ return TenantsApi.of({
628
+ listTenants,
629
+ createTenant,
630
+ updateTenant,
631
+ deleteTenant,
632
+ restoreTenant
633
+ });
634
+ });
635
+ const tenantsApiLayer = Layer.effect(TenantsApi, makeTenantsApi);
636
+ //#endregion
637
+ //#region src/tokens/service.ts
638
+ /** API tokens, issued either deployment-wide or within the current tenant. */
639
+ var TokensApi = class extends Context.Service()("@aperture-browser/api-client/TokensApi") {};
640
+ //#endregion
641
+ //#region src/tokens/layer.ts
642
+ const makeTokensApi = Effect.gen(function* () {
643
+ const httpClient = yield* HttpClient.HttpClient;
644
+ const { authorize } = yield* ApiAuthorization;
645
+ const api = Api.make(httpClient);
646
+ const listAdminTokens = Effect.fn("TokensApi.listAdminTokens")(function* (credentials, params = {}) {
647
+ return yield* api.listAdminTokens({ params: compactQuery({
648
+ limit: params.limit,
649
+ cursor: params.cursor,
650
+ tenantId: params.tenantId,
651
+ name: params.name,
652
+ authorityType: params.authorityType,
653
+ revoked: params.revoked,
654
+ scope: params.scope
655
+ }) }).pipe(authorize(Authorization.of(credentials)));
656
+ });
657
+ const createAdminToken = Effect.fn("TokensApi.createAdminToken")(function* (credentials, input) {
658
+ const systemAdmin = input.authorityType === "system_admin";
659
+ return yield* api.createAdminToken({ payload: {
660
+ name: input.name,
661
+ authorityType: input.authorityType,
662
+ tenantId: input.tenantId ?? null,
663
+ scopes: input.scopes,
664
+ resourceMode: systemAdmin ? "all" : input.resourceMode,
665
+ resourceGrants: systemAdmin ? [] : input.resourceGrants,
666
+ expiresAt: input.expiresAt ?? null
667
+ } }).pipe(authorize(Authorization.of(credentials)));
668
+ });
669
+ const revokeAdminToken = Effect.fn("TokensApi.revokeAdminToken")(function* (credentials, tokenId) {
670
+ yield* api.revokeAdminToken(tokenId, void 0).pipe(authorize(Authorization.of(credentials)));
671
+ });
672
+ const listTenantTokens = Effect.fn("TokensApi.listTenantTokens")(function* (credentials, params = {}) {
673
+ return yield* api.listTenantTokens({ params: compactQuery({
674
+ limit: params.limit,
675
+ cursor: params.cursor,
676
+ name: params.name,
677
+ revoked: params.revoked,
678
+ scope: params.scope
679
+ }) }).pipe(authorize(Authorization.of(credentials)));
680
+ });
681
+ const createTenantToken = Effect.fn("TokensApi.createTenantToken")(function* (credentials, input) {
682
+ return yield* api.createTenantToken({ payload: {
683
+ name: input.name,
684
+ scopes: input.scopes,
685
+ resourceMode: input.resourceMode,
686
+ resourceGrants: input.resourceGrants,
687
+ expiresAt: input.expiresAt ?? null
688
+ } }).pipe(authorize(Authorization.of(credentials)));
689
+ });
690
+ const revokeTenantToken = Effect.fn("TokensApi.revokeTenantToken")(function* (credentials, tokenId) {
691
+ yield* api.revokeTenantToken(tokenId, void 0).pipe(authorize(Authorization.of(credentials)));
692
+ });
693
+ return TokensApi.of({
694
+ listAdminTokens,
695
+ createAdminToken,
696
+ revokeAdminToken,
697
+ listTenantTokens,
698
+ createTenantToken,
699
+ revokeTenantToken
700
+ });
701
+ });
702
+ const tokensApiLayer = Layer.effect(TokensApi, makeTokensApi);
703
+ //#endregion
704
+ //#region src/users/service.ts
705
+ /** User administration and tenant memberships. */
706
+ var UsersApi = class extends Context.Service()("@aperture-browser/api-client/UsersApi") {};
707
+ //#endregion
708
+ //#region src/users/layer.ts
709
+ const makeUsersApi = Effect.gen(function* () {
710
+ const httpClient = yield* HttpClient.HttpClient;
711
+ const { authorize } = yield* ApiAuthorization;
712
+ const api = Api.make(httpClient);
713
+ const listUsers = Effect.fn("UsersApi.listUsers")(function* (credentials, params = {}) {
714
+ return yield* api.listUsers({ params: compactQuery({
715
+ limit: params.limit,
716
+ cursor: params.cursor,
717
+ query: params.query,
718
+ disabled: params.disabled
719
+ }) }).pipe(authorize(Authorization.of(credentials)));
720
+ });
721
+ const createUser = Effect.fn("UsersApi.createUser")(function* (credentials, input) {
722
+ return yield* api.createUser({ payload: input }).pipe(authorize(Authorization.of(credentials)));
723
+ });
724
+ const getUser = Effect.fn("UsersApi.getUser")(function* (credentials, userId) {
725
+ return yield* api.getUser(userId, void 0).pipe(authorize(Authorization.of(credentials)));
726
+ });
727
+ const updateUser = Effect.fn("UsersApi.updateUser")(function* (credentials, userId, input) {
728
+ return yield* api.updateUser(userId, { payload: input }).pipe(authorize(Authorization.of(credentials)));
729
+ });
730
+ const createUserInvitation = Effect.fn("UsersApi.createUserInvitation")(function* (credentials, userId) {
731
+ return yield* api.createUserInvitation(userId, void 0).pipe(authorize(Authorization.of(credentials)));
732
+ });
733
+ const disableUser = Effect.fn("UsersApi.disableUser")(function* (credentials, userId) {
734
+ return yield* api.disableUser(userId, void 0).pipe(authorize(Authorization.of(credentials)));
735
+ });
736
+ const restoreUser = Effect.fn("UsersApi.restoreUser")(function* (credentials, userId) {
737
+ return yield* api.restoreUser(userId, void 0).pipe(authorize(Authorization.of(credentials)));
738
+ });
739
+ const listUserMemberships = Effect.fn("UsersApi.listUserMemberships")(function* (credentials, userId) {
740
+ return yield* api.listUserMemberships(userId, void 0).pipe(authorize(Authorization.of(credentials)));
741
+ });
742
+ const upsertTenantMembership = Effect.fn("UsersApi.upsertTenantMembership")(function* (credentials, tenantId, userId, scopes) {
743
+ const payload = { scopes };
744
+ return yield* api.upsertTenantMembership(tenantId, userId, { payload }).pipe(authorize(Authorization.of(credentials)));
745
+ });
746
+ const deleteTenantMembership = Effect.fn("UsersApi.deleteTenantMembership")(function* (credentials, tenantId, userId) {
747
+ yield* api.deleteTenantMembership(tenantId, userId, void 0).pipe(authorize(Authorization.of(credentials)));
748
+ });
749
+ return UsersApi.of({
750
+ listUsers,
751
+ createUser,
752
+ getUser,
753
+ updateUser,
754
+ createUserInvitation,
755
+ disableUser,
756
+ restoreUser,
757
+ listUserMemberships,
758
+ upsertTenantMembership,
759
+ deleteTenantMembership
760
+ });
761
+ });
762
+ const usersApiLayer = Layer.effect(UsersApi, makeUsersApi);
763
+ //#endregion
764
+ //#region src/layer.ts
765
+ /**
766
+ * Every API service, sharing one ApiAuthorization. The services send requests through the
767
+ * HttpClient the app provides, decorated with each call's authorization.
768
+ */
769
+ const apiClientLayer = Layer.mergeAll(authApiLayer, tenantsApiLayer, usersApiLayer, sessionsApiLayer, snapshotsApiLayer, tokensApiLayer, eventsApiLayer).pipe(Layer.provide(authorizedHttpClientLayer), Layer.provideMerge(apiAuthorizationLayer));
770
+ const baseUrlLayer = (baseUrl) => Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, HttpClient.mapRequest(HttpClientRequest.prependUrl(baseUrl))));
771
+ //#endregion
772
+ //#region src/pagination.ts
773
+ function getNextPageParam(page) {
774
+ return page.meta.hasMore ? page.meta.nextCursor : void 0;
775
+ }
776
+ function flattenInfinitePages(pages) {
777
+ return pages?.flatMap((page) => page.data) ?? [];
778
+ }
779
+ const defaultListLimit = 50;
780
+ const listQueryDefaults = { refetchOnWindowFocus: true };
781
+ //#endregion
782
+ //#region src/schemas.ts
783
+ const SessionStatus = Api.SessionStatus;
784
+ //#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 };
786
+
787
+ //# sourceMappingURL=index.js.map